mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44cf9ab26a | ||
|
|
77366d51ed | ||
|
|
fc0688c223 | ||
|
|
626c7f26fd | ||
|
|
1d947d934b | ||
|
|
4203837648 | ||
|
|
f5cba4c2f1 | ||
|
|
c2a1db3fcb | ||
|
|
594916850f | ||
|
|
c718d6bad8 | ||
|
|
abfda60ff1 | ||
|
|
92cced9bcc | ||
|
|
1929a7c546 | ||
|
|
df7f5a369d | ||
|
|
0de9861da4 | ||
|
|
6b223e4d23 | ||
|
|
48568cdb89 | ||
|
|
8d83dedc0f | ||
|
|
a25d8ee0e7 | ||
|
|
c30bd0fabb | ||
|
|
c9fbf79d6a | ||
|
|
f39e8738da | ||
|
|
c6d22e6ece | ||
|
|
981f10e4da | ||
|
|
076cd0d19d | ||
|
|
5cd82f2002 | ||
|
|
d4922cb0fb | ||
|
|
870d882fef | ||
|
|
e2f873c95c | ||
|
|
efd7b47388 | ||
|
|
a08669df72 | ||
|
|
b8db509581 | ||
|
|
5d6b544021 | ||
|
|
346cd417ca | ||
|
|
f61675e9ce | ||
|
|
8026838563 | ||
|
|
5bd8fa8c4e | ||
|
|
37ef2cb600 | ||
|
|
5184901a5b | ||
|
|
4172959e49 | ||
|
|
eb5b4bca3e | ||
|
|
56db115a1a | ||
|
|
b14edd515a | ||
|
|
57030b5fb9 | ||
|
|
72532de780 | ||
|
|
25323bfb8e | ||
|
|
9ed5dbf483 | ||
|
|
3ca57068a8 | ||
|
|
075471cdbd | ||
|
|
62a0ad5639 | ||
|
|
8bf8f6f906 | ||
|
|
249c1ca574 | ||
|
|
6a843b3088 | ||
|
|
2e94314b78 | ||
|
|
c59c15f66a | ||
|
|
4d1613ba55 | ||
|
|
254cf1dc21 | ||
|
|
607e84deed | ||
|
|
df0e9fca71 | ||
|
|
b831dae8d5 | ||
|
|
0005a50517 | ||
|
|
9f302373d6 | ||
|
|
f896c7396f | ||
|
|
6ca48e40fe | ||
|
|
1cefb9780b | ||
|
|
a1a8a18575 | ||
|
|
bae222227a | ||
|
|
37a050b106 | ||
|
|
12a67f596c | ||
|
|
164bfd810b | ||
|
|
274c234aff | ||
|
|
b8dc4a6004 | ||
|
|
20e1b417cc | ||
|
|
3e8c8b756f | ||
|
|
4dbdbd3bcd | ||
|
|
763d4c3207 | ||
|
|
5eeba29579 | ||
|
|
9351d66dbc | ||
|
|
dae8a40af1 | ||
|
|
e9bd520d9b | ||
|
|
8b78379f6f | ||
|
|
1dc217b32c | ||
|
|
535e9f8b15 | ||
|
|
35626da5c4 | ||
|
|
b9844ed7c1 | ||
|
|
8496e7c7eb | ||
|
|
f0ed5c1b8e | ||
|
|
c247ad5807 | ||
|
|
b604188849 | ||
|
|
7514587b5a | ||
|
|
cf8f928db8 | ||
|
|
5b38f61961 | ||
|
|
176d130f09 | ||
|
|
8bdab8005b | ||
|
|
0b94e02de5 | ||
|
|
273c7ebcf0 | ||
|
|
fe7a5ee1b2 | ||
|
|
3a40778c4b | ||
|
|
5331150cb9 | ||
|
|
37a7f35a27 | ||
|
|
dc2f3a477b | ||
|
|
c6ed9429be | ||
|
|
21753b0e4c | ||
|
|
cb6af44984 | ||
|
|
f509b19b1d | ||
|
|
7464179249 | ||
|
|
c7ac5de28e | ||
|
|
315e9cb194 | ||
|
|
2e7073a890 | ||
|
|
e8d9a913d8 | ||
|
|
305701326c | ||
|
|
4453f1910d | ||
|
|
8c89b1618a | ||
|
|
1b0be9a997 | ||
|
|
3e4ce5caa7 | ||
|
|
f80f6f4a62 | ||
|
|
66caf907fd | ||
|
|
d3150399c7 | ||
|
|
f1c6a12c06 | ||
|
|
947442ec78 | ||
|
|
165dd003d7 | ||
|
|
b852ced3c1 | ||
|
|
4a03d62b91 | ||
|
|
056574eebe | ||
|
|
e61685547a | ||
|
|
7ebaf43282 | ||
|
|
15580ff6a6 | ||
|
|
fd6f5bca83 | ||
|
|
b6d311f20b | ||
|
|
9c0d5517bd | ||
|
|
3445ab9304 | ||
|
|
533219ede7 | ||
|
|
b1f55026af | ||
|
|
a26e9aaf25 | ||
|
|
a55a0645e2 | ||
|
|
e529e12d27 | ||
|
|
e78eee972e | ||
|
|
37cd5b42de | ||
|
|
7fe5247626 | ||
|
|
f098983c9f | ||
|
|
516d2a659e | ||
|
|
808c5dcc46 | ||
|
|
ecea8054a6 | ||
|
|
b0076af9bd | ||
|
|
24cf1e3a7f | ||
|
|
e5ee4cde4f | ||
|
|
e8e1521972 | ||
|
|
6f53b9a6bb | ||
|
|
acaa9f6dc7 | ||
|
|
375f2df694 | ||
|
|
542e50be33 | ||
|
|
ad9ee99521 | ||
|
|
25395a9f9a | ||
|
|
d7029952bb | ||
|
|
340449b77e | ||
|
|
527e229ac8 | ||
|
|
d5bc753764 | ||
|
|
436f7f7e86 | ||
|
|
009e37ec6f | ||
|
|
b253df881d | ||
|
|
c1743aa42d | ||
|
|
0f99d93300 | ||
|
|
e9fa99e16b | ||
|
|
e18d369adf | ||
|
|
22ac8a8c10 | ||
|
|
bebe534bad | ||
|
|
9ffcb23877 | ||
|
|
dc3c2cc5c7 | ||
|
|
4dd2b2216c | ||
|
|
0cada09aa7 | ||
|
|
3ff8cafac6 | ||
|
|
041de6cba3 | ||
|
|
aa5c33a42d | ||
|
|
f892f609c2 | ||
|
|
5e8106114f | ||
|
|
95876d9d8c | ||
|
|
e460536119 | ||
|
|
561d8992bc | ||
|
|
d5e19cb7ba | ||
|
|
d40f753983 | ||
|
|
eb090c6170 | ||
|
|
7b00255b11 | ||
|
|
f0dd5d667b | ||
|
|
7eb3994b02 | ||
|
|
7d31a6fcd7 | ||
|
|
096d6f591b | ||
|
|
19348631ab | ||
|
|
4e558ee142 | ||
|
|
effdaabab3 | ||
|
|
a394fe1af3 | ||
|
|
d16b7ccd6a | ||
|
|
28facc1c3f | ||
|
|
85b68a9969 | ||
|
|
a6a5edf573 | ||
|
|
bd208783d7 | ||
|
|
08e808ef20 | ||
|
|
bb3a18c627 | ||
|
|
74ae1e4a29 | ||
|
|
139de76347 | ||
|
|
2395a6ded2 | ||
|
|
41b15955b0 | ||
|
|
07055bb531 | ||
|
|
638999213e | ||
|
|
5ea49f3c50 | ||
|
|
9fbb708e64 | ||
|
|
f355080b6f | ||
|
|
b8ffd25148 | ||
|
|
2ed96e9678 | ||
|
|
fbbf3c4beb | ||
|
|
b88066b73b | ||
|
|
83d475eb02 | ||
|
|
292576d2a1 | ||
|
|
0c8af978db | ||
|
|
23671f1a99 | ||
|
|
94e882762e | ||
|
|
672538f4f1 | ||
|
|
a9763639ed | ||
|
|
616e694bdd | ||
|
|
0bee379b61 | ||
|
|
d35e452368 | ||
|
|
86f322e252 | ||
|
|
b40def47eb | ||
|
|
93cf3559e1 | ||
|
|
8f947253ad | ||
|
|
8cce59302b | ||
|
|
74ad6d76eb | ||
|
|
95f2f5bab9 | ||
|
|
e5ef9b2ace | ||
|
|
0a138276f8 | ||
|
|
1549598e52 | ||
|
|
1902518cd6 | ||
|
|
bcb8a9b57b | ||
|
|
bdb276cd68 | ||
|
|
79aa381722 | ||
|
|
45f1a13cc3 | ||
|
|
afdbf0a194 | ||
|
|
940ab5fd8e | ||
|
|
6418561d3c | ||
|
|
ce2b5a793f | ||
|
|
6ecefaec75 | ||
|
|
bdf29fc4d5 | ||
|
|
51d9fa91ed | ||
|
|
5cdc6c902e | ||
|
|
03696f8a1a | ||
|
|
7c26ff1b81 | ||
|
|
d472d8c32e | ||
|
|
75e5fe1dd3 | ||
|
|
fb1da4bbd0 | ||
|
|
ac43c0224c | ||
|
|
011b2ad6f8 | ||
|
|
e83e6ed76e | ||
|
|
edec6e4e62 | ||
|
|
cd96db7829 | ||
|
|
ca38d8fe86 | ||
|
|
f10f7df389 | ||
|
|
1cd75f7f54 | ||
|
|
1c84422f8e | ||
|
|
ba330cd70a | ||
|
|
4439162fea | ||
|
|
91120d86ba | ||
|
|
71f5ba9601 | ||
|
|
2e14b44349 | ||
|
|
ac66ea7790 | ||
|
|
632a4f0859 | ||
|
|
e2e4b6e579 | ||
|
|
37255523b1 | ||
|
|
c3a830e9e6 | ||
|
|
8266376838 | ||
|
|
d8e3c29744 | ||
|
|
ef3273674b | ||
|
|
633a25b456 | ||
|
|
b9de562491 | ||
|
|
76f5a23b7f | ||
|
|
4a3a226f27 | ||
|
|
ced7f28898 | ||
|
|
bc2db26e07 | ||
|
|
6649241193 | ||
|
|
fea8e615f9 | ||
|
|
57eb9bd415 | ||
|
|
0cd236414e | ||
|
|
e4957e089a | ||
|
|
3c643d943a | ||
|
|
9b06475811 | ||
|
|
fabae2465b | ||
|
|
233277d94b | ||
|
|
59976a7f7b | ||
|
|
4613167abb | ||
|
|
f5f63aa044 | ||
|
|
6495c6dad9 | ||
|
|
2d1b8cdd30 | ||
|
|
d2cbd2f596 | ||
|
|
6c7c5a1bc7 | ||
|
|
5b116696f0 | ||
|
|
2f1949e093 | ||
|
|
cca4df17d9 | ||
|
|
ef06d90b6b | ||
|
|
e7e6888768 | ||
|
|
a762346a3b | ||
|
|
d78892cad0 | ||
|
|
3fc6357f28 | ||
|
|
65ff056b2e | ||
|
|
f121aeb57f | ||
|
|
d61a0b6904 | ||
|
|
0ef9c76224 | ||
|
|
46f3192c67 | ||
|
|
b2aafc3f95 | ||
|
|
531ebb3537 | ||
|
|
6a11f96a5b | ||
|
|
4fa2e0a58d | ||
|
|
c3d08a2125 | ||
|
|
56be5318ab | ||
|
|
db388e64b9 | ||
|
|
5af927224f | ||
|
|
b425b37e19 | ||
|
|
0132781fff | ||
|
|
9db9513c30 | ||
|
|
2f62b90d7b | ||
|
|
f68c7296a6 | ||
|
|
8fd25acbb6 | ||
|
|
59c9b94d76 | ||
|
|
d23e08f564 | ||
|
|
9a48c3f10c | ||
|
|
790b542163 | ||
|
|
5032148cf0 | ||
|
|
1c14c7b3ba | ||
|
|
82fabe90b3 | ||
|
|
acf7341fb8 | ||
|
|
ac33292e1b | ||
|
|
5d6cb7dfed | ||
|
|
195330ccea | ||
|
|
f6114c9e15 | ||
|
|
d2f2a0039f | ||
|
|
943600edb2 | ||
|
|
76acae9889 | ||
|
|
9cca0a8753 | ||
|
|
8fe8d096fb | ||
|
|
496fa50a23 | ||
|
|
9137396eae | ||
|
|
2f52264f01 | ||
|
|
f0f6d1e5fa | ||
|
|
18d19a9a8b | ||
|
|
3f53041ed2 | ||
|
|
c08ac7db72 | ||
|
|
7419f62159 | ||
|
|
252e59334d | ||
|
|
565dc90bf0 | ||
|
|
84eddaef2f | ||
|
|
641bfb1dd9 | ||
|
|
b87b698148 | ||
|
|
dac5f8964f | ||
|
|
85ffcb74d8 | ||
|
|
9e719461e2 | ||
|
|
f270988e03 | ||
|
|
b4f9401395 | ||
|
|
a1e2007b82 | ||
|
|
e92a57011f | ||
|
|
3736e1fc38 | ||
|
|
2660e1669c | ||
|
|
be6818effe | ||
|
|
a49a463acf | ||
|
|
317b3602c3 | ||
|
|
b637962da0 | ||
|
|
44f9dcb3a2 | ||
|
|
1a2b337618 | ||
|
|
d219ac3f6f | ||
|
|
1a2f867ba9 | ||
|
|
0329f40df5 | ||
|
|
193204de52 | ||
|
|
3f945dcfeb | ||
|
|
2f9ccb84d9 | ||
|
|
f10ea4ebcd | ||
|
|
ada6923039 | ||
|
|
f4cf398651 | ||
|
|
84dba77275 | ||
|
|
19e4ba386d | ||
|
|
7d101182cd | ||
|
|
e60b044ff7 | ||
|
|
4366909cf0 | ||
|
|
7b1d8ce9dd | ||
|
|
94fdeb633f | ||
|
|
50eda634e0 | ||
|
|
2bf75537d8 | ||
|
|
f780736f29 | ||
|
|
85e71622f8 | ||
|
|
ea02a2fac9 | ||
|
|
8368901bf9 | ||
|
|
a18170cdb1 | ||
|
|
e70fb39cb9 | ||
|
|
e7ba689ca2 | ||
|
|
78dcf43c72 | ||
|
|
70951f46e5 | ||
|
|
a6b4c4b049 | ||
|
|
db3d569ed0 | ||
|
|
ec0a1a0b70 | ||
|
|
42e3cce8c3 | ||
|
|
c82062a51b | ||
|
|
61349ac0c2 | ||
|
|
102bd2cfd2 | ||
|
|
e7bb46e819 | ||
|
|
02f8c7ab56 | ||
|
|
96ecabc38b | ||
|
|
57600f2bd3 | ||
|
|
a8628feb3c | ||
|
|
e2695235ab | ||
|
|
44c3ea7844 | ||
|
|
7c81906459 | ||
|
|
4e3679c7b7 | ||
|
|
19c0ae4e33 | ||
|
|
565649e3b6 | ||
|
|
527ef9653a | ||
|
|
23bb5245bd | ||
|
|
e3c4b94b11 | ||
|
|
3fa89640cd | ||
|
|
722bddf916 | ||
|
|
294dad1773 | ||
|
|
b33ae1c481 | ||
|
|
0dc3a0916e | ||
|
|
24372c8558 | ||
|
|
49faa5e749 | ||
|
|
69a3985f9d | ||
|
|
d335131f52 | ||
|
|
fe1b6db653 | ||
|
|
4c2c4cb565 | ||
|
|
bc8109b695 | ||
|
|
a88ca75c14 | ||
|
|
e453a75ea9 | ||
|
|
627f737bbe | ||
|
|
ef3e8da1b5 | ||
|
|
c2d6134cfa | ||
|
|
d51723c153 | ||
|
|
94b2a863bf | ||
|
|
442510f793 | ||
|
|
75a0fc2c0c | ||
|
|
72b1f50314 | ||
|
|
66c36cd945 | ||
|
|
d091d9c460 | ||
|
|
d3a2e2e666 | ||
|
|
a4261f8b19 | ||
|
|
6051588458 | ||
|
|
5c1fd733da | ||
|
|
c499480692 | ||
|
|
3da0b1dfd4 | ||
|
|
720919455f | ||
|
|
df90753d85 | ||
|
|
7bee645b9a | ||
|
|
83a6f24f93 | ||
|
|
8d35be14a0 | ||
|
|
85bd0613ca | ||
|
|
727939af5b | ||
|
|
dd52f0381a | ||
|
|
cf165c0db5 | ||
|
|
ab9db43599 | ||
|
|
d553e363a7 | ||
|
|
be3c3eb9bb | ||
|
|
357807666d | ||
|
|
d1a5a4e39c | ||
|
|
a701c896f0 | ||
|
|
b0de886f8e | ||
|
|
ad1ca4ea92 | ||
|
|
fbaf5261e2 | ||
|
|
19ada4b8f9 | ||
|
|
ff41e59282 | ||
|
|
a15a13ca46 | ||
|
|
2937043e77 | ||
|
|
24efb0bb17 | ||
|
|
dd2bd267c1 | ||
|
|
5cfe9c8998 | ||
|
|
3bd8a62aa8 | ||
|
|
dcd37f3c38 | ||
|
|
a579fd2342 | ||
|
|
f93ff00642 | ||
|
|
8a628631fb | ||
|
|
59733a2a26 | ||
|
|
c7d385c03d | ||
|
|
27a8aa057c | ||
|
|
e63bfbabf0 | ||
|
|
c107d9edcf | ||
|
|
49e69c1c7d | ||
|
|
482b6d7bbf | ||
|
|
dcfe91bdfc | ||
|
|
6072b5b703 | ||
|
|
9c50ac8a69 | ||
|
|
8b7acad257 | ||
|
|
cb8cb48a60 | ||
|
|
8c00ae1d38 | ||
|
|
633b3d3b0a | ||
|
|
60199c0323 | ||
|
|
9e0e03d4c0 | ||
|
|
4972ebf914 | ||
|
|
4a36d63c1b | ||
|
|
d7e768096e | ||
|
|
e13b5d0618 | ||
|
|
c5bf0dc07d | ||
|
|
9fbd83d602 | ||
|
|
39c5b28dfb | ||
|
|
be533994e5 | ||
|
|
368c089172 | ||
|
|
30c6f55c2d | ||
|
|
407d4344c4 | ||
|
|
76e3950028 | ||
|
|
7337b00ca8 | ||
|
|
22582dea3e | ||
|
|
f600a07404 | ||
|
|
ff790e1ff1 | ||
|
|
a345369269 | ||
|
|
8e4a4359b4 | ||
|
|
0a000d1628 | ||
|
|
5bd8e369c4 | ||
|
|
777d756d1b | ||
|
|
7716a8e05d | ||
|
|
94e93b171c | ||
|
|
1bf12be278 | ||
|
|
99b753be9a | ||
|
|
bc2f2d896b | ||
|
|
659fbf26da | ||
|
|
f61a7bb9c0 | ||
|
|
212309083a | ||
|
|
c1ebd01a70 | ||
|
|
af58e2c7b4 | ||
|
|
b373535d6e | ||
|
|
4a3a44924a | ||
|
|
1b2a7989af | ||
|
|
83d1ce177e | ||
|
|
4321c4a827 | ||
|
|
a039ce0987 | ||
|
|
accfaab720 | ||
|
|
35658eb998 | ||
|
|
ccbde0196e | ||
|
|
0209c5461f | ||
|
|
810b4886c7 | ||
|
|
7dfc2149d8 | ||
|
|
9d1e8bd7cd | ||
|
|
bfa4049ac1 | ||
|
|
87c56ce3d5 | ||
|
|
f2ab50b84e | ||
|
|
b6ac6c182e | ||
|
|
5e9f0f0c70 | ||
|
|
e4455aed9a | ||
|
|
bc018c9513 | ||
|
|
2ecba4d70c | ||
|
|
1e11a5950e | ||
|
|
b324363db0 | ||
|
|
a654b15190 | ||
|
|
75ea7f8c0c | ||
|
|
09aeee4c03 | ||
|
|
87122973be | ||
|
|
8b9adc5121 | ||
|
|
9e42e40705 | ||
|
|
8a91225eb1 | ||
|
|
e82815802e | ||
|
|
67069d4a12 | ||
|
|
4a38e7224e | ||
|
|
645f2f748f | ||
|
|
5652f9ae5f | ||
|
|
f3b0b242dd | ||
|
|
876cb7bcd3 | ||
|
|
ee9a121c6f | ||
|
|
00439c5c4a | ||
|
|
d7286a5832 | ||
|
|
972804fdc7 | ||
|
|
bca7328421 | ||
|
|
8097c0d7b1 | ||
|
|
641f54e1b7 | ||
|
|
c36bfe0843 | ||
|
|
8ae66521e3 | ||
|
|
6e21fd9a35 | ||
|
|
a2e8faafe5 | ||
|
|
668fa3033e | ||
|
|
139f3f978d | ||
|
|
09f96e5ba5 | ||
|
|
592a69b1c4 | ||
|
|
9211341ae7 | ||
|
|
5f9d354adb | ||
|
|
0ae36b71f5 | ||
|
|
20e2768f27 | ||
|
|
00c8b5ae3a | ||
|
|
7687c19b33 | ||
|
|
fc3a7cd5f1 | ||
|
|
0b7f906533 | ||
|
|
c78a94ed09 | ||
|
|
debcb171ce | ||
|
|
282468c9d5 | ||
|
|
34b06da83b | ||
|
|
3d55f38dab | ||
|
|
16426db244 | ||
|
|
6f8c76f06c | ||
|
|
43f2043478 | ||
|
|
f61f068e28 | ||
|
|
e234d471fe | ||
|
|
6ff97a9bb1 | ||
|
|
8adf1d3b8a | ||
|
|
2935279603 | ||
|
|
918b39bb63 | ||
|
|
963e88943c | ||
|
|
1a75bcba5e | ||
|
|
b64309edfc | ||
|
|
e3f1db0a9a | ||
|
|
2c848ab44b | ||
|
|
9b23594415 | ||
|
|
e2e52965df | ||
|
|
6e7907f183 | ||
|
|
b4be1292b8 | ||
|
|
db58f4c214 | ||
|
|
59bad77176 | ||
|
|
3504fca8da | ||
|
|
e61817ff6a | ||
|
|
b9df28838c | ||
|
|
fff00e65c4 | ||
|
|
8320b4f456 | ||
|
|
a757669df5 | ||
|
|
e16c0645c0 | ||
|
|
8d37763fd3 | ||
|
|
e0d0551e7b | ||
|
|
caa814e8f9 | ||
|
|
2502d32a2e | ||
|
|
e57dd3529e | ||
|
|
4b8d809237 | ||
|
|
d42182befc | ||
|
|
5efdb1016e | ||
|
|
15e24cda78 | ||
|
|
5013108a7f | ||
|
|
359ba1c572 | ||
|
|
733523011b | ||
|
|
14fc13c34d | ||
|
|
e9fee1358d | ||
|
|
dae3dd9996 | ||
|
|
556db8b02e | ||
|
|
96e993eecf | ||
|
|
fb3e2123cc |
@@ -14,7 +14,6 @@ bugprone-forwarding-reference-overload,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-integer-division,
|
||||
bugprone-lambda-function-name,
|
||||
bugprone-macro-parentheses,
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
@@ -63,7 +62,6 @@ cert-str34-c,
|
||||
cppcoreguidelines-interfaces-global-init,
|
||||
cppcoreguidelines-narrowing-conversions,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
cppcoreguidelines-slicing,
|
||||
google-default-arguments,
|
||||
google-runtime-operator,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
tools/trace_replay/fixtures/*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||
tools/trace_replay/fixtures/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
tools/trace_replay/fixtures/openra.tgz -filter -diff -merge -text
|
||||
tools/trace_replay/fixtures/openra.0000031249.png -filter -diff -merge -text
|
||||
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
|
||||
echo "usage: $0 <trace-case> [fixture-dir]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
case_name="$1"
|
||||
fixture_dir="${2:-tools/trace_replay/fixtures}"
|
||||
python_bin="${PYTHON:-python3}"
|
||||
mirror_base="${MOBILEGL_TRACE_FIXTURE_MIRROR_BASE:-https://repo.miawa.cn/mgl/tools/trace_replay/fixtures}"
|
||||
download_attempts="${MOBILEGL_TRACE_FIXTURE_DOWNLOAD_ATTEMPTS:-5}"
|
||||
retry_delay="${MOBILEGL_TRACE_FIXTURE_RETRY_DELAY:-2}"
|
||||
|
||||
if ! command -v "${python_bin}" >/dev/null 2>&1 && command -v python >/dev/null 2>&1; then
|
||||
python_bin=python
|
||||
fi
|
||||
|
||||
if ! [[ "${download_attempts}" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "MOBILEGL_TRACE_FIXTURE_DOWNLOAD_ATTEMPTS must be a positive integer: ${download_attempts}" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! [[ "${retry_delay}" =~ ^[0-9]+$ ]]; then
|
||||
echo "MOBILEGL_TRACE_FIXTURE_RETRY_DELAY must be a non-negative integer: ${retry_delay}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
fixture_list="$("${python_bin}" tools/trace_replay/trace_cases.py \
|
||||
--format fixture-files \
|
||||
--case "${case_name}" \
|
||||
--fixture-root "${fixture_dir}")"
|
||||
mapfile -t files <<< "${fixture_list}"
|
||||
|
||||
include="$(IFS=,; echo "${files[*]}")"
|
||||
if [ "${case_name}" = "OpenRA" ]; then
|
||||
echo "Fixture files for ${case_name} are stored in Git: ${include}"
|
||||
for file in "${files[@]}"; do
|
||||
test -s "${file}"
|
||||
if head -n 1 "${file}" | grep -q "version https://git-lfs.github.com/spec/v1"; then
|
||||
echo "fixture should not be stored as an LFS pointer: ${file}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_lfs_metadata() {
|
||||
local file="$1"
|
||||
local pointer
|
||||
local expected_oid
|
||||
local expected_size
|
||||
|
||||
if ! pointer="$(git show "HEAD:${file}" 2>/dev/null)"; then
|
||||
echo "failed to read tracked fixture metadata: ${file}" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! grep -q '^version https://git-lfs.github.com/spec/v1$' <<< "${pointer}"; then
|
||||
echo "tracked fixture is not a Git LFS pointer: ${file}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
expected_oid="$(awk '$1 == "oid" && $2 ~ /^sha256:/ { sub(/^sha256:/, "", $2); print $2 }' <<< "${pointer}")"
|
||||
expected_size="$(awk '$1 == "size" { print $2 }' <<< "${pointer}")"
|
||||
if ! [[ "${expected_oid}" =~ ^[0-9a-f]{64}$ ]] || ! [[ "${expected_size}" =~ ^[0-9]+$ ]]; then
|
||||
echo "invalid Git LFS pointer metadata: ${file}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s %s\n' "${expected_oid}" "${expected_size}"
|
||||
}
|
||||
|
||||
verify_fixture_file() {
|
||||
local downloaded_file="$1"
|
||||
local display_name="$2"
|
||||
local expected_oid="$3"
|
||||
local expected_size="$4"
|
||||
local actual_oid
|
||||
local actual_size
|
||||
|
||||
if [ ! -f "${downloaded_file}" ]; then
|
||||
echo "fixture file is missing: ${display_name}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
actual_size="$(wc -c < "${downloaded_file}" | tr -d '[:space:]')"
|
||||
if [ "${actual_size}" != "${expected_size}" ]; then
|
||||
echo "fixture size mismatch for ${display_name}: expected ${expected_size}, got ${actual_size}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
actual_oid="$(sha256sum "${downloaded_file}" | awk '{ print $1 }')"
|
||||
if [ "${actual_oid}" != "${expected_oid}" ]; then
|
||||
echo "fixture SHA-256 mismatch for ${display_name}: expected ${expected_oid}, got ${actual_oid}" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_file_from_mirror() {
|
||||
local file="$1"
|
||||
local url="$2"
|
||||
local metadata
|
||||
local expected_oid
|
||||
local expected_size
|
||||
local tmp_file="${file}.tmp"
|
||||
local attempt
|
||||
local partial_size
|
||||
local curl_status
|
||||
|
||||
metadata="$(get_lfs_metadata "${file}")" || return 1
|
||||
read -r expected_oid expected_size <<< "${metadata}"
|
||||
|
||||
if [ -f "${tmp_file}" ]; then
|
||||
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
|
||||
if [ "${partial_size}" -gt "${expected_size}" ]; then
|
||||
echo "Discarding oversized partial fixture ${tmp_file}: ${partial_size} > ${expected_size}" >&2
|
||||
rm -f "${tmp_file}"
|
||||
elif [ "${partial_size}" = "${expected_size}" ]; then
|
||||
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
|
||||
mv "${tmp_file}" "${file}"
|
||||
return 0
|
||||
fi
|
||||
rm -f "${tmp_file}"
|
||||
fi
|
||||
fi
|
||||
|
||||
for ((attempt = 1; attempt <= download_attempts; attempt++)); do
|
||||
partial_size=0
|
||||
if [ -f "${tmp_file}" ]; then
|
||||
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
|
||||
fi
|
||||
|
||||
if [ "${partial_size}" -gt 0 ]; then
|
||||
echo "Resuming mirror download for ${file} at byte ${partial_size} (attempt ${attempt}/${download_attempts})"
|
||||
else
|
||||
echo "Starting mirror download for ${file} (attempt ${attempt}/${download_attempts})"
|
||||
fi
|
||||
|
||||
if curl -L --fail --show-error --continue-at - --output "${tmp_file}" "${url}"; then
|
||||
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
|
||||
mv "${tmp_file}" "${file}"
|
||||
return 0
|
||||
fi
|
||||
echo "Mirror download failed integrity verification; retrying from the beginning: ${file}" >&2
|
||||
rm -f "${tmp_file}"
|
||||
else
|
||||
curl_status=$?
|
||||
partial_size=0
|
||||
if [ -f "${tmp_file}" ]; then
|
||||
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
|
||||
fi
|
||||
|
||||
if [ "${partial_size}" = "${expected_size}" ]; then
|
||||
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
|
||||
mv "${tmp_file}" "${file}"
|
||||
return 0
|
||||
fi
|
||||
rm -f "${tmp_file}"
|
||||
partial_size=0
|
||||
elif [ "${partial_size}" -gt "${expected_size}" ]; then
|
||||
echo "Discarding oversized partial fixture ${tmp_file}: ${partial_size} > ${expected_size}" >&2
|
||||
rm -f "${tmp_file}"
|
||||
partial_size=0
|
||||
elif [ "${curl_status}" -eq 33 ]; then
|
||||
echo "Mirror refused the resume request; retrying from the beginning: ${file}" >&2
|
||||
rm -f "${tmp_file}"
|
||||
partial_size=0
|
||||
fi
|
||||
|
||||
echo "Mirror download attempt ${attempt}/${download_attempts} failed with curl exit ${curl_status}; retained ${partial_size} bytes for resume: ${file}" >&2
|
||||
fi
|
||||
|
||||
if [ "${attempt}" -lt "${download_attempts}" ]; then
|
||||
sleep "${retry_delay}"
|
||||
fi
|
||||
done
|
||||
|
||||
rm -f "${tmp_file}"
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_from_mirror() {
|
||||
mkdir -p "${fixture_dir}"
|
||||
for file in "${files[@]}"; do
|
||||
local name
|
||||
local url
|
||||
name="$(basename "${file}")"
|
||||
url="${mirror_base%/}/${name}"
|
||||
echo "Fetching trace fixture from mirror: ${url}"
|
||||
if ! fetch_file_from_mirror "${file}" "${url}"; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if fetch_from_mirror; then
|
||||
echo "Fetched trace fixture files for ${case_name} from mirror: ${include}"
|
||||
else
|
||||
echo "Mirror fetch failed for ${case_name}; falling back to Git LFS: ${include}"
|
||||
git lfs install --local
|
||||
git lfs pull --include="${include}" --exclude=""
|
||||
fi
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
metadata="$(get_lfs_metadata "${file}")"
|
||||
read -r expected_oid expected_size <<< "${metadata}"
|
||||
verify_fixture_file "${file}" "${file}" "${expected_oid}" "${expected_size}"
|
||||
done
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "Usage: $0 <aapt2> <plugin-apk> <trace-apk>" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
aapt2=$1
|
||||
plugin_apk=$2
|
||||
trace_apk=$3
|
||||
|
||||
require() {
|
||||
local needle=$1
|
||||
local content=$2
|
||||
local description=$3
|
||||
if ! grep -Fq -- "$needle" <<<"$content"; then
|
||||
echo "::error::Missing ${description}: ${needle}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
for apk in "$plugin_apk" "$trace_apk"; do
|
||||
[[ -f "$apk" ]] || { echo "::error::APK not found: $apk" >&2; exit 1; }
|
||||
done
|
||||
|
||||
plugin_manifest=$("$aapt2" dump xmltree --file AndroidManifest.xml "$plugin_apk")
|
||||
plugin_resources=$("$aapt2" dump resources "$plugin_apk")
|
||||
plugin_resource_text=$(tr -d '"' <<<"$plugin_resources")
|
||||
trace_manifest=$("$aapt2" dump xmltree --file AndroidManifest.xml "$trace_apk")
|
||||
plugin_contents=$(unzip -Z1 "$plugin_apk")
|
||||
|
||||
require 'top.mobilegl.plugin' "$plugin_manifest" 'plugin package name'
|
||||
require 'MobileGL' "$plugin_manifest" 'plugin label'
|
||||
require 'fclPlugin' "$plugin_manifest" 'legacy plugin marker'
|
||||
require 'fclPlugin_V2' "$plugin_manifest" 'V2 plugin marker'
|
||||
require 'LIBGL_ES=3:POJAV_RENDERER=opengles3:MOBILEGL_BACKEND_TYPE=DirectGLES' "$plugin_manifest" 'V1 DirectGLES fallback'
|
||||
require 'string/config' "$plugin_resources" 'V2 renderer configuration resource'
|
||||
require '{displayName:MobileGL,rendererId:opengles3' "$plugin_resource_text" 'V2 MobileGL entry and renderer ID'
|
||||
require 'rendererGLPath:**|libMobileGL.so' "$plugin_resource_text" 'V2 GL library path'
|
||||
require 'rendererEGLPath:**|libMobileGL.so' "$plugin_resource_text" 'V2 EGL library path'
|
||||
require 'key:LIBGL_ES,value:3' "$plugin_resource_text" 'V2 fixed LIBGL_ES variable'
|
||||
require 'key:MOBILEGL_BACKEND_TYPE' "$plugin_resource_text" 'V2 backend variable'
|
||||
require 'defaultValue:DirectGLES' "$plugin_resource_text" 'V2 DirectGLES default'
|
||||
require 'DirectVulkan' "$plugin_resource_text" 'V2 DirectVulkan option'
|
||||
require 'key:MOBILEGL_DISABLE_TIMERQUERY' "$plugin_resource_text" 'V2 timer-query toggle'
|
||||
require 'key:MOBILEGL_DISABLE_SUBGROUP' "$plugin_resource_text" 'V2 Vulkan subgroup toggle'
|
||||
require 'key:MOBILEGL_MAGMA_R11G11B10F_FALLBACK' "$plugin_resource_text" 'V2 Magma format fallback toggle'
|
||||
require 'key:MOBILEGL_MAGMA_FRAMESINFLIGHT' "$plugin_resource_text" 'V2 Magma frames-in-flight setting'
|
||||
require 'key:MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER' "$plugin_resource_text" 'V2 sampler workaround toggle'
|
||||
require 'key:MOBILEGL_COHERENT_AS_FLUSH' "$plugin_resource_text" 'V2 coherent-as-flush toggle'
|
||||
require 'key:MOBILEGL_USE_ANGLE' "$plugin_resource_text" 'V2 ANGLE toggle'
|
||||
|
||||
if [[ $(grep -Fc 'fclPlugin_V2' <<<"$plugin_manifest") -ne 1 ]]; then
|
||||
echo '::error::Plugin manifest must expose exactly one V2 descriptor' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -Eq '^lib/[^/]+/libMobileGL\.so$' <<<"$plugin_contents"; then
|
||||
echo '::error::Plugin APK does not contain libMobileGL.so' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require 'top.mobilegl.plugin.trace' "$trace_manifest" 'trace package name'
|
||||
require 'top.mobilegl.plugin.TRACE_REPLAY' "$trace_manifest" 'trace replay action'
|
||||
if grep -Fq 'fclPlugin' <<<"$trace_manifest"; then
|
||||
echo '::error::Trace APK must not advertise renderer-plugin metadata' >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Fq 'android.intent.action.MAIN' <<<"$trace_manifest"; then
|
||||
echo '::error::Trace APK must not expose a launcher activity' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 'Validated unified MobileGL plugin APK and isolated trace APK.'
|
||||
@@ -0,0 +1,560 @@
|
||||
name: MobileGL APK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- Feat/Backend-Direct-GLES
|
||||
- Feat/Backend-Direct-Vulkan
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||
CCACHE_COMPRESS: "true"
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 4G
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
MOBILEGL_CMAKE_COMPILER_LAUNCHER: ccache
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set artifact metadata
|
||||
run: |
|
||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
with:
|
||||
gradle-version: 8.10.2
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ runner.os }}-apk-${{ github.job }}-ccache-${{ github.ref_name }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-apk-${{ github.job }}-ccache-${{ github.ref_name }}-
|
||||
${{ runner.os }}-apk-${{ github.job }}-ccache-
|
||||
|
||||
- name: Install ccache
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ccache
|
||||
ccache --version
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
accept-android-sdk-licenses: false
|
||||
|
||||
- name: Accept Android SDK licenses
|
||||
run: yes | sdkmanager --licenses >/dev/null
|
||||
|
||||
- name: Install Android NDK
|
||||
run: |
|
||||
sdkmanager "ndk;27.3.13750724"
|
||||
echo "ndk.dir=$ANDROID_HOME/ndk/27.3.13750724" >> android-plugin/local.properties
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: 3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
|
||||
- name: Build plugin APK
|
||||
run: gradle --no-daemon -p android-plugin :app:assemblePluginRelease -Pmobilegl.apkSuffix="${GITHUB_SHA}" -Pmobilegl.logLevel=MOBILEGL_LOG_LEVEL_INFO --parallel --max-workers "$(nproc)"
|
||||
env:
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
|
||||
- name: Download ANGLE x86_64 libraries
|
||||
run: |
|
||||
angle_dir="android-plugin/app/src/trace/jniLibs/x86_64"
|
||||
rm -rf "${angle_dir}"
|
||||
mkdir -p "${angle_dir}"
|
||||
|
||||
package_angle_variant() {
|
||||
variant="$1"
|
||||
commit="$2"
|
||||
egl_sha="$3"
|
||||
gles_sha="$4"
|
||||
source_dir="${RUNNER_TEMP}/mobilegl-angle-${variant}"
|
||||
base="https://raw.githubusercontent.com/FCL-Team/FoldCraftLauncher/${commit}/FCLauncher/src/main/jniLibs/x86_64"
|
||||
mkdir -p "${source_dir}"
|
||||
curl -L --fail --retry 3 -o "${source_dir}/libEGL_angle.so" "${base}/libEGL_angle.so"
|
||||
curl -L --fail --retry 3 -o "${source_dir}/libGLESv2_angle.so" "${base}/libGLESv2_angle.so"
|
||||
echo "${egl_sha} ${source_dir}/libEGL_angle.so" | sha256sum -c -
|
||||
echo "${gles_sha} ${source_dir}/libGLESv2_angle.so" | sha256sum -c -
|
||||
for library in libEGL_angle libGLESv2_angle; do
|
||||
filename="${library}_${variant}.so"
|
||||
cp "${source_dir}/${library}.so" "${angle_dir}/${filename}"
|
||||
done
|
||||
}
|
||||
|
||||
package_angle_variant \
|
||||
ec889e6ea831 \
|
||||
f2a3d510dffd8f6540a52e1a7d0c5787d151075b \
|
||||
c41828768d089899fa058ec0bee711a91be88347f29bdb935223da6be1149c40 \
|
||||
e4f820d99f94365c66df868c7740fef142fe5c0cd7c941790a9e30638857ca4d
|
||||
package_angle_variant \
|
||||
90a62123d794 \
|
||||
bdcc96ac11c79001018ae4375eb73cb54a9f682f \
|
||||
d0f4298ccc770cc801fc52e21733521646161e8a4adb3bd0052d9a1b57ee0ca8 \
|
||||
66fdc867e552192d553d59095ea2e3cef4829de65c356f1fd826027b1905972e
|
||||
|
||||
- name: Build retrace APK
|
||||
run: gradle --no-daemon -p android-plugin :app:assembleTraceRelease -Pmobilegl.apkSuffix="${GITHUB_SHA}" -Pmobilegl.abis=all -Pmobilegl.debuggableRelease=true -Pmobilegl.logLevel=MOBILEGL_LOG_LEVEL_INFO --parallel --max-workers "$(nproc)"
|
||||
env:
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
|
||||
- name: Show ccache stats
|
||||
if: always()
|
||||
run: ccache --show-stats
|
||||
|
||||
- name: Verify APK metadata and packaging
|
||||
run: |
|
||||
AAPT2="$(find "$ANDROID_HOME/build-tools" -name aapt2 -type f | sort -V | tail -n 1)"
|
||||
plugin_apk="android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${GITHUB_SHA}.apk"
|
||||
trace_apk="android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk"
|
||||
test -f "${plugin_apk}"
|
||||
test -f "${trace_apk}"
|
||||
bash .github/scripts/validate-plugin-apks.sh "$AAPT2" "$plugin_apk" "$trace_apk"
|
||||
|
||||
- name: Verify signed APKs
|
||||
run: |
|
||||
APKSIGNER="$(find "$ANDROID_HOME/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
|
||||
mapfile -t APKS < <(printf '%s\n' \
|
||||
"android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${GITHUB_SHA}.apk" \
|
||||
"android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk")
|
||||
for APK in "${APKS[@]}"; do
|
||||
if [[ ! -f "$APK" ]]; then
|
||||
echo "::error::Expected release APK was not produced: $APK"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
for APK in "${APKS[@]}"; do
|
||||
if [[ "$APK" == *-unsigned.apk ]]; then
|
||||
echo "::error::Unsigned release APK produced: $APK"
|
||||
exit 1
|
||||
fi
|
||||
"$APKSIGNER" verify --verbose "$APK"
|
||||
done
|
||||
|
||||
- name: Upload plugin APK
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: MobileGL-plugin-${{ env.date_today }}-${{ github.sha }}
|
||||
path: android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${{ github.sha }}.apk
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload retrace APK
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: MobileGL-retrace-apk-${{ env.date_today }}-${{ github.sha }}
|
||||
path: android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${{ github.sha }}.apk
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
|
||||
trace-cases:
|
||||
name: trace case matrix
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
outputs:
|
||||
android: ${{ steps.trace-cases.outputs.android }}
|
||||
names: ${{ steps.trace-cases.outputs.names }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load trace cases
|
||||
id: trace-cases
|
||||
run: |
|
||||
echo "android=$(python3 tools/trace_replay/trace_cases.py --ci --format github-apk)" >> "$GITHUB_OUTPUT"
|
||||
echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
trace-fixtures:
|
||||
name: trace fixture (${{ matrix.case }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: trace-cases
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch trace fixture
|
||||
run: bash .github/scripts/fetch-trace-fixture-lfs.sh '${{ matrix.case }}'
|
||||
|
||||
- name: Stage trace fixture
|
||||
run: |
|
||||
safe_case="$(printf '%s' '${{ matrix.case }}' | sed 's/[^A-Za-z0-9._-]/_/g')"
|
||||
stage_dir="trace-fixtures/${safe_case}"
|
||||
mkdir -p "${stage_dir}"
|
||||
python3 tools/trace_replay/trace_cases.py --format fixture-files --case '${{ matrix.case }}' |
|
||||
while IFS= read -r file; do
|
||||
cp "${file}" "${stage_dir}/"
|
||||
done
|
||||
|
||||
- name: Upload trace fixture
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: MobileGL-trace-fixture-${{ matrix.case }}
|
||||
path: trace-fixtures/**
|
||||
if-no-files-found: error
|
||||
|
||||
android-avd:
|
||||
name: android avd image
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
AVD_NAME: mobilegl-ci
|
||||
ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd
|
||||
ANDROID_HOME: ${{ github.workspace }}/.android/sdk
|
||||
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
accept-android-sdk-licenses: false
|
||||
|
||||
- name: Accept Android SDK licenses
|
||||
run: yes | sdkmanager --licenses >/dev/null
|
||||
|
||||
- name: Restore Android AVD cache
|
||||
id: android-avd-cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
${{ env.ANDROID_AVD_HOME }}
|
||||
${{ env.ANDROID_SDK_ROOT }}/emulator
|
||||
${{ env.ANDROID_SDK_ROOT }}/platform-tools
|
||||
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
|
||||
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64
|
||||
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
|
||||
|
||||
- name: Create AVD
|
||||
if: steps.android-avd-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sh android-plugin/run-avd-ci.sh create \
|
||||
--api-level 35 \
|
||||
--target google_apis \
|
||||
--arch x86_64 \
|
||||
--profile pixel_6 \
|
||||
--avd-name "${AVD_NAME}"
|
||||
|
||||
retrace:
|
||||
name: retrace (${{ matrix.backend.name }}, ${{ matrix.case.name }})
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- android-avd
|
||||
- trace-cases
|
||||
- trace-fixtures
|
||||
if: ${{ always() && needs.build.result == 'success' && needs.android-avd.result == 'success' && needs.trace-cases.result == 'success' }}
|
||||
timeout-minutes: 75
|
||||
env:
|
||||
AVD_NAME: mobilegl-ci
|
||||
ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd
|
||||
ANDROID_HOME: ${{ github.workspace }}/.android/sdk
|
||||
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
backend:
|
||||
- name: DirectGLES
|
||||
gpu: software
|
||||
- name: DirectVulkan
|
||||
gpu: lavapipe
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.android) }}
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 8
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download trace fixture
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: MobileGL-trace-fixture-${{ matrix.case.name }}
|
||||
path: trace-fixture-download
|
||||
|
||||
- name: Install trace fixture
|
||||
run: |
|
||||
mkdir -p tools/trace_replay/fixtures
|
||||
find trace-fixture-download -type f -exec cp {} tools/trace_replay/fixtures/ \;
|
||||
|
||||
- name: Set artifact metadata
|
||||
run: |
|
||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
echo "EMULATOR_LOG=${RUNNER_TEMP}/mobilegl-emulator.log" >> "$GITHUB_ENV"
|
||||
echo "EMULATOR_PID_FILE=${RUNNER_TEMP}/mobilegl-emulator.pid" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
accept-android-sdk-licenses: false
|
||||
|
||||
- name: Accept Android SDK licenses
|
||||
run: yes | sdkmanager --licenses >/dev/null
|
||||
|
||||
- name: Restore Android AVD cache
|
||||
id: android-avd-cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
${{ env.ANDROID_AVD_HOME }}
|
||||
${{ env.ANDROID_SDK_ROOT }}/emulator
|
||||
${{ env.ANDROID_SDK_ROOT }}/platform-tools
|
||||
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
|
||||
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64
|
||||
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
|
||||
|
||||
- name: Download retrace APK
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: MobileGL-plugin-trace-release-${{ github.sha }}.apk
|
||||
path: android-retrace-apks
|
||||
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Create AVD
|
||||
if: steps.android-avd-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sh android-plugin/run-avd-ci.sh create \
|
||||
--api-level 35 \
|
||||
--target google_apis \
|
||||
--arch x86_64 \
|
||||
--profile pixel_6 \
|
||||
--avd-name "${AVD_NAME}"
|
||||
|
||||
- name: Launch Emulator
|
||||
run: |
|
||||
sh android-plugin/run-avd-ci.sh start \
|
||||
--avd-name "${AVD_NAME}" \
|
||||
--gpu "${{ matrix.backend.gpu }}" \
|
||||
--emulator-log "${EMULATOR_LOG}" \
|
||||
--pid-file "${EMULATOR_PID_FILE}" \
|
||||
--boot-timeout 300
|
||||
|
||||
- name: Retrace and validate
|
||||
env:
|
||||
MOBILEGL_USE_ANGLE: ${{ matrix.backend.name == 'DirectGLES' && '1' || '0' }}
|
||||
MOBILEGL_TRACE_ANGLE_VARIANT: ${{ matrix.case.name == 'minecraft-1.21.4-fabric-iris-bliss-in-world' && '90a62123d794' || 'ec889e6ea831' }}
|
||||
MOBILEGL_MAGMA_R11G11B10F_FALLBACK: ${{ matrix.backend.name == 'DirectVulkan' && '1' || '0' }}
|
||||
run: |
|
||||
apk_file="android-retrace-apks/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk"
|
||||
test -f "${apk_file}"
|
||||
extra_retrace_args=()
|
||||
# Bliss needs the newer signed ANGLE variant plus sampler mipmap
|
||||
# min-filter downgrading on ANGLE llvmpipe.
|
||||
if [ "${{ matrix.backend.name }}" = "DirectGLES" ] && [ "${{ matrix.case.name }}" = "minecraft-1.21.4-fabric-iris-bliss-in-world" ]; then
|
||||
extra_retrace_args+=(--avoid-angle-llvmpipe-sampler-mipmap-min-filter)
|
||||
fi
|
||||
if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then
|
||||
extra_retrace_args+=(--coherent-as-flush)
|
||||
fi
|
||||
|
||||
run_retrace() {
|
||||
timeout "$(( ${{ matrix.case.timeout_seconds }} + 300 ))" sh android-plugin/trace-replay-ci.sh \
|
||||
--apk-file "${apk_file}" \
|
||||
--package top.mobilegl.plugin.trace \
|
||||
--backend "${{ matrix.backend.name }}" \
|
||||
--result-root android-retrace-result \
|
||||
--fixture-root android-retrace-fixture \
|
||||
--case "${{ matrix.case.name }}" \
|
||||
--trace-archive "${{ matrix.case.trace_archive }}" \
|
||||
--trace-file "${{ matrix.case.trace_file }}" \
|
||||
--golden "${{ matrix.case.golden }}" \
|
||||
--alternate-golden "${{ matrix.case.alternate_golden || '' }}" \
|
||||
--target-call "${{ matrix.case.target_call }}" \
|
||||
--width "${{ matrix.case.width }}" \
|
||||
--height "${{ matrix.case.height }}" \
|
||||
--ssim-threshold "${{ matrix.case.ssim_threshold || '0.99' }}" \
|
||||
--crop-x "${{ matrix.case.crop_x }}" \
|
||||
--crop-y "${{ matrix.case.crop_y }}" \
|
||||
--crop-width "${{ matrix.case.crop_width }}" \
|
||||
--crop-height "${{ matrix.case.crop_height }}" \
|
||||
--timeout-seconds "${{ matrix.case.timeout_seconds }}" \
|
||||
"${extra_retrace_args[@]}"
|
||||
}
|
||||
|
||||
retrace_status=0
|
||||
run_retrace || retrace_status=$?
|
||||
if [ "${retrace_status}" -eq 75 ]; then
|
||||
echo "::warning::Android emulator infrastructure failed; restarting it and retrying this retrace once."
|
||||
sh android-plugin/run-avd-ci.sh stop \
|
||||
--avd-name "${AVD_NAME}" \
|
||||
--emulator-log "${EMULATOR_LOG}" \
|
||||
--pid-file "${EMULATOR_PID_FILE}"
|
||||
adb kill-server || true
|
||||
sleep 2
|
||||
sh android-plugin/run-avd-ci.sh start \
|
||||
--avd-name "${AVD_NAME}" \
|
||||
--gpu "${{ matrix.backend.gpu }}" \
|
||||
--emulator-log "${EMULATOR_LOG}" \
|
||||
--pid-file "${EMULATOR_PID_FILE}" \
|
||||
--boot-timeout 300
|
||||
run_retrace
|
||||
elif [ "${retrace_status}" -ne 0 ]; then
|
||||
exit "${retrace_status}"
|
||||
fi
|
||||
|
||||
- name: Collect retrace summary inputs
|
||||
if: always()
|
||||
run: |
|
||||
safe_case="$(printf '%s' '${{ matrix.case.name }}' | sed 's/[^A-Za-z0-9._-]/_/g')"
|
||||
result_dir="android-retrace-result/${safe_case}-${{ matrix.backend.name }}"
|
||||
mkdir -p "${result_dir}"
|
||||
if [ -s "${{ matrix.case.golden }}" ]; then
|
||||
cp "${{ matrix.case.golden }}" "${result_dir}/${safe_case}-${{ matrix.backend.name }}-golden.png"
|
||||
fi
|
||||
if [ -n "${{ matrix.case.alternate_golden || '' }}" ] && [ -s "${{ matrix.case.alternate_golden || '' }}" ]; then
|
||||
cp "${{ matrix.case.alternate_golden || '' }}" "${result_dir}/${safe_case}-${{ matrix.backend.name }}-alternate-golden.png"
|
||||
fi
|
||||
|
||||
- name: Collect emulator diagnostics
|
||||
if: always()
|
||||
run: |
|
||||
mkdir -p android-retrace-result/diagnostics
|
||||
adb devices -l > android-retrace-result/diagnostics/adb-devices.txt || true
|
||||
timeout 30 adb logcat -d -t 1000 > android-retrace-result/diagnostics/logcat.txt || true
|
||||
if [ -f "${EMULATOR_LOG}" ]; then
|
||||
cp "${EMULATOR_LOG}" android-retrace-result/diagnostics/emulator.log
|
||||
fi
|
||||
|
||||
- name: Stop Emulator
|
||||
if: always()
|
||||
run: |
|
||||
sh android-plugin/run-avd-ci.sh stop \
|
||||
--avd-name "${AVD_NAME}" \
|
||||
--emulator-log "${EMULATOR_LOG}" \
|
||||
--pid-file "${EMULATOR_PID_FILE}"
|
||||
|
||||
- name: Upload Android retrace result
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: MobileGL-android-retrace-result-${{ env.date_today }}-${{ github.sha }}-${{ matrix.backend.name }}-${{ matrix.case.name }}
|
||||
path: android-retrace-result/**
|
||||
if-no-files-found: warn
|
||||
|
||||
retrace-summary:
|
||||
name: retrace summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: retrace
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set artifact metadata
|
||||
run: |
|
||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Download Android retrace results
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: MobileGL-android-retrace-result-*
|
||||
path: retrace-artifacts
|
||||
|
||||
- name: Render retrace summary
|
||||
run: |
|
||||
node tools/trace_replay/render_retrace_summary.mjs \
|
||||
--input retrace-artifacts \
|
||||
--output-dir android-retrace-summary \
|
||||
--title "MobileGL Android retrace overview" \
|
||||
--group-label "Android Emulator" \
|
||||
--html mobilegl-android-retrace-overview.html
|
||||
|
||||
- name: Upload Android retrace summary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: android-retrace-summary/mobilegl-android-retrace-overview.html
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
|
||||
remove-artifact-clutter:
|
||||
name: remove artifact clutter
|
||||
runs-on: ubuntu-latest
|
||||
needs: retrace-summary
|
||||
if: always()
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Delete intermediate Android retrace artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
declare -A failed_cases=()
|
||||
while IFS= read -r job_name; do
|
||||
case_name="${job_name#retrace (*, }"
|
||||
case_name="${case_name%)}"
|
||||
failed_cases["${case_name}"]=1
|
||||
done < <(
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100" \
|
||||
--jq '.jobs[] | select(.name | startswith("retrace (")) | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name'
|
||||
)
|
||||
|
||||
if ((${#failed_cases[@]})); then
|
||||
echo "Retaining fixtures for failed retrace case(s):"
|
||||
printf ' %s\n' "${!failed_cases[@]}"
|
||||
else
|
||||
echo "All retrace jobs succeeded; no fixtures need to be retained."
|
||||
fi
|
||||
|
||||
deleted=0
|
||||
retained=0
|
||||
while IFS=$'\t' read -r artifact_id artifact_name; do
|
||||
if [[ "${artifact_name}" == MobileGL-trace-fixture-* ]]; then
|
||||
case_name="${artifact_name#MobileGL-trace-fixture-}"
|
||||
if [[ -v "failed_cases[${case_name}]" ]]; then
|
||||
echo "Retaining ${artifact_name} (${artifact_id}) for failed retrace."
|
||||
((retained += 1))
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Deleting ${artifact_name} (${artifact_id})"
|
||||
gh api --method DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}"
|
||||
((deleted += 1))
|
||||
done < <(
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
|
||||
--jq '.artifacts[] | select(.name | startswith("MobileGL-trace-fixture-") or startswith("MobileGL-android-retrace-result-") or startswith("trace-fixture-") or startswith("retrace-result-")) | [.id, .name] | @tsv'
|
||||
)
|
||||
|
||||
echo "Deleted ${deleted} intermediate Android artifact(s); retained ${retained} failed-retrace fixture(s)."
|
||||
@@ -1,64 +0,0 @@
|
||||
name: Benchmark
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- Feat/Backend-Direct-GLES
|
||||
- Feat/Backend-Direct-Vulkan
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# BENCH_ROOT: ${{github.workspace}}/MobileGL/MG_Benchmark
|
||||
BENCH_ROOT: ${{github.workspace}}
|
||||
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
with:
|
||||
swap-size-gb: 32
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Prepare Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
||||
with:
|
||||
vulkan-query-version: 1.4.304.1
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader
|
||||
vulkan-use-cache: true
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: ${{env.BENCH_ROOT}}/3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
|
||||
- name: Install clang-20
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev
|
||||
|
||||
- name: Show installed toolchain
|
||||
run: |
|
||||
clang-20 --version
|
||||
clang++-20 --version
|
||||
ld.lld-20 --version || ld.lld --version || true
|
||||
dpkg -l 'libc++*' || true
|
||||
|
||||
- name: Configure CMake
|
||||
working-directory: ${{env.BENCH_ROOT}}
|
||||
run: cmake -S . -B build-bench -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON -DBENCHMARK_ENABLE_TESTING=OFF -DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{env.BENCH_ROOT}}/build-bench
|
||||
run: cmake --build .
|
||||
|
||||
- name: Benchmark
|
||||
working-directory: ${{env.BENCH_ROOT}}/build-bench/MobileGL/MG_Benchmark
|
||||
run: ctest -V -C Release
|
||||
+509
-23
@@ -6,27 +6,41 @@ on:
|
||||
- dev
|
||||
- Feat/Backend-Direct-GLES
|
||||
- Feat/Backend-Direct-Vulkan
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# TEST_ROOT: ${{github.workspace}}/MobileGL/MG_Test
|
||||
TEST_ROOT: ${{github.workspace}}
|
||||
BUILD_DIR: build-linux
|
||||
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||
CCACHE_COMPRESS: "true"
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 4G
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 32
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ runner.os }}-test-${{ github.job }}-ccache-${{ github.ref_name }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-test-${{ github.job }}-ccache-${{ github.ref_name }}-
|
||||
${{ runner.os }}-test-${{ github.job }}-ccache-
|
||||
|
||||
- name: Prepare Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
||||
@@ -36,39 +50,511 @@ jobs:
|
||||
vulkan-use-cache: true
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: ${{env.TEST_ROOT}}/3rdparty/glslang
|
||||
working-directory: 3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
|
||||
- name: Install clang-20
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev
|
||||
sudo apt-get install -y ccache clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers ninja-build
|
||||
|
||||
- name: Show installed toolchain
|
||||
run: |
|
||||
ccache --version
|
||||
clang-20 --version
|
||||
clang++-20 --version
|
||||
ld.lld-20 --version || ld.lld --version || true
|
||||
dpkg -l 'libc++*' || true
|
||||
|
||||
dpkg -l 'libc++*' 'libegl*' 'libgles*' 'mesa*' 'vulkan*' || true
|
||||
|
||||
- name: Configure CMake
|
||||
working-directory: ${{env.TEST_ROOT}}
|
||||
run: |
|
||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" == "true" ]; then
|
||||
cmake -S . -B build-test -G Ninja -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCMAKE_BUILD_TYPE=Debug -DMOBILEGL_BUILD_TEST=ON -DMOBILEGL_BUILD_BENCHMARK=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then
|
||||
BUILD_TYPE=Debug
|
||||
else
|
||||
cmake -S . -B build-test -G Ninja -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCMAKE_BUILD_TYPE=Release -DMOBILEGL_BUILD_TEST=ON -DMOBILEGL_BUILD_BENCHMARK=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
BUILD_TYPE=Release
|
||||
fi
|
||||
|
||||
|
||||
cmake -S . -B "${BUILD_DIR}" -G Ninja \
|
||||
-DCMAKE_C_COMPILER=clang-20 \
|
||||
-DCMAKE_CXX_COMPILER=clang++-20 \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO \
|
||||
-DMOBILEGL_BUILD_TEST=ON \
|
||||
-DMOBILEGL_BUILD_BENCHMARK=ON \
|
||||
-DMOBILEGL_BUILD_TRACE_REPLAY=OFF \
|
||||
-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON \
|
||||
-DBENCHMARK_ENABLE_TESTING=OFF \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{env.TEST_ROOT}}/build-test
|
||||
run: cmake --build .
|
||||
run: cmake --build "${BUILD_DIR}" --parallel "$(nproc)"
|
||||
|
||||
- name: Show ccache stats
|
||||
if: always()
|
||||
run: ccache --show-stats
|
||||
|
||||
- name: Package Linux runtime
|
||||
run: |
|
||||
mkdir -p ci-artifacts
|
||||
mapfile -t SHARED_LIBS < <(find "${BUILD_DIR}" -type f \( -name '*.so' -o -name '*.so.*' \) -print | sort)
|
||||
tar \
|
||||
--exclude='*/CMakeFiles' \
|
||||
--exclude='*.o' \
|
||||
--exclude='*.a' \
|
||||
--exclude='*.ninja*' \
|
||||
--exclude='build.ninja' \
|
||||
--exclude='cmake_install.cmake' \
|
||||
-czf ci-artifacts/mobilegl-linux-runtime.tgz \
|
||||
"${BUILD_DIR}/CTestTestfile.cmake" \
|
||||
"${BUILD_DIR}/MobileGL/MG_Test" \
|
||||
"${BUILD_DIR}/MobileGL/MG_Benchmark" \
|
||||
"${SHARED_LIBS[@]}"
|
||||
|
||||
- name: Upload Linux runtime
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: ci-artifacts/mobilegl-linux-runtime.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Install runtime dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libvulkan1 libegl1 libgles2 libgl1-mesa-dri mesa-vulkan-drivers
|
||||
|
||||
- name: Download Linux runtime
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: .
|
||||
|
||||
- name: Unpack Linux runtime
|
||||
run: tar -xzf mobilegl-linux-runtime.tgz
|
||||
|
||||
- name: Normalize CTest command paths
|
||||
run: |
|
||||
python - <<'PY'
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
for path in Path('build-linux').rglob('CTestTestfile.cmake'):
|
||||
text = path.read_text()
|
||||
text = re.sub(r'"[^"]*/cmake-[^"]*/bin/cmake"', '"cmake"', text)
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{env.TEST_ROOT}}/build-test/MobileGL/MG_Test
|
||||
working-directory: build-linux
|
||||
run: |
|
||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" == "true" ]; then
|
||||
ctest -V
|
||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then
|
||||
ctest -V -L unit --no-tests=error
|
||||
else
|
||||
ctest
|
||||
ctest --output-on-failure -L unit --no-tests=error
|
||||
fi
|
||||
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Install runtime dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libvulkan1 libegl1 libgles2 libgl1-mesa-dri mesa-vulkan-drivers
|
||||
|
||||
- name: Download Linux runtime
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: .
|
||||
|
||||
- name: Unpack Linux runtime
|
||||
run: tar -xzf mobilegl-linux-runtime.tgz
|
||||
|
||||
- name: Normalize CTest command paths
|
||||
run: |
|
||||
python - <<'PY'
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
for path in Path('build-linux').rglob('CTestTestfile.cmake'):
|
||||
text = path.read_text()
|
||||
text = re.sub(r'"[^"]*/cmake-[^"]*/bin/cmake"', '"cmake"', text)
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
- name: Benchmark
|
||||
working-directory: build-linux
|
||||
run: ctest -V -C Release -L benchmark --no-tests=error
|
||||
|
||||
build-retrace:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-linux
|
||||
- test
|
||||
- benchmark
|
||||
env:
|
||||
BUILD_DIR: build-retrace
|
||||
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||
CCACHE_COMPRESS: "true"
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 4G
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
MOBILEGL_LIBRARY: ${{ github.workspace }}/build-linux/libMobileGL.so
|
||||
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 32
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ runner.os }}-test-${{ github.job }}-ccache-${{ github.ref_name }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-test-${{ github.job }}-ccache-${{ github.ref_name }}-
|
||||
${{ runner.os }}-test-${{ github.job }}-ccache-
|
||||
|
||||
- name: Prepare Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
||||
with:
|
||||
vulkan-query-version: 1.4.304.1
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader
|
||||
vulkan-use-cache: true
|
||||
|
||||
- name: Update glslang external sources
|
||||
working-directory: 3rdparty/glslang
|
||||
run: python update_glslang_sources.py
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ccache clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers ninja-build
|
||||
|
||||
- name: Show installed toolchain
|
||||
run: |
|
||||
ccache --version
|
||||
clang-20 --version
|
||||
clang++-20 --version
|
||||
ld.lld-20 --version || ld.lld --version || true
|
||||
dpkg -l 'libc++*' 'libegl*' 'libgles*' 'mesa*' 'vulkan*' || true
|
||||
|
||||
- name: Download Linux runtime
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: .
|
||||
|
||||
- name: Unpack Linux runtime
|
||||
run: |
|
||||
tar -xzf mobilegl-linux-runtime.tgz
|
||||
test -f "${MOBILEGL_LIBRARY}"
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then
|
||||
BUILD_TYPE=Debug
|
||||
else
|
||||
BUILD_TYPE=Release
|
||||
fi
|
||||
|
||||
cmake -S . -B "${BUILD_DIR}" -G Ninja \
|
||||
-DCMAKE_C_COMPILER=clang-20 \
|
||||
-DCMAKE_CXX_COMPILER=clang++-20 \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO \
|
||||
-DMOBILEGL_BUILD_TEST=OFF \
|
||||
-DMOBILEGL_BUILD_BENCHMARK=OFF \
|
||||
-DMOBILEGL_BUILD_TRACE_REPLAY=ON \
|
||||
-DMOBILEGL_TRACE_REPLAY_MOBILEGL_LIBRARY="${MOBILEGL_LIBRARY}" \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
|
||||
- name: Build trace replay
|
||||
run: cmake --build "${BUILD_DIR}" --target mobilegl_trace_replay --parallel "$(nproc)"
|
||||
|
||||
- name: Show ccache stats
|
||||
if: always()
|
||||
run: ccache --show-stats
|
||||
|
||||
- name: Normalize CTest command paths
|
||||
run: |
|
||||
python - <<'PY'
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
for path in Path('build-retrace').rglob('CTestTestfile.cmake'):
|
||||
text = path.read_text()
|
||||
text = re.sub(r'"[^"]*/cmake-[^"]*/bin/cmake"', '"cmake"', text)
|
||||
path.write_text(text)
|
||||
PY
|
||||
|
||||
- name: Package trace replay
|
||||
run: |
|
||||
mkdir -p ci-artifacts
|
||||
tar -czf ci-artifacts/mobilegl-trace-replay.tgz \
|
||||
build-retrace/tools/trace_replay/mobilegl_trace_replay \
|
||||
build-retrace/tools/trace_replay/CTestTestfile.cmake
|
||||
|
||||
- name: Upload trace replay
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: mobilegl-trace-replay
|
||||
path: ci-artifacts/mobilegl-trace-replay.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
trace-cases:
|
||||
name: trace case matrix
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
- benchmark
|
||||
outputs:
|
||||
names: ${{ steps.trace-cases.outputs.names }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load trace cases
|
||||
id: trace-cases
|
||||
run: echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
trace-fixtures:
|
||||
name: trace fixture (${{ matrix.case }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: trace-cases
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch trace fixture
|
||||
run: bash .github/scripts/fetch-trace-fixture-lfs.sh '${{ matrix.case }}'
|
||||
|
||||
- name: Stage trace fixture
|
||||
run: |
|
||||
safe_case="$(printf '%s' '${{ matrix.case }}' | sed 's/[^A-Za-z0-9._-]/_/g')"
|
||||
stage_dir="trace-fixtures/${safe_case}"
|
||||
mkdir -p "${stage_dir}"
|
||||
python3 tools/trace_replay/trace_cases.py --format fixture-files --case '${{ matrix.case }}' |
|
||||
while IFS= read -r file; do
|
||||
cp "${file}" "${stage_dir}/"
|
||||
done
|
||||
|
||||
- name: Upload trace fixture
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: trace-fixture-${{ matrix.case }}
|
||||
path: trace-fixtures/**
|
||||
if-no-files-found: error
|
||||
|
||||
retrace:
|
||||
name: retrace (${{ matrix.backend }}, ${{ matrix.case }})
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-linux
|
||||
- build-retrace
|
||||
- trace-cases
|
||||
- trace-fixtures
|
||||
if: ${{ always() && needs.build-linux.result == 'success' && needs.build-retrace.result == 'success' && needs.trace-cases.result == 'success' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
backend:
|
||||
- DirectGLES
|
||||
- DirectVulkan
|
||||
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
||||
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 16
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download trace fixture
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: trace-fixture-${{ matrix.case }}
|
||||
path: trace-fixture-download
|
||||
|
||||
- name: Install trace fixture
|
||||
run: |
|
||||
mkdir -p tools/trace_replay/fixtures
|
||||
find trace-fixture-download -type f -exec cp {} tools/trace_replay/fixtures/ \;
|
||||
|
||||
- name: Get CMake
|
||||
uses: lukka/get-cmake@v4.3.3
|
||||
|
||||
- name: Install runtime dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libvulkan1 libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers
|
||||
test -e /usr/lib/x86_64-linux-gnu/libEGL.so
|
||||
test -e /usr/lib/x86_64-linux-gnu/libGLESv2.so
|
||||
|
||||
- name: Download Linux runtime
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-linux-runtime
|
||||
path: .
|
||||
|
||||
- name: Download trace replay
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mobilegl-trace-replay
|
||||
path: .
|
||||
|
||||
- name: Unpack retrace runtime
|
||||
run: |
|
||||
tar -xzf mobilegl-linux-runtime.tgz
|
||||
tar -xzf mobilegl-trace-replay.tgz
|
||||
test -f build-linux/libMobileGL.so
|
||||
test -f build-retrace/tools/trace_replay/mobilegl_trace_replay
|
||||
|
||||
- name: Retrace and validate
|
||||
working-directory: build-retrace/tools/trace_replay
|
||||
run: |
|
||||
if [ '${{ matrix.backend }}' = 'DirectVulkan' ]; then
|
||||
export MOBILEGL_MAGMA_R11G11B10F_FALLBACK=1
|
||||
fi
|
||||
ctest -V --no-tests=error -R '^MobileGLTraceReplay\.${{ matrix.case }}\.${{ matrix.backend }}$'
|
||||
|
||||
- name: Upload actual image
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: retrace-result-${{ matrix.backend }}-${{ matrix.case }}
|
||||
path: |
|
||||
build-retrace/tools/trace_replay/${{ matrix.case }}/actual-images/**
|
||||
build-retrace/tools/trace_replay/${{ matrix.case }}/${{ matrix.backend }}/output/**
|
||||
if-no-files-found: warn
|
||||
|
||||
retrace-summary:
|
||||
name: retrace summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: retrace
|
||||
if: ${{ always() && needs.retrace.result != 'skipped' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set artifact metadata
|
||||
run: |
|
||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Download retrace results
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: retrace-result-*
|
||||
path: retrace-artifacts
|
||||
|
||||
- name: Render retrace summary
|
||||
run: |
|
||||
node tools/trace_replay/render_retrace_summary.mjs \
|
||||
--input retrace-artifacts \
|
||||
--output-dir retrace-summary \
|
||||
--title "MobileGL Linux retrace overview" \
|
||||
--group-label "Linux" \
|
||||
--html mobilegl-linux-retrace-overview.html
|
||||
|
||||
- name: Upload retrace summary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: retrace-summary/mobilegl-linux-retrace-overview.html
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
|
||||
remove-artifact-clutter:
|
||||
name: remove artifact clutter
|
||||
runs-on: ubuntu-latest
|
||||
needs: retrace-summary
|
||||
if: always()
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Delete intermediate Linux retrace artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
declare -A failed_cases=()
|
||||
while IFS= read -r job_name; do
|
||||
case_name="${job_name#retrace (*, }"
|
||||
case_name="${case_name%)}"
|
||||
failed_cases["${case_name}"]=1
|
||||
done < <(
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100" \
|
||||
--jq '.jobs[] | select(.name | startswith("retrace (")) | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name'
|
||||
)
|
||||
|
||||
if ((${#failed_cases[@]})); then
|
||||
echo "Retaining fixtures for failed retrace case(s):"
|
||||
printf ' %s\n' "${!failed_cases[@]}"
|
||||
else
|
||||
echo "All retrace jobs succeeded; no fixtures need to be retained."
|
||||
fi
|
||||
|
||||
deleted=0
|
||||
retained=0
|
||||
while IFS=$'\t' read -r artifact_id artifact_name; do
|
||||
if [[ "${artifact_name}" == trace-fixture-* ]]; then
|
||||
case_name="${artifact_name#trace-fixture-}"
|
||||
if [[ -v "failed_cases[${case_name}]" ]]; then
|
||||
echo "Retaining ${artifact_name} (${artifact_id}) for failed retrace."
|
||||
((retained += 1))
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Deleting ${artifact_name} (${artifact_id})"
|
||||
gh api --method DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}"
|
||||
((deleted += 1))
|
||||
done < <(
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
|
||||
--jq '.artifacts[] | select(.name | startswith("trace-fixture-") or startswith("retrace-result-")) | [.id, .name] | @tsv'
|
||||
)
|
||||
|
||||
echo "Deleted ${deleted} intermediate Linux artifact(s); retained ${retained} failed-retrace fixture(s)."
|
||||
|
||||
+7
-1
@@ -18,4 +18,10 @@ MobileGL/MG_Test/build
|
||||
/cmake-build*
|
||||
.idea
|
||||
MobileGL/MG*/build*
|
||||
MobileGL/MG*/cmake-build*
|
||||
MobileGL/MG*/cmake-build*
|
||||
/android-plugin/.gradle
|
||||
/android-plugin/build
|
||||
/android-plugin/app/build
|
||||
/android-plugin/app/src/trace/jniLibs
|
||||
/android-plugin/local.properties
|
||||
tools/trace_replay/work/
|
||||
|
||||
+12
@@ -19,3 +19,15 @@
|
||||
[submodule "3rdparty/VulkanMemoryAllocator"]
|
||||
path = 3rdparty/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "3rdparty/Vulkan-Utility-Libraries"]
|
||||
path = 3rdparty/Vulkan-Utility-Libraries
|
||||
url = https://github.com/KhronosGroup/Vulkan-Utility-Libraries.git
|
||||
[submodule "3rdparty/Vulkan-Headers"]
|
||||
path = 3rdparty/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "3rdparty/SPIRV-Reflect"]
|
||||
path = 3rdparty/SPIRV-Reflect
|
||||
url = https://github.com/KhronosGroup/SPIRV-Reflect.git
|
||||
[submodule "3rdparty/apitrace"]
|
||||
path = 3rdparty/apitrace
|
||||
url = https://github.com/MobileGL-Dev/apitrace.git
|
||||
|
||||
+1
Submodule 3rdparty/SPIRV-Reflect added at 10b4f09a24
+1
Submodule 3rdparty/Vulkan-Headers added at ad9ce1235e
+1
Submodule 3rdparty/Vulkan-Utility-Libraries added at 738ec97a3f
+1
Submodule 3rdparty/apitrace added at 10935bb5e4
+122
-4
@@ -6,6 +6,11 @@ option(MOBILEGL_BUILD_TEST "Build MobileGL tests"
|
||||
option(MOBILEGL_BUILD_BENCHMARK "Build MobileGL benchmarks" ON )
|
||||
option(MOBILEGL_FORCE_RELEASE_OPT "Enable Release optimization flags in Debug build" ON )
|
||||
option(MOBILEGL_ENABLE_TRACY "Enable tracy for profiling" OFF)
|
||||
option(MOBILEGL_BUILD_TRACE_REPLAY "Build desktop apitrace replay runner" OFF)
|
||||
option(MOBILEGL_TRACE_ANGLE_VARIANTS "Enable signed trace-APK ANGLE variant loading" OFF)
|
||||
option(MOBILEGL_IOS "Build MobileGL for iOS instead of macOS when APPLE is set" OFF)
|
||||
set(MOBILEGL_LOG_ACTIVE_LEVEL "MOBILEGL_LOG_LEVEL_INFO" CACHE STRING "MobileGL active log level macro")
|
||||
set(MOBILEGL_VULKAN_LIBRARY "" CACHE FILEPATH "Vulkan loader/MoltenVK library to link for iOS builds")
|
||||
|
||||
if (ANDROID)
|
||||
set(MOBILEGL_BUILD_TEST OFF CACHE BOOL "Build MobileGL tests" FORCE)
|
||||
@@ -104,10 +109,20 @@ set(SPIRV_CROSS_ENABLE_CPP OFF CACHE BOOL "Disable C++ API target" FORCE)
|
||||
set(SPIRV_CROSS_CLI OFF CACHE BOOL "Disable CLI binary" FORCE)
|
||||
set(SPIRV_CROSS_STATIC ON CACHE BOOL "Prefer static libs" FORCE)
|
||||
|
||||
set(SPIRV_REFLECT_EXECUTABLE OFF CACHE BOOL "Build spirv-reflect executable" FORCE)
|
||||
set(SPIRV_REFLECT_STATIC_LIB ON CACHE BOOL "Build a SPIRV-Reflect static library" FORCE)
|
||||
set(SPIRV_REFLECT_BUILD_TESTS OFF CACHE BOOL "Build the SPIRV-Reflect test suite" FORCE)
|
||||
set(SPIRV_REFLECT_ENABLE_ASSERTS OFF CACHE BOOL "Enable asserts for debugging" FORCE)
|
||||
set(SPIRV_REFLECT_ENABLE_ASAN OFF CACHE BOOL "Use address sanitization" FORCE)
|
||||
set(SPIRV_REFLECT_INSTALL OFF CACHE BOOL "Whether to install" FORCE)
|
||||
|
||||
# add_subdirectory(3rdparty/DiligentCore)
|
||||
add_subdirectory(3rdparty/glslang)
|
||||
add_subdirectory(3rdparty/SPIRV-Cross)
|
||||
add_subdirectory(3rdparty/VulkanMemoryAllocator)
|
||||
add_subdirectory(3rdparty/Vulkan-Headers)
|
||||
add_subdirectory(3rdparty/Vulkan-Utility-Libraries)
|
||||
add_subdirectory(3rdparty/SPIRV-Reflect)
|
||||
|
||||
set(XXHASH_BUILD_XXHSUM OFF)
|
||||
option(BUILD_SHARED_LIBS OFF)
|
||||
@@ -160,6 +175,8 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Util/Converters/GLToMG/RenderStateEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToVk/RenderStateEnumConverter.cpp
|
||||
MobileGL/MG_Util/Converters/MGToVk/TextureEnumConverter.cpp
|
||||
|
||||
MobileGL/MG_Util/Classifiers/TextureEnumClassifier.cpp
|
||||
|
||||
@@ -167,11 +184,19 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp
|
||||
|
||||
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
||||
MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp
|
||||
|
||||
MobileGL/MG_Util/SelfTest/DriverPost.cpp
|
||||
|
||||
MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp
|
||||
MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp
|
||||
|
||||
@@ -199,6 +224,7 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp
|
||||
MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp
|
||||
|
||||
MobileGL/MG_Impl/Init.cpp
|
||||
MobileGL/MG_Impl/GetProcAddress.cpp
|
||||
@@ -219,13 +245,17 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.cpp
|
||||
|
||||
MobileGL/MG_State/GLState/Core.cpp
|
||||
MobileGL/MG_State/EGLState/Core.cpp
|
||||
@@ -255,6 +285,21 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp
|
||||
)
|
||||
|
||||
if (APPLE AND NOT MOBILEGL_IOS)
|
||||
list(APPEND SOURCE_FILES
|
||||
MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
|
||||
MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp
|
||||
MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp
|
||||
MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
list(APPEND SOURCE_FILES
|
||||
MobileGL/MG_Util/SelfTest/DriverPostJni.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
set(MOBILEGL_LINK_LIBRARIES
|
||||
glslang::glslang
|
||||
spirv-cross-c
|
||||
@@ -262,6 +307,8 @@ set(MOBILEGL_LINK_LIBRARIES
|
||||
SPIRV-Tools
|
||||
xxHash::xxhash
|
||||
GPUOpen::VulkanMemoryAllocator
|
||||
Vulkan::UtilityHeaders
|
||||
spirv-reflect-static
|
||||
)
|
||||
|
||||
set(MOBILEGL_COMPILE_DEF
|
||||
@@ -311,8 +358,22 @@ target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}
|
||||
PUBLIC
|
||||
${MOBILEGL_COMPILE_DEF}
|
||||
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||
$<$<BOOL:${MOBILEGL_TRACE_ANGLE_VARIANTS}>:MOBILEGL_TRACE_ANGLE_VARIANTS=1>
|
||||
)
|
||||
|
||||
if(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
foreach(MOBILEGL_LOADER_ALIAS
|
||||
libEGL.so libEGL.so.1)
|
||||
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
"$<TARGET_FILE_NAME:${CMAKE_PROJECT_NAME}>"
|
||||
"$<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/${MOBILEGL_LOADER_ALIAS}"
|
||||
COMMENT "Creating ${MOBILEGL_LOADER_ALIAS} alias for Linux GL/EGL loaders"
|
||||
)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
add_library(${CMAKE_PROJECT_NAME}_s STATIC
|
||||
${SOURCE_FILES}
|
||||
@@ -344,6 +405,7 @@ if(NOT ANDROID)
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}_s
|
||||
PUBLIC
|
||||
${MOBILEGL_COMPILE_DEF}
|
||||
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -362,7 +424,48 @@ if (ANDROID)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID)
|
||||
if (APPLE AND NOT MOBILEGL_IOS)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
"-framework Cocoa"
|
||||
"-framework QuartzCore"
|
||||
"-framework Foundation"
|
||||
"-framework OpenGL"
|
||||
objc)
|
||||
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||
"-framework Cocoa"
|
||||
"-framework QuartzCore"
|
||||
"-framework Foundation"
|
||||
"-framework OpenGL"
|
||||
objc)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE AND MOBILEGL_IOS)
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME} PUBLIC MOBILEGL_IOS=1 _LIBCPP_DISABLE_AVAILABILITY)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
"-framework CoreGraphics"
|
||||
"-framework Foundation"
|
||||
"-framework QuartzCore"
|
||||
objc)
|
||||
if (MOBILEGL_VULKAN_LIBRARY)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC "${MOBILEGL_VULKAN_LIBRARY}")
|
||||
endif()
|
||||
|
||||
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||
target_compile_definitions(${CMAKE_PROJECT_NAME}_s PUBLIC MOBILEGL_IOS=1 _LIBCPP_DISABLE_AVAILABILITY)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||
"-framework CoreGraphics"
|
||||
"-framework Foundation"
|
||||
"-framework QuartzCore"
|
||||
objc)
|
||||
if (MOBILEGL_VULKAN_LIBRARY)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC "${MOBILEGL_VULKAN_LIBRARY}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT MOBILEGL_IOS)
|
||||
find_package(Vulkan)
|
||||
if (Vulkan_FOUND)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Vulkan::Vulkan Vulkan::Headers)
|
||||
@@ -370,6 +473,17 @@ if (NOT ANDROID)
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${Vulkan_INCLUDE_DIR})
|
||||
target_include_directories(${CMAKE_PROJECT_NAME}_s PUBLIC ${Vulkan_INCLUDE_DIR})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT ANDROID)
|
||||
# Enable testing in the top-level scope so a CTestTestfile.cmake is emitted
|
||||
# at the build-tree root. This lets `ctest` be invoked from the top-level
|
||||
# build directory (IDE "run all tests", CI) and discover every test in the
|
||||
# subdirectories below, instead of having to descend into each
|
||||
# MG_Test/MG_Benchmark subdirectory. Tests are tagged with CTest labels
|
||||
# (unit / benchmark / integration), so e.g. `ctest -L unit` selects just
|
||||
# the unit suite.
|
||||
enable_testing()
|
||||
|
||||
if (MOBILEGL_BUILD_TEST)
|
||||
add_subdirectory(MobileGL/MG_Test)
|
||||
@@ -378,4 +492,8 @@ if (NOT ANDROID)
|
||||
if (MOBILEGL_BUILD_BENCHMARK)
|
||||
add_subdirectory(MobileGL/MG_Benchmark)
|
||||
endif()
|
||||
|
||||
if (MOBILEGL_BUILD_TRACE_REPLAY)
|
||||
add_subdirectory(tools/trace_replay)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
+51
-1
@@ -14,9 +14,59 @@ namespace MobileGL::MG_Config {
|
||||
inline const String ProjectName = "MobileGL";
|
||||
inline const String CoreName = "MobileGL Core";
|
||||
inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)";
|
||||
inline const Version CoreVersion = {26, 2, 0, "-dev", VersionType::Development};
|
||||
inline const Version CoreVersion = {26, 7, 0, "-dev", VersionType::Development};
|
||||
inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
|
||||
inline const Uint64 CacheVersion = 0;
|
||||
|
||||
extern BackendType ActiveBackendType;
|
||||
|
||||
// Feature toggles parsed once from environment variables in MG_ConfigLoader::Init()
|
||||
// (ConfigLoader.cpp), before the accepted-env map is destroyed. All Bool fields share
|
||||
// one truthy rule: the variable is set, non-empty, not "0", and not "false"
|
||||
// (case-insensitive).
|
||||
//
|
||||
// Env variables intentionally NOT mirrored here (kept as live std::getenv at their
|
||||
// call sites):
|
||||
// - DISPLAY: X11 session variable, not MobileGL configuration.
|
||||
// - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init
|
||||
// (see MG_Util/Debug/Log.cpp).
|
||||
struct FeaturesTable {
|
||||
// MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries.
|
||||
Bool DisableTimerQuery = false;
|
||||
// MOBILEGL_USE_ANGLE: load ANGLE EGL/GLES libraries.
|
||||
Bool UseAngle = false;
|
||||
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||
// MOBILEGL_TRACE_ANGLE_VARIANT: signed trace-APK ANGLE build short hash.
|
||||
String TraceAngleVariant;
|
||||
#endif
|
||||
// MOBILEGL_DISABLE_SUBGROUP: force-disable Vulkan shader subgroup support.
|
||||
Bool DisableSubgroup = false;
|
||||
// MOBILEGL_MAGMA_R11G11B10F_FALLBACK: use fallback format for R11G11B10F on Vulkan.
|
||||
Bool MagmaR11G11B10FFallback = false;
|
||||
// MOBILEGL_MAGMA_FRAMESINFLIGHT: requested Magma frames in flight, defaulting to 3.
|
||||
Uint32 MagmaFramesInFlight = 3;
|
||||
// MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER: avoid mipmap min filters in samplers,
|
||||
// resolves certain rendering bugs on ANGLE + llvmpipe.
|
||||
Bool AvoidSamplerMipmapMinFilter = false;
|
||||
// MOBILEGL_COHERENT_AS_FLUSH: app-compat for engines (e.g. Flywheel) that write
|
||||
// GPU-read data through persistent GL_MAP_FLUSH_EXPLICIT_BIT maps they never
|
||||
// flush. Persistent FLUSH_EXPLICIT map requests are rewritten to coherent
|
||||
// semantics: writes reach the backend without glFlushMappedBufferRange, and
|
||||
// flush calls on rewritten maps become error-free no-ops. Non-persistent maps
|
||||
// keep spec FLUSH_EXPLICIT behavior.
|
||||
Bool CoherentAsFlush = false;
|
||||
// MOBILEGL_TRACE_SKIP_AUTODESTROY: skip teardown in the ELF destructor (Init.cpp).
|
||||
Bool TraceSkipAutodestroy = false;
|
||||
// MOBILEGL_DISABLE_UBO_RING: force the DirectGLES global-UBO upload back to the
|
||||
// per-draw glBufferSubData path instead of the persistent-mapped ring allocator
|
||||
// (negative control / driver-bug escape hatch).
|
||||
Bool DisableUboRing = false;
|
||||
// MOBILEGL_RELAXED_SEMANTICS: relax strict core-profile rules (e.g. VAO-0 draws,
|
||||
// texture-name reuse after delete) even on contexts that explicitly requested a core
|
||||
// profile. Without it, relaxed semantics still apply to every context that did not
|
||||
// explicitly request a core profile via EGL_CONTEXT_OPENGL_PROFILE_MASK / a >=3.1
|
||||
// version request.
|
||||
Bool RelaxedSemantics = false;
|
||||
};
|
||||
extern FeaturesTable Features;
|
||||
} // namespace MobileGL::MG_Config
|
||||
|
||||
@@ -8,6 +8,19 @@
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
extern char** environ;
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Config {
|
||||
// Zero/default-initialized at static-init time (all fields have constexpr-friendly
|
||||
// defaults), so it is safe to read even if MG_ConfigLoader::Init has not run yet.
|
||||
FeaturesTable Features;
|
||||
} // namespace MobileGL::MG_Config
|
||||
|
||||
namespace MobileGL::MG_ConfigLoader {
|
||||
static UniquePtr<UnorderedMap<String, String>> acceptedEnvVariablesMap;
|
||||
|
||||
@@ -56,6 +69,62 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
}
|
||||
}
|
||||
|
||||
// Unified truthy rule for boolean feature env variables: set, non-empty, not "0",
|
||||
// and not "false" (case-insensitive).
|
||||
static Bool IsTruthyValue(const String& value) {
|
||||
if (value.empty() || value == "0") {
|
||||
return false;
|
||||
}
|
||||
String lowered = value;
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return lowered != "false";
|
||||
}
|
||||
|
||||
inline Bool QueryEnvFlag(const String& key) {
|
||||
auto it = acceptedEnvVariablesMap->find(key);
|
||||
return it != acceptedEnvVariablesMap->end() && IsTruthyValue(it->second);
|
||||
}
|
||||
|
||||
inline Uint32 QueryEnvUint32(const String& key, Uint32 defaultValue, Uint32 minValue, Uint32 maxValue) {
|
||||
auto it = acceptedEnvVariablesMap->find(key);
|
||||
if (it == acceptedEnvVariablesMap->end()) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const String& value = it->second;
|
||||
char* parseEnd = nullptr;
|
||||
errno = 0;
|
||||
const unsigned long parsedValue = std::strtoul(value.c_str(), &parseEnd, 10);
|
||||
if (parseEnd == value.c_str() || *parseEnd != '\0' || errno == ERANGE || parsedValue < minValue ||
|
||||
parsedValue > maxValue) {
|
||||
MGLOG_W("Config: Ignoring invalid env variable %s='%s'; expected an integer in range [%u, %u], "
|
||||
"using default %u",
|
||||
key.c_str(), value.c_str(), minValue, maxValue, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return static_cast<Uint32>(parsedValue);
|
||||
}
|
||||
|
||||
inline void InitFeatures() {
|
||||
auto& features = MG_Config::Features;
|
||||
features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY");
|
||||
features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE");
|
||||
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
||||
#endif
|
||||
features.DisableSubgroup = QueryEnvFlag("MOBILEGL_DISABLE_SUBGROUP");
|
||||
features.MagmaR11G11B10FFallback = QueryEnvFlag("MOBILEGL_MAGMA_R11G11B10F_FALLBACK");
|
||||
features.MagmaFramesInFlight = QueryEnvUint32("MOBILEGL_MAGMA_FRAMESINFLIGHT", 3, 1, 64);
|
||||
features.AvoidSamplerMipmapMinFilter =
|
||||
QueryEnvFlag("MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER");
|
||||
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
|
||||
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
|
||||
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
||||
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||
}
|
||||
|
||||
inline void InitBackendType() {
|
||||
String backendTypeStr;
|
||||
QueryEnvVariable("MOBILEGL_BACKEND_TYPE", backendTypeStr, "DirectGLES");
|
||||
@@ -77,6 +146,7 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
InitializeAcceptedEnvVariables();
|
||||
|
||||
InitBackendType();
|
||||
InitFeatures();
|
||||
|
||||
// Destroy the map since we won't need it anymore
|
||||
acceptedEnvVariablesMap.reset();
|
||||
|
||||
+16
-8
@@ -32,9 +32,13 @@
|
||||
#define MOBILEGL_GLX_API MOBILEGL_API
|
||||
#define MOBILEGL_GL_API MOBILEGL_API
|
||||
#define MOBILEGL_EGL_API MOBILEGL_API
|
||||
#define MOBILEGL_CGL_API MOBILEGL_API
|
||||
#define MOBILEGL_NSOPENGL_API MOBILEGL_API
|
||||
|
||||
// ====================== MobileGL configurations ======================= //
|
||||
#ifndef MOBILEGL_LOG_ACTIVE_LEVEL
|
||||
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO
|
||||
#endif
|
||||
|
||||
#define MOBILEGL_LOG_ENABLE_CONSOLE 0
|
||||
#define MOBILEGL_LOG_ENABLE_FILE 1
|
||||
@@ -63,11 +67,15 @@
|
||||
#endif
|
||||
|
||||
// =============================== Utils ================================ //
|
||||
#define MOBILEGL_ASSERT(condition, ...) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
|
||||
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
|
||||
TRAP; \
|
||||
} \
|
||||
} while (0)
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||
#define MOBILEGL_ASSERT(condition, ...) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
|
||||
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
|
||||
TRAP; \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define MOBILEGL_ASSERT(condition, ...)
|
||||
#endif
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
@@ -108,10 +110,32 @@
|
||||
#define VK_USE_PLATFORM_WIN32_KHR
|
||||
#elif defined(__APPLE__)
|
||||
#define VK_USE_PLATFORM_METAL_EXT
|
||||
#elif defined(__linux__)
|
||||
#define VK_USE_PLATFORM_XLIB_KHR
|
||||
typedef struct _XDisplay Display;
|
||||
typedef unsigned long XID;
|
||||
typedef XID Window;
|
||||
typedef unsigned long VisualID;
|
||||
#else
|
||||
#warning "VK_USE_PLATFORM_*_KHR not defined for this platform!"
|
||||
#endif
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
#pragma push_macro("Bool")
|
||||
#pragma push_macro("None")
|
||||
#pragma push_macro("Always")
|
||||
#pragma push_macro("Status")
|
||||
#pragma push_macro("LSBFirst")
|
||||
#pragma push_macro("DestroyAll")
|
||||
#endif
|
||||
#include <vulkan/vulkan.h>
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
#pragma pop_macro("DestroyAll")
|
||||
#pragma pop_macro("LSBFirst")
|
||||
#pragma pop_macro("Status")
|
||||
#pragma pop_macro("Always")
|
||||
#pragma pop_macro("None")
|
||||
#pragma pop_macro("Bool")
|
||||
#endif
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
+43
-10
@@ -15,7 +15,39 @@
|
||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace {
|
||||
Bool g_isInitialized = false;
|
||||
|
||||
void DestroyImpl(Bool logLifecycle) {
|
||||
if (!g_isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (logLifecycle) {
|
||||
MGLOG_I("MobileGL closing...");
|
||||
}
|
||||
glslang::FinalizeProcess();
|
||||
MG_Backend::pActiveBackendObject.reset();
|
||||
MG_State::pGLContext.reset();
|
||||
MG_State::pEGLContext.reset();
|
||||
MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.reset();
|
||||
MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.reset();
|
||||
MG_Backend::gBackendFunctionsTable = {};
|
||||
g_isInitialized = false;
|
||||
if (logLifecycle) {
|
||||
MG_Util::Debug::Close();
|
||||
}
|
||||
|
||||
// TODO: add and use Destroy functions for other subsystems
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
if (g_isInitialized) {
|
||||
MGLOG_D("MobileGL already initialized; skipping duplicate Initialize()");
|
||||
return;
|
||||
}
|
||||
|
||||
MG_Util::Debug::InitFile();
|
||||
MGLOG_I("Initializing MobileGL...");
|
||||
MG_ConfigLoader::Init();
|
||||
@@ -28,19 +60,12 @@ namespace MobileGL {
|
||||
MGLOG_D("MG_Impl initialized");
|
||||
glslang::InitializeProcess();
|
||||
MGLOG_D("glslang initialized");
|
||||
g_isInitialized = true;
|
||||
MGLOG_I("MobileGL initialized");
|
||||
}
|
||||
|
||||
void Destroy() {
|
||||
MGLOG_I("MobileGL closing...");
|
||||
glslang::FinalizeProcess();
|
||||
delete MG_State::pGLContext;
|
||||
delete MG_State::pEGLContext;
|
||||
delete MG_Impl::GLImpl::TextureImpl::pProxyTextureManager;
|
||||
delete MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
|
||||
MG_Util::Debug::Close();
|
||||
|
||||
// TODO: add and use Destroy functions for other subsystems
|
||||
DestroyImpl(true);
|
||||
}
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
@@ -49,7 +74,15 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
__attribute__((destructor)) static void AutoDestroy() {
|
||||
Destroy();
|
||||
if (MG_Config::Features.TraceSkipAutodestroy) {
|
||||
return;
|
||||
}
|
||||
#if defined(__APPLE__)
|
||||
// macOS injected dylibs can run destructors after logging/backend static state is already torn down.
|
||||
return;
|
||||
#else
|
||||
DestroyImpl(false);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -7,18 +7,164 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "BackendObject.h"
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
namespace {
|
||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
(void)dpy;
|
||||
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
std::thread::id CurrentThreadKey() {
|
||||
return std::this_thread::get_id();
|
||||
}
|
||||
|
||||
const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache,
|
||||
SizeT targetIndex,
|
||||
SizeT formatIndex,
|
||||
FormatCapability capability) {
|
||||
if (HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) return "Full";
|
||||
if (HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex], capability)) return "Caveat";
|
||||
return "None";
|
||||
}
|
||||
|
||||
SizeT GetPrintedFormatNameWidth() {
|
||||
SizeT width = 0;
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||
width = std::max(width, MG_Util::ConvertTextureInternalFormatToString(format).size());
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
SizeT GetCapabilityColumnWidth(FormatCapability capability) {
|
||||
SizeT width = std::strlen(GetFormatCapabilityName(capability));
|
||||
width = std::max<SizeT>(width, std::strlen("Caveat"));
|
||||
return width;
|
||||
}
|
||||
|
||||
String BuildFormatCapabilityHeader(SizeT formatNameWidth) {
|
||||
std::ostringstream line;
|
||||
line << std::left << std::setw(static_cast<Int>(formatNameWidth)) << "";
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||
<< GetFormatCapabilityName(capability);
|
||||
}
|
||||
return line.str();
|
||||
}
|
||||
|
||||
String BuildFormatCapabilityRow(const FormatCapabilityCache& cache,
|
||||
SizeT targetIndex,
|
||||
SizeT formatIndex,
|
||||
SizeT formatNameWidth) {
|
||||
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||
std::ostringstream line;
|
||||
line << std::left << std::setw(static_cast<Int>(formatNameWidth))
|
||||
<< MG_Util::ConvertTextureInternalFormatToString(format);
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||
<< GetFormatCapabilitySupportString(cache, targetIndex, formatIndex, capability);
|
||||
}
|
||||
return line.str();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FormatCapabilityCache::Clear() {
|
||||
for (auto& row : FullCaps) {
|
||||
row.fill(FormatCapabilityFlags{});
|
||||
}
|
||||
for (auto& row : CaveatCaps) {
|
||||
row.fill(FormatCapabilityFlags{});
|
||||
}
|
||||
for (auto& row : SampleCounts) {
|
||||
for (auto& counts : row) {
|
||||
counts.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability) {
|
||||
return static_cast<Bool>(caps & capability);
|
||||
}
|
||||
|
||||
SizeT GetFormatCapabilityTargetIndex(TextureTarget target) {
|
||||
if (target == TextureTarget::Unknown || static_cast<Int>(target) < 0 ||
|
||||
static_cast<SizeT>(target) >= kFormatCapabilityTextureTargetCount) {
|
||||
return kFormatCapabilityTargetCount;
|
||||
}
|
||||
return static_cast<SizeT>(target);
|
||||
}
|
||||
|
||||
SizeT GetRenderbufferFormatCapabilityTargetIndex() {
|
||||
return kFormatCapabilityRenderbufferTargetIndex;
|
||||
}
|
||||
|
||||
const char* GetFormatCapabilityName(FormatCapability capability) {
|
||||
switch (capability) {
|
||||
case FormatCapability::Creatable:
|
||||
return "Creatable";
|
||||
case FormatCapability::Sampled:
|
||||
return "Sampled";
|
||||
case FormatCapability::LinearFilter:
|
||||
return "LinearFilter";
|
||||
case FormatCapability::GenerateMipmap:
|
||||
return "GenerateMipmap";
|
||||
case FormatCapability::TextureGather:
|
||||
return "TextureGather";
|
||||
case FormatCapability::TextureShadow:
|
||||
return "TextureShadow";
|
||||
case FormatCapability::FramebufferRenderable:
|
||||
return "FramebufferRenderable";
|
||||
case FormatCapability::FramebufferLayered:
|
||||
return "FramebufferLayered";
|
||||
case FormatCapability::MultisampleTexture:
|
||||
return "MultisampleTexture";
|
||||
case FormatCapability::MultisampleRenderbuffer:
|
||||
return "MultisampleRenderbuffer";
|
||||
case FormatCapability::ColorAttachment:
|
||||
return "ColorAttachment";
|
||||
case FormatCapability::DepthAttachment:
|
||||
return "DepthAttachment";
|
||||
case FormatCapability::StencilAttachment:
|
||||
return "StencilAttachment";
|
||||
case FormatCapability::TextureBuffer:
|
||||
return "TextureBuffer";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
String GetFormatCapabilityTargetName(SizeT targetIndex) {
|
||||
if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) {
|
||||
return "Renderbuffer";
|
||||
}
|
||||
if (targetIndex >= kFormatCapabilityTextureTargetCount) {
|
||||
return "Unknown";
|
||||
}
|
||||
return MG_Util::ConvertTextureTargetToString(static_cast<TextureTarget>(targetIndex));
|
||||
}
|
||||
|
||||
void PrintFormatCapabilities(const FormatCapabilityCache& cache) {
|
||||
const SizeT formatNameWidth = GetPrintedFormatNameWidth();
|
||||
|
||||
MGLOG_D("Backend format capabilities:");
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
|
||||
MGLOG_D("");
|
||||
const String targetName = GetFormatCapabilityTargetName(targetIndex);
|
||||
MGLOG_D("- %s", targetName.c_str());
|
||||
const String header = BuildFormatCapabilityHeader(formatNameWidth);
|
||||
MGLOG_D("%s", header.c_str());
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const String row = BuildFormatCapabilityRow(cache, targetIndex, formatIndex, formatNameWidth);
|
||||
MGLOG_D("%s", row.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool BackendObject::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (dpy == EGL_NO_DISPLAY) {
|
||||
@@ -42,28 +188,116 @@ namespace MobileGL::MG_Backend {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::CreateEGLWindowSurface(const WindowHandle& handle) {
|
||||
Bool BackendObject::CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
return RegisterEGLWindowSurface(surface, handle) && ActivateEGLSurface(surface);
|
||||
}
|
||||
|
||||
Bool BackendObject::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||
if (surfaceIt == m_eglSurfaces.end() || surfaceIt->second.Kind != SurfaceKind::Window) {
|
||||
MGLOG_E("ResizeEGLWindowSurface failed: no window surface is initialized");
|
||||
return false;
|
||||
}
|
||||
surfaceIt->second.Window.Width = width;
|
||||
surfaceIt->second.Window.Height = height;
|
||||
if (m_eglSurface == surface) {
|
||||
m_windowHandle.Width = width;
|
||||
m_windowHandle.Height = height;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
return RegisterEGLPbufferSurface(surface, width, height) && ActivateEGLSurface(surface);
|
||||
}
|
||||
|
||||
Bool BackendObject::RegisterEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_eglDisplayInitialized) {
|
||||
MGLOG_E("CreateEGLWindowSurface failed: EGL display is not initialized");
|
||||
MGLOG_E("RegisterEGLWindowSurface failed: EGL display is not initialized");
|
||||
return false;
|
||||
}
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
MGLOG_E("RegisterEGLWindowSurface failed: invalid EGLSurface");
|
||||
return false;
|
||||
}
|
||||
if (handle.Backend == WindowBackend::Unknown || !handle.Handle) {
|
||||
MGLOG_E("CreateEGLWindowSurface failed: invalid native window handle");
|
||||
MGLOG_E("RegisterEGLWindowSurface failed: invalid native window handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_eglWindowSurfaceInitialized && m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle) {
|
||||
auto& state = m_eglSurfaces[surface];
|
||||
state = EGLSurfaceState{
|
||||
.Kind = SurfaceKind::Window,
|
||||
.Window = handle,
|
||||
.Width = static_cast<EGLint>(std::max<Uint32>(handle.Width, 1)),
|
||||
.Height = static_cast<EGLint>(std::max<Uint32>(handle.Height, 1)),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject::RegisterEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_eglDisplayInitialized) {
|
||||
MGLOG_E("RegisterEGLPbufferSurface failed: EGL display is not initialized");
|
||||
return false;
|
||||
}
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
MGLOG_E("RegisterEGLPbufferSurface failed: invalid EGLSurface");
|
||||
return false;
|
||||
}
|
||||
if (width <= 0 || height <= 0) {
|
||||
MGLOG_E("RegisterEGLPbufferSurface failed: invalid size %dx%d", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_eglSurfaces[surface] = EGLSurfaceState{
|
||||
.Kind = SurfaceKind::Pbuffer,
|
||||
.Width = width,
|
||||
.Height = height,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
const BackendObject::EGLSurfaceState* BackendObject::GetRegisteredEGLSurface(EGLSurface surface) const {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||
return surfaceIt == m_eglSurfaces.end() ? nullptr : &surfaceIt->second;
|
||||
}
|
||||
|
||||
Bool BackendObject::ActivateEGLSurface(EGLSurface surface) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
const auto* surfaceState = GetRegisteredEGLSurface(surface);
|
||||
if (!surfaceState) {
|
||||
MGLOG_E("ActivateEGLSurface failed: EGL surface is not registered");
|
||||
return false;
|
||||
}
|
||||
if (m_eglSurfaceInitialized && m_eglSurface == surface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SetWindowHandle(handle);
|
||||
if (!InitWindowSurface()) {
|
||||
MGLOG_E("CreateEGLWindowSurface failed: backend InitWindowSurface failed");
|
||||
if (surfaceState->Kind == SurfaceKind::Window) {
|
||||
SetWindowHandle(surfaceState->Window);
|
||||
if (!InitWindowSurface()) {
|
||||
MGLOG_E("ActivateEGLSurface failed: backend InitWindowSurface failed");
|
||||
return false;
|
||||
}
|
||||
} else if (surfaceState->Kind == SurfaceKind::Pbuffer) {
|
||||
if (!InitPbufferSurface(surfaceState->Width, surfaceState->Height)) {
|
||||
MGLOG_E("ActivateEGLSurface failed: backend InitPbufferSurface failed");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
MGLOG_E("ActivateEGLSurface failed: unsupported surface kind");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_eglWindowSurfaceInitialized = true;
|
||||
m_eglSurface = surface;
|
||||
m_eglSurfaceInitialized = true;
|
||||
m_eglSurfaceKind = surfaceState->Kind;
|
||||
m_eglCurrentThreads.clear();
|
||||
m_backendCapabilitiesInitialized = false;
|
||||
return true;
|
||||
@@ -73,7 +307,7 @@ namespace MobileGL::MG_Backend {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
const auto threadKey = CurrentThreadKey();
|
||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
||||
m_eglCurrentThreads.erase(threadKey);
|
||||
ReleaseEGLCurrentThread(threadKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -81,8 +315,22 @@ namespace MobileGL::MG_Backend {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL display mismatch or not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!m_eglWindowSurfaceInitialized) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL window surface is not initialized");
|
||||
if (!m_eglSurfaceInitialized) {
|
||||
if (draw != read || !ActivateEGLSurface(draw)) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL surface is not initialized");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!GetRegisteredEGLSurface(draw) || !GetRegisteredEGLSurface(read)) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL surface is not registered");
|
||||
return false;
|
||||
}
|
||||
if (draw != read) {
|
||||
MGLOG_E("MakeEGLCurrent failed: separate draw/read surfaces are not supported");
|
||||
return false;
|
||||
}
|
||||
if (draw != m_eglSurface && !ActivateEGLSurface(draw)) {
|
||||
MGLOG_E("MakeEGLCurrent failed: EGL surface is not backed by this backend");
|
||||
return false;
|
||||
}
|
||||
if (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE || ctx == EGL_NO_CONTEXT) {
|
||||
@@ -98,14 +346,23 @@ namespace MobileGL::MG_Backend {
|
||||
m_backendCapabilitiesInitialized = true;
|
||||
}
|
||||
|
||||
m_eglCurrentThreads[threadKey] = true;
|
||||
ReleaseEGLCurrentThread(threadKey);
|
||||
m_eglCurrentThreads[threadKey] = EGLCurrentState{
|
||||
.Display = dpy,
|
||||
.DrawSurface = draw,
|
||||
.ReadSurface = read,
|
||||
.Context = ctx,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject::ResetEGLRuntimeState() {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_eglWindowSurfaceInitialized = false;
|
||||
m_eglSurfaceInitialized = false;
|
||||
m_backendCapabilitiesInitialized = false;
|
||||
m_eglSurfaceKind = SurfaceKind::None;
|
||||
m_eglSurface = EGL_NO_SURFACE;
|
||||
m_windowHandle = {};
|
||||
m_eglCurrentThreads.clear();
|
||||
}
|
||||
|
||||
@@ -115,11 +372,17 @@ namespace MobileGL::MG_Backend {
|
||||
MGLOG_E("SwapEGLBuffers failed: EGL display mismatch or not initialized");
|
||||
return false;
|
||||
}
|
||||
if (m_eglCurrentThreads.find(CurrentThreadKey()) == m_eglCurrentThreads.end()) {
|
||||
const auto currentIt = m_eglCurrentThreads.find(CurrentThreadKey());
|
||||
if (currentIt == m_eglCurrentThreads.end()) {
|
||||
MGLOG_E("SwapEGLBuffers failed: no current context attached");
|
||||
return false;
|
||||
}
|
||||
if (!m_eglWindowSurfaceInitialized || draw == EGL_NO_SURFACE) {
|
||||
if (currentIt->second.Display != dpy || currentIt->second.DrawSurface != draw ||
|
||||
currentIt->second.Context == EGL_NO_CONTEXT) {
|
||||
MGLOG_E("SwapEGLBuffers failed: draw surface is not current on this thread");
|
||||
return false;
|
||||
}
|
||||
if (!m_eglSurfaceInitialized || draw == EGL_NO_SURFACE || draw != m_eglSurface) {
|
||||
MGLOG_E("SwapEGLBuffers failed: invalid draw surface");
|
||||
return false;
|
||||
}
|
||||
@@ -134,8 +397,99 @@ namespace MobileGL::MG_Backend {
|
||||
return true;
|
||||
}
|
||||
|
||||
void BackendObject::SetEGLSwapInterval(Int interval) {
|
||||
const auto& backendFunctions = GetBackendFunctions();
|
||||
if (backendFunctions.SetSwapInterval) {
|
||||
backendFunctions.SetSwapInterval(interval);
|
||||
}
|
||||
}
|
||||
|
||||
Bool BackendObject::IsEGLSurfaceCurrent(EGLSurface surface) const {
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& current : m_eglCurrentThreads) {
|
||||
if (current.second.DrawSurface == surface || current.second.ReadSurface == surface) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BackendObject::DestroyPendingEGLSurfaceIfUnused(EGLSurface surface) {
|
||||
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||
if (surfaceIt == m_eglSurfaces.end() || !surfaceIt->second.DestroyPending ||
|
||||
IsEGLSurfaceCurrent(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_eglSurfaces.erase(surfaceIt);
|
||||
if (m_eglSurface == surface) {
|
||||
OnEGLSurfaceReleased(surface);
|
||||
ResetEGLRuntimeState();
|
||||
}
|
||||
}
|
||||
|
||||
void BackendObject::ReleaseEGLCurrentThread(const std::thread::id& threadKey) {
|
||||
auto currentIt = m_eglCurrentThreads.find(threadKey);
|
||||
if (currentIt == m_eglCurrentThreads.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const EGLSurface drawSurface = currentIt->second.DrawSurface;
|
||||
const EGLSurface readSurface = currentIt->second.ReadSurface;
|
||||
m_eglCurrentThreads.erase(currentIt);
|
||||
DestroyPendingEGLSurfaceIfUnused(drawSurface);
|
||||
DestroyPendingEGLSurfaceIfUnused(readSurface);
|
||||
}
|
||||
|
||||
void BackendObject::ReleaseEGLSurface(EGLSurface surface) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||
if (surfaceIt == m_eglSurfaces.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsEGLSurfaceCurrent(surface)) {
|
||||
surfaceIt->second.DestroyPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_eglSurfaces.erase(surfaceIt);
|
||||
if (m_eglSurface == surface) {
|
||||
OnEGLSurfaceReleased(surface);
|
||||
ResetEGLRuntimeState();
|
||||
}
|
||||
}
|
||||
|
||||
void BackendObject::ReleaseEGLResources() {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
ResetEGLRuntimeState();
|
||||
m_eglSurfaces.clear();
|
||||
m_eglDisplay = EGL_NO_DISPLAY;
|
||||
m_eglDisplayInitialized = false;
|
||||
}
|
||||
|
||||
void BackendObject::SetWindowHandle(const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_windowHandle = handle;
|
||||
}
|
||||
|
||||
const FormatCapabilityCache& BackendObject::GetFormatCapabilities() const {
|
||||
return m_formatCapabilities;
|
||||
}
|
||||
|
||||
FormatCapabilityCache& BackendObject::MutableFormatCapabilities() {
|
||||
return m_formatCapabilities;
|
||||
}
|
||||
|
||||
Bool BackendObject::InitPbufferSurface(EGLint width, EGLint height) {
|
||||
(void)width;
|
||||
(void)height;
|
||||
return false;
|
||||
}
|
||||
|
||||
void BackendObject::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||
(void)surface;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend
|
||||
|
||||
@@ -8,8 +8,14 @@
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State::GLState {
|
||||
class FramebufferObject;
|
||||
class ITextureObject;
|
||||
}
|
||||
|
||||
enum class BackendType {
|
||||
DirectGLES,
|
||||
DirectVulkan,
|
||||
@@ -18,11 +24,88 @@ namespace MobileGL {
|
||||
};
|
||||
|
||||
namespace MG_Backend {
|
||||
enum class FormatCapability : Uint64 {
|
||||
Creatable = 1ull << 0,
|
||||
|
||||
Sampled = 1ull << 1,
|
||||
LinearFilter = 1ull << 2,
|
||||
GenerateMipmap = 1ull << 3,
|
||||
TextureGather = 1ull << 4,
|
||||
TextureShadow = 1ull << 5,
|
||||
|
||||
FramebufferRenderable = 1ull << 6,
|
||||
FramebufferLayered = 1ull << 7,
|
||||
MultisampleTexture = 1ull << 8,
|
||||
MultisampleRenderbuffer = 1ull << 9,
|
||||
|
||||
ColorAttachment = 1ull << 10,
|
||||
DepthAttachment = 1ull << 11,
|
||||
StencilAttachment = 1ull << 12,
|
||||
|
||||
TextureBuffer = 1ull << 13
|
||||
};
|
||||
|
||||
using FormatCapabilityFlags = Flags<FormatCapability>;
|
||||
|
||||
inline constexpr Array<FormatCapability, 14> kReportedFormatCapabilities = {
|
||||
FormatCapability::Creatable,
|
||||
FormatCapability::Sampled,
|
||||
FormatCapability::LinearFilter,
|
||||
FormatCapability::GenerateMipmap,
|
||||
FormatCapability::TextureGather,
|
||||
FormatCapability::TextureShadow,
|
||||
FormatCapability::FramebufferRenderable,
|
||||
FormatCapability::FramebufferLayered,
|
||||
FormatCapability::MultisampleTexture,
|
||||
FormatCapability::MultisampleRenderbuffer,
|
||||
FormatCapability::ColorAttachment,
|
||||
FormatCapability::DepthAttachment,
|
||||
FormatCapability::StencilAttachment,
|
||||
FormatCapability::TextureBuffer,
|
||||
};
|
||||
|
||||
inline constexpr SizeT kFormatCapabilityTextureTargetCount =
|
||||
static_cast<SizeT>(TextureTarget::TextureTargetCount);
|
||||
inline constexpr SizeT kFormatCapabilityRenderbufferTargetIndex = kFormatCapabilityTextureTargetCount;
|
||||
inline constexpr SizeT kFormatCapabilityTargetCount = kFormatCapabilityTextureTargetCount + 1;
|
||||
inline constexpr SizeT kFormatCapabilityFormatCount =
|
||||
static_cast<SizeT>(TextureInternalFormat::TextureInternalFormatCount);
|
||||
|
||||
using FormatCapabilityTable =
|
||||
Array<Array<FormatCapabilityFlags, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>;
|
||||
using FormatSampleCountTable =
|
||||
Array<Array<Vector<Int>, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>;
|
||||
|
||||
struct FormatCapabilityCache {
|
||||
FormatCapabilityTable FullCaps{};
|
||||
FormatCapabilityTable CaveatCaps{};
|
||||
FormatSampleCountTable SampleCounts{};
|
||||
|
||||
void Clear();
|
||||
};
|
||||
|
||||
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability);
|
||||
SizeT GetFormatCapabilityTargetIndex(TextureTarget target);
|
||||
SizeT GetRenderbufferFormatCapabilityTargetIndex();
|
||||
const char* GetFormatCapabilityName(FormatCapability capability);
|
||||
String GetFormatCapabilityTargetName(SizeT targetIndex);
|
||||
void PrintFormatCapabilities(const FormatCapabilityCache& cache);
|
||||
|
||||
// Opaque backend fence-sync handle, created by GLFunctionsTable::FenceSync
|
||||
// and released by GLFunctionsTable::DeleteSync.
|
||||
using BackendSyncHandle = void*;
|
||||
|
||||
// Opaque backend timer-query handle, created by
|
||||
// GLFunctionsTable::BeginTimeElapsedQuery / QueryCounterTimestamp and
|
||||
// released by GLFunctionsTable::DeleteBackendQuery.
|
||||
using BackendQueryHandle = void*;
|
||||
|
||||
struct GLFunctionsTable {
|
||||
void (*DrawArrays)(GLenum mode, GLint first, GLsizei count);
|
||||
void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||
void (*DrawElementsBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLint basevertex);
|
||||
void (*MultiDrawArrays)(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||
void (*MultiDrawElements)(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount);
|
||||
void (*MultiDrawElementsBaseVertex)(GLenum mode, const GLsizei* count, GLenum type,
|
||||
@@ -31,6 +114,10 @@ namespace MobileGL {
|
||||
void (*MultiDrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
||||
GLsizei stride);
|
||||
void (*MultiDrawArraysIndirect)(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void (*MultiDrawElementsIndirectCount)(GLenum mode, GLenum type, const void* indirect,
|
||||
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
|
||||
void (*MultiDrawArraysIndirectCount)(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void (*DrawRangeElementsBaseVertex)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex);
|
||||
void (*DrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
@@ -54,29 +141,163 @@ namespace MobileGL {
|
||||
void (*ClearBufferfv)(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void (*ClearBufferuiv)(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void (*ClearBufferiv)(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void (*ClearNamedFramebufferfv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void (*BlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
||||
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void (*CopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height, GLint border);
|
||||
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
||||
GLsizei width, GLsizei height);
|
||||
void (*CopyImageSubData)(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||
void (*GenerateMipmap)(GLenum target);
|
||||
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
void* pixels);
|
||||
void (*GetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void (*GetTextureImage)(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
||||
GLsizei bufSize, GLvoid* pixels);
|
||||
void (*DispatchCompute)(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void (*DispatchComputeIndirect)(GLintptr indirect);
|
||||
void (*MemoryBarrier)(GLbitfield barriers);
|
||||
void (*MemoryBarrierByRegion)(GLbitfield barriers);
|
||||
void (*BindImageTexture)(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer,
|
||||
GLenum access, GLenum format);
|
||||
void (*GetIntegeri_v)(GLenum target, GLuint index, GLint* data);
|
||||
void (*GetInteger64i_v)(GLenum target, GLuint index, GLint64* data);
|
||||
void (*GetProgramiv)(GLuint program, GLenum pname, GLint* params);
|
||||
void (*GetProgramInterfaceiv)(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||
GLuint (*GetProgramResourceIndex)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void (*GetProgramResourceName)(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
||||
GLsizei* length, GLchar* name);
|
||||
void (*GetProgramResourceiv)(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||
GLint (*GetProgramResourceLocation)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
GLint (*GetProgramResourceLocationIndex)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void (*ShaderStorageBlockBinding)(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||
// GL fence sync objects. All entries are optional (may be null); the
|
||||
// frontend then falls back to always-signaled sync semantics.
|
||||
// FenceSync may itself return null when the backend cannot create a
|
||||
// fence right now (e.g. the calling thread does not own the backend
|
||||
// context); the frontend treats such a sync as always signaled.
|
||||
BackendSyncHandle (*FenceSync)();
|
||||
GLenum (*ClientWaitSync)(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void (*WaitSync)(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void (*DeleteSync)(BackendSyncHandle sync);
|
||||
Bool (*GetSyncStatus)(BackendSyncHandle sync); // true = signaled
|
||||
// GL timer-query objects (GL_ARB_timer_query). All entries are
|
||||
// optional (may be null); the frontend then falls back to zero
|
||||
// results and reports GL_QUERY_COUNTER_BITS == 0.
|
||||
// BeginTimeElapsedQuery / QueryCounterTimestamp may themselves
|
||||
// return null when the backend cannot create a query right now;
|
||||
// the frontend treats such a query as immediately available with
|
||||
// a zero result.
|
||||
// Dynamic support check: true only when the live backend can
|
||||
// actually time at the moment of the call (extension / entry
|
||||
// points / timestamp valid bits are known then, not at table
|
||||
// init). Gates the advertised GL_QUERY_COUNTER_BITS.
|
||||
Bool (*IsTimerQuerySupported)();
|
||||
BackendQueryHandle (*BeginTimeElapsedQuery)(); // starts a TIME_ELAPSED span
|
||||
void (*EndTimeElapsedQuery)(BackendQueryHandle query); // ends the span
|
||||
BackendQueryHandle (*QueryCounterTimestamp)(); // glQueryCounter(GL_TIMESTAMP) one-shot
|
||||
Bool (*IsQueryResultAvailable)(BackendQueryHandle query); // non-blocking
|
||||
// Returns true when a final value was produced (*outNanoseconds
|
||||
// written; the frontend may cache it and release the handle).
|
||||
// Returns false when the result could not be obtained YET - e.g.
|
||||
// a Vulkan wait that refuses to block on a not-yet-submitted
|
||||
// frame serial - in which case the frontend must keep the handle
|
||||
// and leave the query readable later.
|
||||
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||
void (*DeleteBackendQuery)(BackendQueryHandle query);
|
||||
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
||||
};
|
||||
struct GlobalBackendFunctionsTable {
|
||||
GLFunctionsTable GL;
|
||||
void (*Present)();
|
||||
// Optional: applies the app-requested eglSwapInterval to the native
|
||||
// presentation path (null = backend keeps its own pacing policy).
|
||||
void (*SetSwapInterval)(Int interval);
|
||||
};
|
||||
|
||||
struct DynamicBackendParameters {
|
||||
SizeT UniformBufferOffsetAlignment = 256;
|
||||
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT. 1.0 means the backend cannot filter anisotropically,
|
||||
// which is also why the extension is not advertised in that case.
|
||||
Float MaxTextureMaxAnisotropy = 1.0f;
|
||||
Float AliasedLineWidthRangeMin = 1.0f;
|
||||
Float AliasedLineWidthRangeMax = 1.0f;
|
||||
Float SmoothLineWidthRangeMin = 1.0f;
|
||||
Float SmoothLineWidthRangeMax = 1.0f;
|
||||
Float SmoothLineWidthGranularity = 1.0f;
|
||||
Float PointSizeRangeMin = 1.0f;
|
||||
Float PointSizeRangeMax = 1.0f;
|
||||
Float PointSizeGranularity = 1.0f;
|
||||
Int Max3DTextureSize = 16384;
|
||||
Int MaxArrayTextureLayers = 2048;
|
||||
Int MaxCubeMapTextureSize = 16384;
|
||||
Int MaxFramebufferWidth = 16384;
|
||||
Int MaxFramebufferHeight = 16384;
|
||||
Int MaxFramebufferLayers = 2048;
|
||||
Int MaxRenderbufferSize = 16384;
|
||||
Int MaxTextureSize = 16384;
|
||||
Int MaxColorTextureSamples = 1;
|
||||
Int MaxDepthTextureSamples = 1;
|
||||
Int MaxFramebufferSamples = 1;
|
||||
Int MaxIntegerSamples = 1;
|
||||
Int MaxSamples = 1;
|
||||
Int MaxSampleMaskWords = 1;
|
||||
Int MaxTextureImageUnits = 32;
|
||||
Int MaxVertexTextureImageUnits = 32;
|
||||
Int MaxComputeTextureImageUnits = 32;
|
||||
Int MaxCombinedTextureImageUnits = 192;
|
||||
Int MaxVertexAttribs = 16;
|
||||
Int MaxComputeShaderStorageBlocks = 8;
|
||||
Int MaxCombinedShaderStorageBlocks = 32;
|
||||
Int MaxComputeUniformBlocks = 12;
|
||||
Int MaxComputeWorkGroupInvocations = 128;
|
||||
Int MaxShaderStorageBufferBindings = 8;
|
||||
Int MaxTextureBufferSize = 65536;
|
||||
Int MaxUniformBufferBindings = 24;
|
||||
Int MaxUniformBlockSize = 16384;
|
||||
Int MaxImageUnits = 8;
|
||||
Int MaxCombinedImageUniforms = 8;
|
||||
Int MaxVertexImageUniforms = 0;
|
||||
Int MaxGeometryImageUniforms = 0;
|
||||
Int MaxFragmentImageUniforms = 8;
|
||||
Int MaxComputeImageUniforms = 8;
|
||||
Int MaxDrawBuffers = 8;
|
||||
Int MaxColorAttachments = 8;
|
||||
Int MaxClipDistances = 8;
|
||||
Int MaxViewports = 16;
|
||||
Int MaxViewportWidth = 16384;
|
||||
Int MaxViewportHeight = 16384;
|
||||
Float ViewportBoundsRangeMin = 0.0f;
|
||||
Float ViewportBoundsRangeMax = 0.0f;
|
||||
Int ViewportSubpixelBits = 0;
|
||||
Bool SupportsWideLines = false;
|
||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||
Uint32 SubgroupSize = 0;
|
||||
Uint32 SubgroupSupportedStages = 0;
|
||||
Uint32 SubgroupSupportedFeatures = 0;
|
||||
Bool SubgroupQuadOperationsInAllStages = false;
|
||||
};
|
||||
|
||||
enum class WindowBackend {
|
||||
Android,
|
||||
// TODO: X11, Wayland, Windows, macOS, etc.
|
||||
X11,
|
||||
MetalLayer,
|
||||
// TODO: Wayland, Windows, etc.
|
||||
WindowBackendCount,
|
||||
Unknown = -1
|
||||
};
|
||||
@@ -84,6 +305,8 @@ namespace MobileGL {
|
||||
struct WindowHandle {
|
||||
WindowBackend Backend = WindowBackend::Unknown;
|
||||
void* Handle = nullptr;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
};
|
||||
|
||||
class BackendObject {
|
||||
@@ -95,9 +318,16 @@ namespace MobileGL {
|
||||
virtual Bool InitWindowSurface() = 0;
|
||||
|
||||
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
||||
virtual Bool CreateEGLWindowSurface(const WindowHandle& handle);
|
||||
virtual Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
||||
virtual Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height);
|
||||
virtual Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
||||
virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
||||
// Forwards the app-requested eglSwapInterval to the backend's native
|
||||
// presentation path (no-op for backends without a SetSwapInterval hook).
|
||||
virtual void SetEGLSwapInterval(Int interval);
|
||||
virtual void ReleaseEGLSurface(EGLSurface surface);
|
||||
virtual void ReleaseEGLResources();
|
||||
|
||||
void SetWindowHandle(const WindowHandle& handle);
|
||||
|
||||
@@ -105,18 +335,56 @@ namespace MobileGL {
|
||||
virtual String GetBackendAPIVersionString() const = 0;
|
||||
virtual const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0;
|
||||
virtual const DynamicBackendParameters& GetDynamicParameters() const = 0;
|
||||
const FormatCapabilityCache& GetFormatCapabilities() const;
|
||||
virtual BackendType GetBackendType() const = 0;
|
||||
|
||||
protected:
|
||||
enum class SurfaceKind {
|
||||
None,
|
||||
Window,
|
||||
Pbuffer
|
||||
};
|
||||
|
||||
struct EGLCurrentState {
|
||||
EGLDisplay Display = EGL_NO_DISPLAY;
|
||||
EGLSurface DrawSurface = EGL_NO_SURFACE;
|
||||
EGLSurface ReadSurface = EGL_NO_SURFACE;
|
||||
EGLContext Context = EGL_NO_CONTEXT;
|
||||
};
|
||||
|
||||
struct EGLSurfaceState {
|
||||
SurfaceKind Kind = SurfaceKind::None;
|
||||
Bool DestroyPending = false;
|
||||
WindowHandle Window;
|
||||
EGLint Width = 1;
|
||||
EGLint Height = 1;
|
||||
};
|
||||
|
||||
void ResetEGLRuntimeState();
|
||||
Bool RegisterEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
||||
Bool RegisterEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
||||
const EGLSurfaceState* GetRegisteredEGLSurface(EGLSurface surface) const;
|
||||
Bool ActivateEGLSurface(EGLSurface surface);
|
||||
virtual Bool InitPbufferSurface(EGLint width, EGLint height);
|
||||
virtual void OnEGLSurfaceReleased(EGLSurface surface);
|
||||
FormatCapabilityCache& MutableFormatCapabilities();
|
||||
|
||||
mutable std::recursive_mutex m_eglStateMutex;
|
||||
FormatCapabilityCache m_formatCapabilities;
|
||||
WindowHandle m_windowHandle;
|
||||
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
||||
EGLSurface m_eglSurface = EGL_NO_SURFACE;
|
||||
Bool m_eglDisplayInitialized = false;
|
||||
Bool m_eglWindowSurfaceInitialized = false;
|
||||
Bool m_eglSurfaceInitialized = false;
|
||||
Bool m_backendCapabilitiesInitialized = false;
|
||||
UnorderedMap<std::thread::id, Bool> m_eglCurrentThreads;
|
||||
SurfaceKind m_eglSurfaceKind = SurfaceKind::None;
|
||||
UnorderedMap<std::thread::id, EGLCurrentState> m_eglCurrentThreads;
|
||||
UnorderedMap<EGLSurface, EGLSurfaceState> m_eglSurfaces;
|
||||
|
||||
private:
|
||||
Bool IsEGLSurfaceCurrent(EGLSurface surface) const;
|
||||
void DestroyPendingEGLSurfaceIfUnused(EGLSurface surface);
|
||||
void ReleaseEGLCurrentThread(const std::thread::id& threadKey);
|
||||
};
|
||||
} // namespace MG_Backend
|
||||
} // namespace MobileGL
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,12 @@
|
||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Populates the same format-capability cache used by backend startup. The caller
|
||||
// must keep the supplied GLES context current for the duration of this call.
|
||||
void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl,
|
||||
const MG_External::GLESCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache);
|
||||
|
||||
class BackendObject_DirectGLES : public BackendObject {
|
||||
public:
|
||||
~BackendObject_DirectGLES() override;
|
||||
@@ -20,9 +26,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Bool InitCapabilities() override;
|
||||
Bool InitWindowSurface() override;
|
||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||
Bool CreateEGLWindowSurface(const WindowHandle& handle) override;
|
||||
Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override;
|
||||
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
|
||||
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||
void ReleaseEGLResources() override;
|
||||
|
||||
const RendererInfo& GetRendererInfo() const override;
|
||||
String GetBackendAPIVersionString() const override;
|
||||
@@ -32,9 +41,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
const MG_External::GLESFunctionsTable& GetGLESFunctions() const;
|
||||
const MG_External::EGLFunctionsTable& GetEGLFunctions() const;
|
||||
void ApplyGLESCapabilitiesForTesting(const MG_External::GLESCapabilities& capabilities);
|
||||
|
||||
private:
|
||||
void UpdateDynamicBackendParameters();
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||
|
||||
Bool m_initialized = false;
|
||||
MG_External::EGLFunctionsTable m_EGLFunctions;
|
||||
@@ -42,4 +54,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MG_External::GLESCapabilities m_GLESCapabilities;
|
||||
DynamicBackendParameters m_dynamicParameters;
|
||||
};
|
||||
|
||||
// Single-source-of-truth helpers shared with the driver POST
|
||||
// (MG_Util/SelfTest/DriverPost.cpp), so the identity strings and extension list
|
||||
// MobileGL reports to applications on this backend cannot drift from what the
|
||||
// POST screen shows.
|
||||
|
||||
// Static identity of the Espryt renderer (renderer/backend names, target GL/GLSL
|
||||
// versions, ExtraVendor). The Extensions vector inside is live backend state that
|
||||
// is reconciled after capability init; callers that need the advertised list for
|
||||
// a known capability set must use BuildAdvertisedExtensions instead.
|
||||
const RendererInfo& GetRendererIdentity();
|
||||
|
||||
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
|
||||
// for a device whose timer queries / anisotropic filtering are (or are not) usable.
|
||||
// The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported);
|
||||
|
||||
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
|
||||
// initialized backend returns from GetBackendAPIVersionString (and that ends up
|
||||
// inside the application-visible GL_RENDERER string).
|
||||
String FormatBackendAPIVersionString(const String& glesRendererString, Int glesMajor, Int glesMinor);
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||
#include <MG_State/GLState/TextureState/TextureState.h>
|
||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||
@@ -17,6 +19,10 @@
|
||||
operation Utils::CheckGLESError();
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Re-establishes the frontend texture-unit bindings on the native ES context.
|
||||
// Content uploads use scratch bindings, so draws and dispatches call this after
|
||||
// texture synchronization.
|
||||
void BindCurrentTextures();
|
||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
@@ -25,11 +31,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount);
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex);
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex);
|
||||
@@ -46,18 +55,100 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
GLuint baseinstance);
|
||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height, GLint border);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height);
|
||||
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||
void GenerateMipmap(GLenum target);
|
||||
const GLubyte* GetString(GLenum name);
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void DispatchComputeIndirect(GLintptr indirect);
|
||||
void MemoryBarrier(GLbitfield barriers);
|
||||
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||
void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access,
|
||||
GLenum format);
|
||||
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||
void GetInteger64i_v(GLenum target, GLuint index, GLint64* data);
|
||||
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length,
|
||||
GLchar* name);
|
||||
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||
Bool InitWindowSurface(NativeWindowType window);
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height);
|
||||
Bool MakeCurrent();
|
||||
Bool ReleaseCurrent();
|
||||
// True when the backend ES context is current on the calling thread, i.e.
|
||||
// immediate buffer ops may issue GL calls right now.
|
||||
Bool IsBackendContextCurrentOnThisThread();
|
||||
// GL fence sync objects, backed by native ES fences. FenceSync returns null
|
||||
// (the frontend then falls back to an always-signaled sync) when the calling
|
||||
// thread does not own the ES context. Waits/queries degrade to "signaled" in
|
||||
// the same situation, and handles created under a since-destroyed ES context
|
||||
// are always treated as signaled.
|
||||
BackendSyncHandle FenceSync();
|
||||
GLenum ClientWaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void WaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void DeleteSync(BackendSyncHandle sync);
|
||||
Bool GetSyncStatus(BackendSyncHandle sync);
|
||||
// True when GL_EXT_disjoint_timer_query and every entry point the timer
|
||||
// hooks below need are present. Also gates the E_GL_ARB_timer_query
|
||||
// advertisement in BackendObject_DirectGLES::InitCapabilities, and is
|
||||
// registered as the GLFunctionsTable::IsTimerQuerySupported hook: a pure
|
||||
// capability read needs no current ES context, and it stays false until
|
||||
// the ES capabilities have been filled in.
|
||||
Bool AreTimerQueriesSupported();
|
||||
// GL timer-query objects, backed by GL_EXT_disjoint_timer_query. The
|
||||
// creators return null (the frontend then falls back to an immediately
|
||||
// available zero result) when the calling thread does not own the ES
|
||||
// context or the extension/entry points are missing, and handles created
|
||||
// under a since-destroyed ES context are always treated as complete with
|
||||
// a zero result (mirrors the fence-sync handles above).
|
||||
BackendQueryHandle BeginTimeElapsedQuery();
|
||||
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||
BackendQueryHandle QueryCounterTimestamp();
|
||||
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||
// Returns true when a final value landed in *outNanoseconds (a zero for
|
||||
// null or stale-generation handles IS final: the frontend may cache it
|
||||
// and release the handle). Returns false only when the calling thread
|
||||
// does not own the ES context, so the value is genuinely unobtainable
|
||||
// right now; the handle stays alive and readable later.
|
||||
Bool GetQueryResult64(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||
void DeleteBackendQuery(BackendQueryHandle query);
|
||||
Int64 GetGpuTimestampNs();
|
||||
void Present();
|
||||
// Frame-completion watermarks for the buffer-storage pool: CurrentFrameSerial()
|
||||
// is bumped once per Present(); CompletedFrameSerial() is the newest frame whose
|
||||
// GPU work has provably finished (advanced by polling a one-fence-per-frame ring).
|
||||
// A buffer retired during frame N is safe to recycle once CompletedFrameSerial() >= N.
|
||||
Uint64 CurrentFrameSerial();
|
||||
Uint64 CompletedFrameSerial();
|
||||
// Applies (or defers until the window surface exists) the app-requested
|
||||
// eglSwapInterval on the native EGL surface.
|
||||
void SetSwapInterval(Int interval);
|
||||
void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
|
||||
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
|
||||
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,67 +8,304 @@
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include "DirectGLES.h"
|
||||
#include "MG_State/GLState/SamplerState/SamplerObject.h"
|
||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||
String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType);
|
||||
|
||||
template <typename StateObject, typename BackendObject>
|
||||
class StateBackendObjectRegistry {
|
||||
public:
|
||||
using StatePtr = SharedPtr<StateObject>;
|
||||
using StateWeakPtr = std::weak_ptr<StateObject>;
|
||||
using BackendPtr = SharedPtr<BackendObject>;
|
||||
using BackendMap = UnorderedMap<StateObject*, BackendPtr>;
|
||||
using StateRefMap = UnorderedMap<StateObject*, StateWeakPtr>;
|
||||
using iterator = typename BackendMap::iterator;
|
||||
using const_iterator = typename BackendMap::const_iterator;
|
||||
|
||||
BackendPtr& GetOrCreate(const StatePtr& stateObj) {
|
||||
MOBILEGL_ASSERT(stateObj != nullptr, "State object must not be null");
|
||||
|
||||
auto* key = stateObj.get();
|
||||
auto trackedStateIt = m_stateRefs.find(key);
|
||||
if (trackedStateIt != m_stateRefs.end() && trackedStateIt->second.expired()) {
|
||||
EraseByKey(key);
|
||||
}
|
||||
m_stateRefs[key] = stateObj;
|
||||
return m_backendObjects[key];
|
||||
}
|
||||
|
||||
iterator find(StateObject* stateObj) {
|
||||
if (!IsAlive(stateObj)) {
|
||||
EraseByKey(stateObj);
|
||||
return m_backendObjects.end();
|
||||
}
|
||||
return m_backendObjects.find(stateObj);
|
||||
}
|
||||
|
||||
const_iterator find(StateObject* stateObj) const {
|
||||
return const_cast<StateBackendObjectRegistry*>(this)->find(stateObj);
|
||||
}
|
||||
|
||||
iterator begin() { return m_backendObjects.begin(); }
|
||||
const_iterator begin() const { return m_backendObjects.begin(); }
|
||||
iterator end() { return m_backendObjects.end(); }
|
||||
const_iterator end() const { return m_backendObjects.end(); }
|
||||
|
||||
void CollectGarbageIfNeeded() {
|
||||
++m_gcTick;
|
||||
if (m_gcTick < kGCInterval) {
|
||||
return;
|
||||
}
|
||||
CollectGarbage();
|
||||
m_gcTick = 0;
|
||||
}
|
||||
|
||||
void CollectGarbageNow() { CollectGarbage(); }
|
||||
|
||||
private:
|
||||
bool IsAlive(StateObject* stateObj) const {
|
||||
const auto trackedStateIt = m_stateRefs.find(stateObj);
|
||||
if (trackedStateIt == m_stateRefs.end()) {
|
||||
return false;
|
||||
}
|
||||
return !trackedStateIt->second.expired();
|
||||
}
|
||||
|
||||
void EraseByKey(StateObject* stateObj) {
|
||||
m_stateRefs.erase(stateObj);
|
||||
m_backendObjects.erase(stateObj);
|
||||
}
|
||||
|
||||
void CollectGarbage() {
|
||||
if (m_isCollecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_isCollecting = true;
|
||||
|
||||
Vector<StateObject*> staleKeys;
|
||||
staleKeys.reserve(m_stateRefs.size());
|
||||
for (const auto& [stateKey, stateWeakRef] : m_stateRefs) {
|
||||
if (stateWeakRef.expired()) {
|
||||
staleKeys.push_back(stateKey);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* stateKey : staleKeys) {
|
||||
m_stateRefs.erase(stateKey);
|
||||
m_backendObjects.erase(stateKey);
|
||||
}
|
||||
|
||||
m_isCollecting = false;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr Uint32 kGCInterval = 1024;
|
||||
StateRefMap m_stateRefs;
|
||||
BackendMap m_backendObjects;
|
||||
Uint32 m_gcTick = 0;
|
||||
Bool m_isCollecting = false;
|
||||
};
|
||||
|
||||
namespace BufferImpl {
|
||||
const GLenum TempBufferTarget = GL_ARRAY_BUFFER;
|
||||
class BackendBufferObject {
|
||||
|
||||
// The DirectGLES storage behind one frontend buffer. Owned (refcounted) by
|
||||
// the frontend BufferObject; immediate BufferBackendOps keep it current, so
|
||||
// draw-time "sync" reduces to ensuring the storage exists.
|
||||
class GLESBufferResource : public MG_State::GLState::BackendBufferResource {
|
||||
public:
|
||||
BackendBufferObject();
|
||||
void SyncToBackend(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
||||
Uint GetBackendBufferId() { return m_backendBufferId; }
|
||||
void Bind(GLenum target = TempBufferTarget);
|
||||
~GLESBufferResource() override = default;
|
||||
|
||||
private:
|
||||
void SyncToBackend_glBufferData(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
||||
void SyncToBackend_glBufferSubData(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
||||
void SyncToBackend_glMapBufferRange(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject,
|
||||
Bool invalidate = true, Bool unsynchronized = true);
|
||||
|
||||
Uint m_backendBufferId = 0;
|
||||
SizeT m_prevBufferSize = 0;
|
||||
Bool m_isInitialized = false;
|
||||
Uint id = 0;
|
||||
SizeT storageSize = 0;
|
||||
Bool storageInitialized = false;
|
||||
// ES context generation this resource's id belongs to; ids from a
|
||||
// destroyed context are invalid and must not be deleted or reused.
|
||||
Uint contextGeneration = 0;
|
||||
// Frontend change serial the backend storage reflects. When immediate
|
||||
// ops cannot run (ops unregistered, no current context), this lags and
|
||||
// EnsureBufferResource falls back to a full re-upload. Atomic: read on
|
||||
// the context-owning thread while ops on other threads may update it.
|
||||
std::atomic<Uint64> syncedChangeSerial{0};
|
||||
// Ops that arrived while no ES context was current on the calling thread
|
||||
// (or before storage existed); replayed by EnsureBufferResource. The ES
|
||||
// context migrates between app threads, so deferring ops can race with
|
||||
// the owning thread replaying them: guard both fields with pendingMutex.
|
||||
Bool pendingRespecify = false;
|
||||
VecRange1D pendingRanges;
|
||||
std::mutex pendingMutex;
|
||||
// Zero-copy coherent persistent map (EXT_buffer_storage): the GL store is
|
||||
// immutable, persistently+coherently mapped, and persistentPtr is what the app
|
||||
// (and the frontend PipeResource) write into directly. While set, draw-time
|
||||
// sync is a no-op and no per-draw glBufferSubData is issued. Cleared on ES
|
||||
// context loss.
|
||||
Bool persistentMapped = false;
|
||||
void* persistentPtr = nullptr;
|
||||
};
|
||||
|
||||
extern BackendBufferObject* g_boundVertexBufferObject;
|
||||
extern UnorderedMap<SharedPtr<MG_State::GLState::BufferObject>, SharedPtr<BackendBufferObject>>
|
||||
g_backendBufferObjects;
|
||||
// Registered as the frontend's BufferBackendOps at backend init and on
|
||||
// every MakeCurrent (the ES context can be destroyed and recreated, e.g.
|
||||
// by the trace replayer's probe context).
|
||||
void RegisterBufferBackendOps();
|
||||
void UnregisterBufferBackendOps();
|
||||
// The ES context died: unregister ops, invalidate all outstanding GL ids
|
||||
// (they belonged to the dead context) and drop deferred deletes.
|
||||
void OnBackendContextDestroyed();
|
||||
|
||||
// Get-or-create the backend resource and bring its storage up to date
|
||||
// (creates the GL buffer, replays pending ops, pushes persistent-mapped
|
||||
// ranges). Requires the ES context to be current. Returns nullptr only
|
||||
// for null input.
|
||||
GLESBufferResource* EnsureBufferResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||
// Existing resource or nullptr; performs no GL calls.
|
||||
GLESBufferResource* GetBufferResource(MG_State::GLState::BufferObject* bufferObject);
|
||||
|
||||
// Deletes GL buffers whose owning frontend objects died (possibly on a
|
||||
// thread without a current ES context). Called from draw-time sync.
|
||||
void ProcessDeferredBufferReleases();
|
||||
|
||||
// glBindBuffer with a redundant-bind cache for GL_ARRAY_BUFFER.
|
||||
void BindBufferId(GLenum target, Uint id);
|
||||
void InvalidateArrayBufferBindingCache();
|
||||
// Redundant-bind cache for INDEXED buffer bindings (glBindBufferBase/Range on
|
||||
// GL_UNIFORM_BUFFER / GL_SHADER_STORAGE_BUFFER): skips the GL call when the
|
||||
// (id, range) already at that index matches, like the array-buffer/texture/
|
||||
// sampler caches already do. Invalidated on MakeCurrent (context may reset).
|
||||
void BindBufferBaseCached(GLenum glTarget, Uint index, Uint id);
|
||||
void BindBufferRangeCached(GLenum glTarget, Uint index, Uint id, GLintptr offset, GLsizeiptr size);
|
||||
void InvalidateIndexedBufferBindingCache();
|
||||
// Buffer-storage pool maintenance. TrimBufferPool evicts over-budget entries
|
||||
// (called once per frame from Present); ClearBufferPool drops all pooled ids
|
||||
// without glDeleteBuffers (called when the ES context is going away).
|
||||
void TrimBufferPool();
|
||||
void ClearBufferPool();
|
||||
|
||||
// --- Global-UBO ring ------------------------------------------------------
|
||||
// One persistently+coherently mapped buffer (EXT_buffer_storage) shared by
|
||||
// every program's lowered default-uniform block. Each content change is
|
||||
// bump-allocated into a fresh slot and bound with glBindBufferRange, so the
|
||||
// CPU never rewrites bytes the GPU may still be reading — the per-draw
|
||||
// glBufferSubData into one static UBO forced Adreno to resolve that
|
||||
// write-after-read hazard on every uniform-dirtying draw (MC dirties
|
||||
// uniforms every draw). Reclamation rides the Present() frame-fence
|
||||
// watermark; no ring bytes are recycled before their frame's GPU work
|
||||
// completed.
|
||||
//
|
||||
// A program's cached slot, reusable within one frame while the frontend UBO
|
||||
// content version is unchanged. Cross-frame reuse is intentionally not
|
||||
// attempted: later same-frame allocations may recycle bytes of completed
|
||||
// frames, so re-referencing them would need per-bind pinning — rewriting
|
||||
// GetUBOSize() bytes once per program per frame is far cheaper.
|
||||
struct UboRingAllocation {
|
||||
Uint32 contentVersion = ~0u; // frontend UBO content version held at `offset`
|
||||
Uint32 ringGeneration = 0; // ring identity the slot lives in (0 = never valid)
|
||||
Uint64 frameSerial = ~Uint64{0}; // frame the slot was written in
|
||||
SizeT offset = 0;
|
||||
};
|
||||
// False when the feature is disabled, EXT_buffer_storage / fences are
|
||||
// missing, the ES context is not current, or ring creation already failed
|
||||
// under this context (callers then take the legacy glBufferSubData path).
|
||||
Bool UboRingAvailable();
|
||||
// Bump-allocate `size` bytes aligned to GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT.
|
||||
// Grows the ring (new GL store, generation bump) when the in-flight span
|
||||
// would be overrun. Returns false when storage (re)creation fails.
|
||||
Bool UboRingAllocate(SizeT size, SizeT& outOffset);
|
||||
void* UboRingMappedPtr();
|
||||
Uint UboRingBufferId();
|
||||
Uint32 UboRingGeneration();
|
||||
// Present()-time upkeep: records the frame's high-water mark for reclamation
|
||||
// and deletes grown-away ring stores once the GPU is done with them.
|
||||
void UboRingOnPresent();
|
||||
} // namespace BufferImpl
|
||||
|
||||
namespace VertexArrayImpl {
|
||||
class BackendVertexArrayObject {
|
||||
public:
|
||||
BackendVertexArrayObject();
|
||||
void SyncToBackend(SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
|
||||
Uint GetBackendVertexArrayId() { return m_backendVAOId; }
|
||||
void Bind();
|
||||
~BackendVertexArrayObject();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
|
||||
void SyncClientSideAttributesForDrawArrays(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
|
||||
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
||||
void Bind() const;
|
||||
|
||||
private:
|
||||
void BindAttributeBuffer(Uint index, const MG_State::GLState::VertexAttribute& attrib);
|
||||
|
||||
Uint m_backendVAOId = 0;
|
||||
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
||||
Bool m_isInitialized = false;
|
||||
Uint16 m_syncedIndexBufferVersion = 0;
|
||||
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
||||
m_syncedAttributeVersions;
|
||||
};
|
||||
|
||||
extern UnorderedMap<SharedPtr<MG_State::GLState::VertexArrayObject>, SharedPtr<BackendVertexArrayObject>>
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject>
|
||||
g_backendVertexArrayObjects;
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
namespace TextureImpl {
|
||||
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
||||
if (target == TextureTarget::Texture1D || target == TextureTarget::TextureRectangle ||
|
||||
target == TextureTarget::Texture2DMultisampleArray || target == TextureTarget::Texture1DArray ||
|
||||
target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DArray)
|
||||
return false;
|
||||
return true;
|
||||
// Rectangle textures need non-normalized sampling ES cannot express; everything else is
|
||||
// either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see
|
||||
// MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and
|
||||
// coordinate padding for 1D/1D-array shaders.
|
||||
return target != TextureTarget::TextureRectangle;
|
||||
}
|
||||
|
||||
// ES has no 1D targets: 1D textures are stored as 2D (height 1) and 1D arrays as 2D arrays
|
||||
// (height 1, layers in depth). Must match SPIRV-Cross's ES 1D-as-2D shader emulation.
|
||||
inline TextureTarget MapToBackendTextureTarget(TextureTarget target) {
|
||||
switch (target) {
|
||||
case TextureTarget::Texture1D:
|
||||
return TextureTarget::Texture2D;
|
||||
case TextureTarget::Texture1DArray:
|
||||
return TextureTarget::Texture2DArray;
|
||||
default:
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
inline GLenum ConvertTextureTargetToBackendGLEnum(TextureTarget target) {
|
||||
return MG_Util::ConvertTextureTargetToGLEnum(MapToBackendTextureTarget(target));
|
||||
}
|
||||
|
||||
inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) {
|
||||
switch (uploadTarget) {
|
||||
case TextureUploadTarget::Texture1D:
|
||||
return GL_TEXTURE_2D;
|
||||
case TextureUploadTarget::Texture1DArray:
|
||||
return GL_TEXTURE_2D_ARRAY;
|
||||
default:
|
||||
return MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// 1D arrays store layers in the state-side height; the ES 2D-array image keeps height 1 and
|
||||
// moves the layer count into depth.
|
||||
inline IntVec3 GetBackendUploadSize(TextureTarget stateTarget, const IntVec3& texelSize) {
|
||||
if (stateTarget == TextureTarget::Texture1DArray) {
|
||||
return {texelSize.x(), 1, texelSize.y()};
|
||||
}
|
||||
return texelSize;
|
||||
}
|
||||
|
||||
inline Bool IsMultisampleTextureTarget(TextureTarget target) {
|
||||
return target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
inline Bool SupportsWrapR(TextureTarget target) {
|
||||
return target == TextureTarget::Texture3D || target == TextureTarget::TextureCubeMap;
|
||||
}
|
||||
|
||||
struct StateTextureBasicInfo { // Used for tracking texture state changes
|
||||
@@ -78,11 +315,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SizeT depth = 0;
|
||||
SizeT mipmapLevels = 0;
|
||||
Uint bufferExternalIndex = 0;
|
||||
Int samples = 0;
|
||||
Bool fixedSampleLocations = true;
|
||||
|
||||
bool operator==(const StateTextureBasicInfo& other) const {
|
||||
return internalFormat == other.internalFormat && width == other.width && height == other.height &&
|
||||
depth == other.depth && mipmapLevels == other.mipmapLevels &&
|
||||
bufferExternalIndex == other.bufferExternalIndex;
|
||||
bufferExternalIndex == other.bufferExternalIndex && samples == other.samples &&
|
||||
fixedSampleLocations == other.fixedSampleLocations;
|
||||
}
|
||||
|
||||
bool operator!=(const StateTextureBasicInfo& other) const { return !(*this == other); }
|
||||
@@ -92,15 +332,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
class BackendTextureObject {
|
||||
public:
|
||||
BackendTextureObject();
|
||||
void SyncMipmapsToBackend(SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void SyncBuiltinSamplerToBackend(SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void SyncTextureParamsToBackend(SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void SyncMipmapsToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void SyncBuiltinSamplerToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void SyncTextureParamsToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
void RequireImageBindableStorage();
|
||||
void Bind(GLenum target, Uint unit = TempTextureUnit);
|
||||
Uint GetBackendTextureId();
|
||||
Uint GetBackendTextureId() const;
|
||||
|
||||
private:
|
||||
void RecreateBackendTexture();
|
||||
|
||||
Uint m_backendTextureId = 0;
|
||||
Bool m_isInitialized = false;
|
||||
Bool m_imageBindableStorageRequired = false;
|
||||
Bool m_backendStorageImmutable = false;
|
||||
StateTextureBasicInfo m_prevTextureInfo;
|
||||
SamplerParameters m_cacheSamplerParameters;
|
||||
UintVec2 m_cacheLodRange = {0, 1000};
|
||||
@@ -113,8 +358,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
void ActivateTextureUnit(Uint unit);
|
||||
void UnbindTexture(Uint unit, GLenum target);
|
||||
extern UnorderedMap<SharedPtr<MG_State::GLState::ITextureObject>, SharedPtr<BackendTextureObject>>
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::ITextureObject, BackendTextureObject>
|
||||
g_backendTextureObjects;
|
||||
SharedPtr<BackendTextureObject>& SyncTextureObjectToBackend(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||
Bool imageBindableStorageRequired = false);
|
||||
extern Array<Array<BackendTextureObject*, (SizeT)TextureTarget::TextureTargetCount>,
|
||||
MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
|
||||
g_boundTexturesCache;
|
||||
@@ -125,13 +373,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
class BackendFramebufferObject {
|
||||
public:
|
||||
BackendFramebufferObject();
|
||||
void SyncToBackend(SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
||||
FramebufferTarget asTarget);
|
||||
Uint GetBackendFramebufferId() { return m_backendFBOId; }
|
||||
void Bind(FramebufferTarget target);
|
||||
bool SyncAttachmentObject(GLenum glFBOTarget,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachmentObject,
|
||||
GLenum glBackendAttachment);
|
||||
void InvalidateSyncedState();
|
||||
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
|
||||
void Bind(FramebufferTarget target) const;
|
||||
// FramebufferAttachmentType GetCompactedAttachmentTypeAtDrawBufferIndex(Int index);
|
||||
GLenum GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const;
|
||||
|
||||
@@ -159,28 +405,113 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
FramebufferObject::FramebufferAttachmentVersionArray m_syncedFrontendAttachmentVersions = {0};
|
||||
};
|
||||
|
||||
extern UnorderedMap<SharedPtr<MG_State::GLState::FramebufferObject>, SharedPtr<BackendFramebufferObject>>
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
||||
g_backendFramebufferObjects;
|
||||
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
|
||||
// Tracks the bound FBO's object version (bumped on any attachment/drawbuffer change)
|
||||
// per target: re-attaching textures or changing draw buffers on an already-bound FBO
|
||||
// must re-sync it even when the binding-slot version has not moved.
|
||||
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedObjectVersions;
|
||||
extern Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
|
||||
g_fboSyncedObjects;
|
||||
} // namespace FramebufferImpl
|
||||
|
||||
// Image uniforms take their unit from the layout(binding=N) qualifier baked into
|
||||
// the transpiled ESSL; unlike samplers they must not (and in ES cannot) be
|
||||
// assigned through glUniform1i.
|
||||
inline Bool IsImageUniformType(GLenum type) {
|
||||
switch (type) {
|
||||
case 0x904D: /*GL_IMAGE_2D*/
|
||||
case 0x904E: /*GL_IMAGE_3D*/
|
||||
case 0x9050: /*GL_IMAGE_CUBE*/
|
||||
case 0x9051: /*GL_IMAGE_BUFFER*/
|
||||
case 0x9053: /*GL_IMAGE_2D_ARRAY*/
|
||||
case 0x9058: /*GL_INT_IMAGE_2D*/
|
||||
case 0x9059: /*GL_INT_IMAGE_3D*/
|
||||
case 0x905B: /*GL_INT_IMAGE_CUBE*/
|
||||
case 0x905C: /*GL_INT_IMAGE_BUFFER*/
|
||||
case 0x905E: /*GL_INT_IMAGE_2D_ARRAY*/
|
||||
case 0x9063: /*GL_UNSIGNED_INT_IMAGE_2D*/
|
||||
case 0x9064: /*GL_UNSIGNED_INT_IMAGE_3D*/
|
||||
case 0x9066: /*GL_UNSIGNED_INT_IMAGE_CUBE*/
|
||||
case 0x9067: /*GL_UNSIGNED_INT_IMAGE_BUFFER*/
|
||||
case 0x9069: /*GL_UNSIGNED_INT_IMAGE_2D_ARRAY*/
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace PrgramImpl {
|
||||
class BackendProgramObjectImpl {
|
||||
public:
|
||||
// Per-link cache of a sampler-style uniform's backend location: built once in
|
||||
// SyncToBackend so draws stop issuing glGetUniformLocation string queries.
|
||||
// lastAssignedUnit mirrors the program-state value set through glUniform1i
|
||||
// (program state persists across binds, so caching per program is exact).
|
||||
struct SamplerUniformBinding {
|
||||
Uint frontendLocation = 0;
|
||||
Int backendLocation = -1;
|
||||
GLenum uniformType = 0;
|
||||
Int lastAssignedUnit = -1;
|
||||
};
|
||||
|
||||
BackendProgramObjectImpl();
|
||||
~BackendProgramObjectImpl();
|
||||
void SyncToBackend(SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||
void Use();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||
void Use() const;
|
||||
void SetBaseInstance(Uint32 baseInstance) const;
|
||||
void SetBaseInstanceWordIndex(Int32 wordIndex) const;
|
||||
void SetDrawID(Uint32 drawId) const;
|
||||
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
||||
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||
|
||||
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
||||
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
||||
Vector<SamplerUniformBinding>& GetSamplerUniformBindings() { return m_samplerUniformBindings; }
|
||||
Uint32 GetLastUploadedGlobalUboVersion() const { return m_lastUploadedGlobalUboVersion; }
|
||||
void SetLastUploadedGlobalUboVersion(Uint32 version) { m_lastUploadedGlobalUboVersion = version; }
|
||||
// Backend-reported GL_UNIFORM_BLOCK_DATA_SIZE of the global block; ring
|
||||
// bindings must span at least this much (may exceed the frontend's
|
||||
// reflected size when the transpiled block pads differently).
|
||||
Int GetGlobalUboBackendBlockSize() const { return m_globalUboBackendBlockSize; }
|
||||
BufferImpl::UboRingAllocation& GetGlobalUboRingAllocation() { return m_globalUboRingAllocation; }
|
||||
// Frontend link version this backend program (and its resource caches) was
|
||||
// built from; a mismatch means every link-derived cache here is stale.
|
||||
Uint32 GetSyncedLinkVersion() const { return m_syncedLinkVersion; }
|
||||
|
||||
private:
|
||||
void CacheResourceLocations(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||
|
||||
Uint m_backendProgramId = 0;
|
||||
Uint m_backendGlobalUBOId = 0;
|
||||
Int m_baseInstanceUniformLocation = -1;
|
||||
Int m_drawIdUniformLocation = -1;
|
||||
Int m_baseInstanceWordIndexUniformLocation = -1;
|
||||
Int m_indirectParamsBinding = -1;
|
||||
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||
Uint32 m_unormFallbackClampOutputMask = 0;
|
||||
Bool m_isInitialized = false;
|
||||
|
||||
Int m_globalUboBackendBlockIndex = -1;
|
||||
Int m_globalUboBackendBlockSize = 0;
|
||||
Vector<Int> m_uniformBlockBackendIndices; // frontend block index -> backend index (-1 = absent)
|
||||
Vector<SamplerUniformBinding> m_samplerUniformBindings;
|
||||
Uint32 m_lastUploadedGlobalUboVersion = ~0u;
|
||||
BufferImpl::UboRingAllocation m_globalUboRingAllocation;
|
||||
Uint32 m_syncedLinkVersion = ~0u;
|
||||
};
|
||||
|
||||
extern UnorderedMap<SharedPtr<MG_State::GLState::ProgramObject>, SharedPtr<BackendProgramObjectImpl>>
|
||||
extern Uint32 g_snormFallbackClampOutputMask;
|
||||
extern Uint32 g_unormFallbackClampOutputMask;
|
||||
// Backend id of the last glUseProgram issued through this backend; lets Use()
|
||||
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||
// ES context is recreated.
|
||||
extern Uint g_lastUsedBackendProgramId;
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
||||
g_backendProgramObjects;
|
||||
} // namespace PrgramImpl
|
||||
|
||||
@@ -188,9 +519,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
class BackendSamplerObject {
|
||||
public:
|
||||
BackendSamplerObject();
|
||||
void SyncToBackend(SharedPtr<MG_State::GLState::SamplerObject>& stateSamplerObject);
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::SamplerObject>& stateSamplerObject);
|
||||
void Bind(Uint unit);
|
||||
Uint GetBackendSamplerId();
|
||||
Uint GetBackendSamplerId() const;
|
||||
|
||||
private:
|
||||
Uint m_backendSamplerId = 0;
|
||||
@@ -203,7 +534,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
extern Array<BackendSamplerObject*, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
|
||||
g_boundSamplersCache;
|
||||
extern UnorderedMap<SharedPtr<MG_State::GLState::SamplerObject>, SharedPtr<BackendSamplerObject>>
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::SamplerObject, BackendSamplerObject>
|
||||
g_backendSamplerObjects;
|
||||
} // namespace SamplerImpl
|
||||
|
||||
@@ -212,8 +543,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
public:
|
||||
BackendRenderbufferObject();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::RenderbufferObject>& stateRBOObject);
|
||||
Uint GetBackendRenderbufferId() { return m_backendRBOId; }
|
||||
void Bind();
|
||||
Uint GetBackendRenderbufferId() const { return m_backendRBOId; }
|
||||
void Bind() const;
|
||||
|
||||
private:
|
||||
Uint m_backendRBOId = 0;
|
||||
@@ -221,9 +552,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
TextureInternalFormat m_cacheInternalFormat = TextureInternalFormat::Unknown;
|
||||
Int m_cacheWidth = 0;
|
||||
Int m_cacheHeight = 0;
|
||||
Int m_cacheSamples = 0;
|
||||
};
|
||||
|
||||
extern UnorderedMap<SharedPtr<MG_State::GLState::RenderbufferObject>, SharedPtr<BackendRenderbufferObject>>
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject>
|
||||
g_backendRenderbufferObjects;
|
||||
} // namespace RenderbufferImpl
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "DirectGLES.h"
|
||||
#include "Utils.h"
|
||||
#include "Managers.h"
|
||||
#include "MG_Backend/BackendObjects.h"
|
||||
#include "MG_Util/Converters/GLToMG/FramebufferEnumConverter.h"
|
||||
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
||||
|
||||
@@ -17,28 +18,137 @@
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||
#include <MG_Util/Math/HalfFloat.h>
|
||||
#include <MG_Util/Math/SmallFloat.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
namespace BufferImpl {} // namespace BufferImpl
|
||||
namespace {
|
||||
Flags<PixelFormatNormalizeOptionBit> GetForcedPixelFormatNormalizeOptions() {
|
||||
Flags<PixelFormatNormalizeOptionBit> options;
|
||||
if (g_GLESCapabilities.IsAngleRenderer) {
|
||||
options |= PixelFormatNormalizeOptionBit::NoRgb16;
|
||||
options |= PixelFormatNormalizeOptionBit::NoSnorm16;
|
||||
options |= PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
namespace VertexArrayImpl {} // namespace VertexArrayImpl
|
||||
Flags<PixelFormatNormalizeOptionBit> GetDriverPixelFormatNormalizeOptions() {
|
||||
Flags<PixelFormatNormalizeOptionBit> options = PixelFormatNormalizeOptionBit::NoDepthComponent32;
|
||||
options |= PixelFormatNormalizeOptionBit::NoRGBA8Snorm;
|
||||
options |= PixelFormatNormalizeOptionBit::NoRGB16Snorm;
|
||||
if (!g_GLESCapabilities.SupportsNorm16Texture) {
|
||||
options |= PixelFormatNormalizeOptionBit::NoNorm16;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
Flags<PixelFormatNormalizeOptionBit> GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat) {
|
||||
using namespace MG_Util::TextureFormatProcessor;
|
||||
const Flags<PixelFormatNormalizeOptionBit> forcedOptions =
|
||||
GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, GetForcedPixelFormatNormalizeOptions());
|
||||
if (forcedOptions) {
|
||||
return forcedOptions;
|
||||
}
|
||||
return GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
|
||||
GetDriverPixelFormatNormalizeOptions());
|
||||
}
|
||||
|
||||
Bool HasCachedFormatCapability(TextureInternalFormat internalFormat,
|
||||
SizeT targetIndex,
|
||||
Bool caveat,
|
||||
FormatCapability capability) {
|
||||
if (!pActiveBackendObject || targetIndex >= kFormatCapabilityTargetCount) {
|
||||
return false;
|
||||
}
|
||||
const SizeT formatIndex = static_cast<SizeT>(internalFormat);
|
||||
if (formatIndex >= kFormatCapabilityFormatCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const FormatCapabilityCache& cache = pActiveBackendObject->GetFormatCapabilities();
|
||||
const FormatCapabilityFlags caps =
|
||||
caveat ? cache.CaveatCaps[targetIndex][formatIndex] : cache.FullCaps[targetIndex][formatIndex];
|
||||
return HasFormatCapability(caps, capability);
|
||||
}
|
||||
|
||||
Bool HasAnyCachedFormatCapability(TextureInternalFormat internalFormat,
|
||||
Bool caveat,
|
||||
FormatCapability capability) {
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
|
||||
if (HasCachedFormatCapability(internalFormat, targetIndex, caveat, capability)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ShouldUseCaveatFormat(TextureInternalFormat internalFormat, SizeT targetIndex) {
|
||||
if (targetIndex < kFormatCapabilityTargetCount) {
|
||||
const Bool fullCreatable =
|
||||
HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::Creatable);
|
||||
const Bool caveatCreatable =
|
||||
HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::Creatable);
|
||||
const Bool fullRenderable =
|
||||
HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::FramebufferRenderable);
|
||||
const Bool caveatRenderable =
|
||||
HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::FramebufferRenderable);
|
||||
return (!fullCreatable && caveatCreatable) || (!fullRenderable && caveatRenderable);
|
||||
}
|
||||
|
||||
if (HasAnyCachedFormatCapability(internalFormat, false, FormatCapability::Creatable)) {
|
||||
return false;
|
||||
}
|
||||
return HasAnyCachedFormatCapability(internalFormat, true, FormatCapability::Creatable);
|
||||
}
|
||||
|
||||
void GenerateFormatInfo(TextureInternalFormat internalFormat,
|
||||
SizeT targetIndex,
|
||||
GLenum* outInternalFormat,
|
||||
GLenum* outFormat,
|
||||
GLenum* outType) {
|
||||
using namespace MobileGL::MG_Util::TextureFormatProcessor;
|
||||
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
||||
Flags<PixelFormatNormalizeOptionBit> options;
|
||||
if (!pActiveBackendObject || ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
||||
options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat);
|
||||
}
|
||||
NormalizePixelFormat(requestedInternalFormat, options, outInternalFormat, outFormat, outType);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace TextureImpl {
|
||||
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType) {
|
||||
GLenum* outFormat, GLenum* outType, TextureTarget target) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
using namespace MobileGL::MG_Util::TextureFormatProcessor;
|
||||
auto options = (g_GLESCapabilities.SupportsNorm16Texture) ? PixelFormatNormalizeOptionBit::None
|
||||
: PixelFormatNormalizeOptionBit::NoNorm16;
|
||||
NormalizePixelFormat(MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat), options,
|
||||
outInternalFormat, outFormat, outType);
|
||||
const SizeT targetIndex =
|
||||
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||
GenerateFormatInfo(internalFormat, targetIndex, outInternalFormat, outFormat, outType);
|
||||
}
|
||||
|
||||
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
GenerateFormatInfo(internalFormat, GetRenderbufferFormatCapabilityTargetIndex(), outInternalFormat,
|
||||
outFormat, outType);
|
||||
}
|
||||
|
||||
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target) {
|
||||
const SizeT targetIndex =
|
||||
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||
return ShouldUseCaveatFormat(internalFormat, targetIndex);
|
||||
}
|
||||
|
||||
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat) {
|
||||
return ShouldUseCaveatFormat(internalFormat, GetRenderbufferFormatCapabilityTargetIndex());
|
||||
}
|
||||
} // namespace TextureImpl
|
||||
|
||||
namespace FramebufferImpl {} // namespace FramebufferImpl
|
||||
|
||||
namespace PrgramImpl {
|
||||
String ProcessOutColorLocations(const String& glslCode) {
|
||||
#ifdef TRACY_ENABLE
|
||||
@@ -108,14 +218,128 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return result;
|
||||
}
|
||||
|
||||
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||
Uint32 unormOutputMask) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
const Uint32 outputMask = snormOutputMask | unormOutputMask;
|
||||
if (shaderType != GL_FRAGMENT_SHADER || outputMask == 0) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
const std::regex outputPattern(
|
||||
R"(layout\s*\(\s*location\s*=\s*([0-9]+)\s*\)\s*out\s+(?:(?:lowp|mediump|highp)\s+)?vec4\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
|
||||
std::sregex_iterator outputIt(glslCode.begin(), glslCode.end(), outputPattern);
|
||||
std::sregex_iterator outputEnd;
|
||||
struct OutputClamp {
|
||||
String Name;
|
||||
Bool Signed;
|
||||
};
|
||||
Vector<OutputClamp> outputClamps;
|
||||
for (; outputIt != outputEnd; ++outputIt) {
|
||||
const Uint location = static_cast<Uint>(std::stoul((*outputIt)[1].str()));
|
||||
if (location < 32 && (outputMask & (1u << location))) {
|
||||
outputClamps.push_back({(*outputIt)[2].str(), static_cast<Bool>(snormOutputMask & (1u << location))});
|
||||
}
|
||||
}
|
||||
if (outputClamps.empty()) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
const std::regex mainPattern(R"(void\s+main\s*\([^)]*\)\s*\{)");
|
||||
std::smatch mainMatch;
|
||||
if (!std::regex_search(glslCode, mainMatch, mainPattern)) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
SizeT bracePos = static_cast<SizeT>(mainMatch.position(0) + mainMatch.length(0) - 1);
|
||||
Int depth = 0;
|
||||
for (SizeT pos = bracePos; pos < glslCode.size(); ++pos) {
|
||||
if (glslCode[pos] == '{') {
|
||||
++depth;
|
||||
} else if (glslCode[pos] == '}') {
|
||||
--depth;
|
||||
if (depth == 0) {
|
||||
String clampLine;
|
||||
for (const OutputClamp& outputClamp : outputClamps) {
|
||||
const String minValue = outputClamp.Signed ? "-1.0" : "0.0";
|
||||
clampLine += "\n " + outputClamp.Name + " = clamp(" + outputClamp.Name +
|
||||
", vec4(" + minValue + "), vec4(1.0));";
|
||||
}
|
||||
clampLine += "\n";
|
||||
glslCode.insert(pos, clampLine);
|
||||
return glslCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
String result = glslCode;
|
||||
const String integerType = R"((?:(?:lowp|mediump|highp)\s+)?(?:u?int|[iu]vec[234])\b)";
|
||||
|
||||
auto addFlatQualifier = [&result, &integerType](const String& qualifier) {
|
||||
const std::regex pattern("(layout\\s*\\([^)]*\\)\\s*)(?!(?:flat|smooth|noperspective)\\s)(" +
|
||||
qualifier + "\\s+" + integerType + ")");
|
||||
result = std::regex_replace(result, pattern, "$1flat $2");
|
||||
};
|
||||
|
||||
switch (shaderType) {
|
||||
case GL_VERTEX_SHADER:
|
||||
addFlatQualifier("out");
|
||||
break;
|
||||
case GL_GEOMETRY_SHADER:
|
||||
addFlatQualifier("in");
|
||||
addFlatQualifier("out");
|
||||
break;
|
||||
case GL_FRAGMENT_SHADER:
|
||||
addFlatQualifier("in");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
String RemoveLayoutBinding(const String& glslCode) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
// Sampler and uniform-block bindings are re-established at draw time through the
|
||||
// API, so their layout qualifiers are stripped (they may exceed ES limits). SSBO
|
||||
// blocks and image uniforms are different: ES has no glShaderStorageBlockBinding,
|
||||
// and image units cannot be set with glUniform1i, so for those declarations the
|
||||
// binding qualifier is the only binding mechanism and must be preserved.
|
||||
static std::regex bindingRegex(R"(layout\s*\(\s*binding\s*=\s*\d+\s*\)\s*)");
|
||||
String result = std::regex_replace(glslCode, bindingRegex, "");
|
||||
static std::regex bindingRegex2(R"(layout\s*\(\s*binding\s*=\s*\d+\s*,)");
|
||||
result = std::regex_replace(result, bindingRegex2, "layout(");
|
||||
static std::regex keepBindingRegex(R"(\b(buffer|[iu]?image[A-Za-z0-9]*)\b)");
|
||||
|
||||
String result;
|
||||
result.reserve(glslCode.size());
|
||||
SizeT lineStart = 0;
|
||||
while (lineStart <= glslCode.size()) {
|
||||
SizeT lineEnd = glslCode.find('\n', lineStart);
|
||||
const Bool lastLine = lineEnd == String::npos;
|
||||
String line = glslCode.substr(lineStart, lastLine ? String::npos : lineEnd - lineStart);
|
||||
|
||||
if (!std::regex_search(line, keepBindingRegex)) {
|
||||
line = std::regex_replace(line, bindingRegex, "");
|
||||
line = std::regex_replace(line, bindingRegex2, "layout(");
|
||||
}
|
||||
|
||||
result += line;
|
||||
if (lastLine) {
|
||||
break;
|
||||
}
|
||||
result += '\n';
|
||||
lineStart = lineEnd + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace PrgramImpl
|
||||
@@ -125,7 +349,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
while (GLenum err = g_GLESFuncs.glGetError() != GL_NO_ERROR) {
|
||||
for (GLenum err = g_GLESFuncs.glGetError(); err != GL_NO_ERROR; err = g_GLESFuncs.glGetError()) {
|
||||
MGLOG_E("-> GLES Error: %s", MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
}
|
||||
}
|
||||
@@ -166,7 +390,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return GL_UNIFORM_BUFFER_BINDING;
|
||||
|
||||
case GL_FRAMEBUFFER:
|
||||
return GL_FRAMEBUFFER_BINDING;
|
||||
case GL_DRAW_FRAMEBUFFER:
|
||||
return GL_DRAW_FRAMEBUFFER_BINDING;
|
||||
case GL_READ_FRAMEBUFFER:
|
||||
@@ -176,7 +399,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return GL_RENDERBUFFER_BINDING;
|
||||
|
||||
case GL_VERTEX_ARRAY:
|
||||
return GL_VERTEX_ARRAY_BINDING;
|
||||
case GL_VERTEX_ARRAY_BINDING:
|
||||
return GL_VERTEX_ARRAY_BINDING;
|
||||
|
||||
@@ -230,4 +452,317 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
} // namespace Utils
|
||||
|
||||
// ---- Client-format readback conversion helpers -------------------------------------------------
|
||||
// ReadPixels/GetTexImage read a guaranteed wide RGBA(_INTEGER) layout from the ES driver and repack
|
||||
// it on the CPU into the client's (format, type) layout. Everything here is pure byte shuffling so
|
||||
// unit tests can assert the exact packed words; field positions follow GL 3.3 table 3.6 and mirror
|
||||
// the GL CTS packed_pixels oracle (glcPackedPixelsTests.cpp pack_UNSIGNED_* helpers).
|
||||
namespace ReadbackImpl {
|
||||
using MG_Util::DecodeHalfBitsToFloat;
|
||||
using MG_Util::EncodeFloatToHalfBits;
|
||||
|
||||
Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping) {
|
||||
switch (format) {
|
||||
case GL_RED: outMapping = {{0, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_RED_INTEGER: outMapping = {{0, 0, 0, 0}, 1, true}; return true;
|
||||
// Desktop-GL single-channel client formats (GL CTS packed_pixels rgba8_format_green/blue):
|
||||
// the destination holds one component sourced from the named channel of the wide RGBA read.
|
||||
// GL_ALPHA is mapped here from the raw enum because the state layer folds it into Red for the
|
||||
// legacy alpha-texture upload hack.
|
||||
case GL_GREEN: outMapping = {{1, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_GREEN_INTEGER: outMapping = {{1, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_BLUE: outMapping = {{2, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_BLUE_INTEGER: outMapping = {{2, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_ALPHA: outMapping = {{3, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_ALPHA_INTEGER: outMapping = {{3, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_RG: outMapping = {{0, 1, 0, 0}, 2, false}; return true;
|
||||
case GL_RG_INTEGER: outMapping = {{0, 1, 0, 0}, 2, true}; return true;
|
||||
case GL_RGB: outMapping = {{0, 1, 2, 0}, 3, false}; return true;
|
||||
case GL_RGB_INTEGER: outMapping = {{0, 1, 2, 0}, 3, true}; return true;
|
||||
case GL_BGR: outMapping = {{2, 1, 0, 0}, 3, false}; return true;
|
||||
case GL_BGR_INTEGER: outMapping = {{2, 1, 0, 0}, 3, true}; return true;
|
||||
case GL_RGBA: outMapping = {{0, 1, 2, 3}, 4, false}; return true;
|
||||
case GL_RGBA_INTEGER: outMapping = {{0, 1, 2, 3}, 4, true}; return true;
|
||||
case GL_BGRA: outMapping = {{2, 1, 0, 3}, 4, false}; return true;
|
||||
case GL_BGRA_INTEGER: outMapping = {{2, 1, 0, 3}, 4, true}; return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Bool GetPackedReadbackLayout(GLenum type, PackedReadbackLayout& out) {
|
||||
switch (type) {
|
||||
// Non-REV types pack the first format component starting at the most significant bit,
|
||||
// *_REV types starting at the least significant bit (GL CTS pack_UNSIGNED_SHORT_5_6_5:
|
||||
// R bits 15-11; pack_UNSIGNED_SHORT_1_5_5_5_REV: R bits 4-0, A bit 15).
|
||||
case GL_UNSIGNED_BYTE_3_3_2: out = {3, {3, 3, 2, 0}, {5, 2, 0, 0}, 1, false}; return true;
|
||||
case GL_UNSIGNED_BYTE_2_3_3_REV: out = {3, {3, 3, 2, 0}, {0, 3, 6, 0}, 1, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_5_6_5: out = {3, {5, 6, 5, 0}, {11, 5, 0, 0}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_5_6_5_REV: out = {3, {5, 6, 5, 0}, {0, 5, 11, 0}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_4_4_4_4: out = {4, {4, 4, 4, 4}, {12, 8, 4, 0}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_4_4_4_4_REV: out = {4, {4, 4, 4, 4}, {0, 4, 8, 12}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_5_5_5_1: out = {4, {5, 5, 5, 1}, {11, 6, 1, 0}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_1_5_5_5_REV: out = {4, {5, 5, 5, 1}, {0, 5, 10, 15}, 2, false}; return true;
|
||||
case GL_UNSIGNED_INT_8_8_8_8: out = {4, {8, 8, 8, 8}, {24, 16, 8, 0}, 4, false}; return true;
|
||||
case GL_UNSIGNED_INT_8_8_8_8_REV: out = {4, {8, 8, 8, 8}, {0, 8, 16, 24}, 4, false}; return true;
|
||||
case GL_UNSIGNED_INT_10_10_10_2: out = {4, {10, 10, 10, 2}, {22, 12, 2, 0}, 4, false}; return true;
|
||||
case GL_UNSIGNED_INT_2_10_10_10_REV: out = {4, {10, 10, 10, 2}, {0, 10, 20, 30}, 4, false}; return true;
|
||||
// Packed-float RGB types: fields hold unsigned small floats; 5_9_9_9_REV's shared 5-bit
|
||||
// exponent (bits 31-27) is emitted by EncodeSharedExponentRGB9E5, not a component field.
|
||||
case GL_UNSIGNED_INT_10F_11F_11F_REV: out = {3, {11, 11, 10, 0}, {0, 11, 22, 0}, 4, true}; return true;
|
||||
case GL_UNSIGNED_INT_5_9_9_9_REV: out = {3, {9, 9, 9, 0}, {0, 9, 18, 0}, 4, true}; return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SizeT GetReadbackComponentSize(GLenum type) {
|
||||
PackedReadbackLayout packedLayout{};
|
||||
if (GetPackedReadbackLayout(type, packedLayout)) {
|
||||
return packedLayout.byteSize;
|
||||
}
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_BYTE:
|
||||
return 1;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_SHORT:
|
||||
case GL_HALF_FLOAT:
|
||||
return 2;
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_INT:
|
||||
case GL_FLOAT:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SizeT GetReadbackDstPixelSize(const ReadbackChannelMapping& mapping, GLenum type) {
|
||||
PackedReadbackLayout packedLayout{};
|
||||
if (GetPackedReadbackLayout(type, packedLayout)) {
|
||||
if (packedLayout.fieldCount != mapping.channelCount) {
|
||||
return 0; // 3-field packed types pair with 3-component formats only, 4 with 4
|
||||
}
|
||||
if (mapping.isInteger && packedLayout.isFloatPacked) {
|
||||
return 0; // packed-float RGB types never pair with integer formats
|
||||
}
|
||||
return packedLayout.byteSize;
|
||||
}
|
||||
if (mapping.isInteger && (type == GL_FLOAT || type == GL_HALF_FLOAT)) {
|
||||
return 0;
|
||||
}
|
||||
const SizeT componentSize = GetReadbackComponentSize(type);
|
||||
return componentSize == 0 ? 0 : static_cast<SizeT>(mapping.channelCount) * componentSize;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void WritePackedReadbackWord(Uint8* dst, Uint32 word, SizeT byteSize) {
|
||||
switch (byteSize) {
|
||||
case 1: {
|
||||
const auto out = static_cast<Uint8>(word);
|
||||
Memcpy(dst, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
const auto out = static_cast<Uint16>(word);
|
||||
Memcpy(dst, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Memcpy(dst, &word, sizeof(word));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Shared encoders live in MG_Util/Math/SmallFloat.h so the upload conversion
|
||||
// (PixelStoreProcessor) uses byte-identical packing; kept exported here for unit tests.
|
||||
Uint32 EncodeFloatToUnsignedF11(Float value) { return MG_Util::EncodeFloatToUnsignedF11(value); }
|
||||
Uint32 EncodeFloatToUnsignedF10(Float value) { return MG_Util::EncodeFloatToUnsignedF10(value); }
|
||||
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]) { return MG_Util::EncodeSharedExponentRGB9E5(rgb); }
|
||||
|
||||
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
|
||||
const ReadbackChannelMapping& mapping, GLenum type) {
|
||||
PackedReadbackLayout packedLayout{};
|
||||
const Bool isPacked = GetPackedReadbackLayout(type, packedLayout);
|
||||
const SizeT dstComponentSize = GetReadbackComponentSize(type);
|
||||
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
|
||||
const SizeT srcPixelBytes = 4 * GetReadbackComponentSize(wideType);
|
||||
|
||||
for (SizeT col = 0; col < width; ++col) {
|
||||
const Uint8* srcPixel = src + col * srcPixelBytes;
|
||||
Uint8* dstPixel = dst + col * dstPixelBytes;
|
||||
if (mapping.isInteger) {
|
||||
Int64 srcValues[4];
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = wideType == GL_INT
|
||||
? static_cast<Int64>(reinterpret_cast<const Int32*>(srcPixel)[c])
|
||||
: static_cast<Int64>(reinterpret_cast<const Uint32*>(srcPixel)[c]);
|
||||
}
|
||||
if (isPacked) {
|
||||
// Integer sources clamp each component to the unsigned range of its field
|
||||
// (GL 3.3 section 4.3.1 final conversion).
|
||||
Uint32 word = 0;
|
||||
for (Int ch = 0; ch < packedLayout.fieldCount; ++ch) {
|
||||
const Int64 fieldMax = (Int64{1} << packedLayout.width[ch]) - 1;
|
||||
const auto v = static_cast<Uint32>(
|
||||
std::clamp<Int64>(srcValues[mapping.sourceChannel[ch]], 0, fieldMax));
|
||||
word |= v << packedLayout.shift[ch];
|
||||
}
|
||||
WritePackedReadbackWord(dstPixel, word, packedLayout.byteSize);
|
||||
} else {
|
||||
for (Int ch = 0; ch < mapping.channelCount; ++ch) {
|
||||
const Int64 v = srcValues[mapping.sourceChannel[ch]];
|
||||
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
*dstComponent = static_cast<Uint8>(std::clamp<Int64>(v, 0, 255));
|
||||
break;
|
||||
case GL_BYTE: {
|
||||
const auto out = static_cast<Int8>(std::clamp<Int64>(v, -128, 127));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_SHORT: {
|
||||
const auto out = static_cast<Uint16>(std::clamp<Int64>(v, 0, 65535));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_SHORT: {
|
||||
const auto out = static_cast<Int16>(std::clamp<Int64>(v, -32768, 32767));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_INT: {
|
||||
const auto out = static_cast<Uint32>(std::clamp<Int64>(v, 0, 4294967295LL));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_INT: {
|
||||
const auto out =
|
||||
static_cast<Int32>(std::clamp<Int64>(v, -2147483648LL, 2147483647LL));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Float srcValues[4];
|
||||
switch (wideType) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = static_cast<Float>(srcPixel[c]) / 255.0f;
|
||||
}
|
||||
break;
|
||||
case GL_BYTE:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = std::max(
|
||||
static_cast<Float>(reinterpret_cast<const Int8*>(srcPixel)[c]) / 127.0f, -1.0f);
|
||||
}
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] =
|
||||
static_cast<Float>(reinterpret_cast<const Uint16*>(srcPixel)[c]) / 65535.0f;
|
||||
}
|
||||
break;
|
||||
case GL_SHORT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = std::max(
|
||||
static_cast<Float>(reinterpret_cast<const Int16*>(srcPixel)[c]) / 32767.0f, -1.0f);
|
||||
}
|
||||
break;
|
||||
case GL_HALF_FLOAT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = DecodeHalfBitsToFloat(reinterpret_cast<const Uint16*>(srcPixel)[c]);
|
||||
}
|
||||
break;
|
||||
default: // GL_FLOAT
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = reinterpret_cast<const Float*>(srcPixel)[c];
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (isPacked) {
|
||||
Uint32 word = 0;
|
||||
if (packedLayout.isFloatPacked) {
|
||||
const Float fields[3] = {srcValues[mapping.sourceChannel[0]],
|
||||
srcValues[mapping.sourceChannel[1]],
|
||||
srcValues[mapping.sourceChannel[2]]};
|
||||
word = type == GL_UNSIGNED_INT_5_9_9_9_REV
|
||||
? EncodeSharedExponentRGB9E5(fields)
|
||||
: (EncodeFloatToUnsignedF11(fields[0]) << packedLayout.shift[0]) |
|
||||
(EncodeFloatToUnsignedF11(fields[1]) << packedLayout.shift[1]) |
|
||||
(EncodeFloatToUnsignedF10(fields[2]) << packedLayout.shift[2]);
|
||||
} else {
|
||||
// Normalized encode: round(clamp(v, 0, 1) * (2^bits - 1)) into each field.
|
||||
for (Int ch = 0; ch < packedLayout.fieldCount; ++ch) {
|
||||
const auto fieldMax = static_cast<Float>((1u << packedLayout.width[ch]) - 1u);
|
||||
const auto v = static_cast<Uint32>(std::llround(
|
||||
std::clamp(srcValues[mapping.sourceChannel[ch]], 0.0f, 1.0f) * fieldMax));
|
||||
word |= v << packedLayout.shift[ch];
|
||||
}
|
||||
}
|
||||
WritePackedReadbackWord(dstPixel, word, packedLayout.byteSize);
|
||||
} else {
|
||||
for (Int ch = 0; ch < mapping.channelCount; ++ch) {
|
||||
const Float v = srcValues[mapping.sourceChannel[ch]];
|
||||
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
*dstComponent =
|
||||
static_cast<Uint8>(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0));
|
||||
break;
|
||||
case GL_BYTE: {
|
||||
const auto out =
|
||||
static_cast<Int8>(std::llround(std::clamp(v, -1.0f, 1.0f) * 127.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_SHORT: {
|
||||
const auto out =
|
||||
static_cast<Uint16>(std::llround(std::clamp(v, 0.0f, 1.0f) * 65535.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_SHORT: {
|
||||
const auto out =
|
||||
static_cast<Int16>(std::llround(std::clamp(v, -1.0f, 1.0f) * 32767.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_INT: {
|
||||
const auto out = static_cast<Uint32>(
|
||||
std::llround(static_cast<Double>(std::clamp(v, 0.0f, 1.0f)) * 4294967295.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_INT: {
|
||||
const auto out = static_cast<Int32>(
|
||||
std::llround(static_cast<Double>(std::clamp(v, -1.0f, 1.0f)) * 2147483647.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_FLOAT:
|
||||
Memcpy(dstComponent, &v, sizeof(v));
|
||||
break;
|
||||
case GL_HALF_FLOAT: {
|
||||
const Uint16 out = EncodeFloatToHalfBits(v);
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace ReadbackImpl
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
|
||||
@@ -14,15 +14,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
namespace DebugImpl {
|
||||
class ErrorLopper {
|
||||
public:
|
||||
void Loop(std::function<void(GLenum)>);
|
||||
void Clear();
|
||||
static void Loop(const std::function<void(GLenum)>&);
|
||||
static void Clear();
|
||||
ErrorLopper();
|
||||
~ErrorLopper();
|
||||
};
|
||||
|
||||
class OpenGLScopeMarker {
|
||||
public:
|
||||
explicit OpenGLScopeMarker(String scopeName);
|
||||
explicit OpenGLScopeMarker(const String& scopeName);
|
||||
~OpenGLScopeMarker();
|
||||
};
|
||||
} // namespace DebugImpl
|
||||
@@ -35,14 +35,67 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
namespace TextureImpl {
|
||||
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType);
|
||||
GLenum* outFormat, GLenum* outType,
|
||||
TextureTarget target = TextureTarget::Unknown);
|
||||
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType);
|
||||
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target);
|
||||
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat);
|
||||
} // namespace TextureImpl
|
||||
|
||||
namespace FramebufferImpl {} // namespace FramebufferImpl
|
||||
|
||||
// Pure CPU helpers of the client-format readback conversion (ReadPixels/GetTexImage repack a wide
|
||||
// RGBA(_INTEGER) read into the caller's (format, type) layout). Kept context-free so unit tests can
|
||||
// exercise the exact packing the GL CTS packed_pixels oracle compares against.
|
||||
namespace ReadbackImpl {
|
||||
struct ReadbackChannelMapping {
|
||||
Int sourceChannel[4]; // RGBA source channel feeding each destination component
|
||||
Int channelCount; // destination component count
|
||||
Bool isInteger;
|
||||
};
|
||||
Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping);
|
||||
|
||||
// Byte size of one destination component of `type`; packed types report the packed word size.
|
||||
// 0 = type not supported by the conversion path.
|
||||
SizeT GetReadbackComponentSize(GLenum type);
|
||||
|
||||
// Bit-field layout of a GL packed pixel type. width/shift are indexed in the client format's
|
||||
// component order (matching ReadbackChannelMapping); shift is the LSB position of the field in
|
||||
// the packed word: non-REV types pack the first component from the MSB, *_REV types from the
|
||||
// LSB (GL 3.3 table 3.6; field positions mirror the GL CTS glcPackedPixelsTests pack_* oracle).
|
||||
struct PackedReadbackLayout {
|
||||
Int fieldCount; // format components stored in the packed word
|
||||
Int width[4]; // bit width of each component's field
|
||||
Int shift[4]; // LSB bit position of each component's field
|
||||
SizeT byteSize; // packed word size in bytes (1, 2 or 4)
|
||||
Bool isFloatPacked; // 10F_11F_11F_REV / 5_9_9_9_REV: fields hold unsigned small floats
|
||||
};
|
||||
Bool GetPackedReadbackLayout(GLenum type, PackedReadbackLayout& out);
|
||||
|
||||
// Unsigned small-float encoders (EXT_packed_float / EXT_texture_shared_exponent semantics).
|
||||
Uint32 EncodeFloatToUnsignedF11(Float value);
|
||||
Uint32 EncodeFloatToUnsignedF10(Float value);
|
||||
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]);
|
||||
|
||||
// Destination bytes per pixel for a (format mapping, type) readback pair; 0 when the pair is
|
||||
// not convertible (unknown type, packed field count != format component count, floating-point
|
||||
// or packed-float type with an integer format).
|
||||
SizeT GetReadbackDstPixelSize(const ReadbackChannelMapping& mapping, GLenum type);
|
||||
|
||||
// Repacks one row of wide RGBA(_INTEGER) texels (4 components of wideType each) into the
|
||||
// client's (format, type) layout. src holds width * 4 * GetReadbackComponentSize(wideType)
|
||||
// bytes, dst receives width * GetReadbackDstPixelSize(mapping, type) bytes.
|
||||
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
|
||||
const ReadbackChannelMapping& mapping, GLenum type);
|
||||
} // namespace ReadbackImpl
|
||||
|
||||
namespace PrgramImpl {
|
||||
String ProcessOutColorLocations(const String& glslCode);
|
||||
String ForceSupporterOutput(const String& glslCode);
|
||||
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||
Uint32 unormOutputMask);
|
||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType);
|
||||
String RemoveLayoutBinding(const String& glslCode);
|
||||
} // namespace PrgramImpl
|
||||
|
||||
|
||||
@@ -9,16 +9,320 @@
|
||||
#include "BackendObject_DirectVulkan.h"
|
||||
#include "MG_Backend/BackendObject.h"
|
||||
#include "DirectVulkan.h"
|
||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||
#include "MG_State/GLState/TextureState/TextureState.h"
|
||||
#include "MG_Util/Classifiers/TextureEnumClassifier.h"
|
||||
#include "MG_Util/Converters/MGToGL/TextureEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
||||
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
Bool IsR11G11B10FFallbackEnabled() {
|
||||
return MG_Config::Features.MagmaR11G11B10FFallback;
|
||||
}
|
||||
|
||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
(void)dpy;
|
||||
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
Bool IsFormatIndexValid(TextureInternalFormat format) {
|
||||
return format != TextureInternalFormat::Unknown && static_cast<Int>(format) >= 0 &&
|
||||
static_cast<SizeT>(format) < kFormatCapabilityFormatCount;
|
||||
}
|
||||
|
||||
Bool IsLayeredTarget(TextureTarget target) {
|
||||
return target == TextureTarget::Texture3D || target == TextureTarget::Texture1DArray ||
|
||||
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMap ||
|
||||
target == TextureTarget::TextureCubeMapArray ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
Bool IsMultisampleTarget(TextureTarget target) {
|
||||
return target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
Bool IsTextureBufferTarget(TextureTarget target) {
|
||||
return target == TextureTarget::TextureBuffer;
|
||||
}
|
||||
|
||||
Bool IsIntegerInternalFormat(TextureInternalFormat format) {
|
||||
const GLenum glFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format);
|
||||
GLenum normalizedInternalFormat = glFormat;
|
||||
GLenum imageFormat = GL_RGBA;
|
||||
GLenum imageType = GL_UNSIGNED_BYTE;
|
||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||
glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType);
|
||||
return imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || imageFormat == GL_RGB_INTEGER ||
|
||||
imageFormat == GL_RGBA_INTEGER;
|
||||
}
|
||||
|
||||
FormatCapabilityFlags GetAttachmentCaps(TextureInternalFormat format) {
|
||||
FormatCapabilityFlags caps = FormatCapability::FramebufferRenderable;
|
||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(format);
|
||||
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(format);
|
||||
if (!isDepth && !isStencil) {
|
||||
caps |= FormatCapability::ColorAttachment;
|
||||
}
|
||||
if (isDepth) {
|
||||
caps |= FormatCapability::DepthAttachment;
|
||||
}
|
||||
if (isStencil) {
|
||||
caps |= FormatCapability::StencilAttachment;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
FormatCapabilityFlags BuildVulkanCaps(TextureInternalFormat logicalFormat,
|
||||
TextureTarget target,
|
||||
VkFormatFeatureFlags features) {
|
||||
FormatCapabilityFlags caps;
|
||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
|
||||
const Bool isInteger = IsIntegerInternalFormat(logicalFormat);
|
||||
|
||||
if (IsTextureBufferTarget(target)) {
|
||||
if ((features & VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) != 0) {
|
||||
caps |= FormatCapability::Creatable;
|
||||
caps |= FormatCapability::Sampled;
|
||||
caps |= FormatCapability::TextureBuffer;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
const Bool sampled = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
|
||||
const Bool linearFilter = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
|
||||
const Bool colorRenderable = (features & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
|
||||
const Bool depthStencilRenderable =
|
||||
(features & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
|
||||
const Bool renderable = (isDepth || isStencil) ? depthStencilRenderable : colorRenderable;
|
||||
|
||||
if (sampled || renderable) {
|
||||
caps |= FormatCapability::Creatable;
|
||||
}
|
||||
if (sampled) {
|
||||
caps |= FormatCapability::Sampled;
|
||||
if (linearFilter && !isInteger && !isStencil) {
|
||||
caps |= FormatCapability::LinearFilter;
|
||||
}
|
||||
if (!isStencil && (features & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0 &&
|
||||
(features & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0) {
|
||||
caps |= FormatCapability::GenerateMipmap;
|
||||
}
|
||||
if (!isInteger && !isDepth && !isStencil) {
|
||||
caps |= FormatCapability::TextureGather;
|
||||
}
|
||||
if (isDepth && !isStencil) {
|
||||
caps |= FormatCapability::TextureShadow;
|
||||
}
|
||||
}
|
||||
if (renderable) {
|
||||
caps |= GetAttachmentCaps(logicalFormat);
|
||||
if (IsLayeredTarget(target)) {
|
||||
caps |= FormatCapability::FramebufferLayered;
|
||||
}
|
||||
}
|
||||
if (IsMultisampleTarget(target)) {
|
||||
caps |= FormatCapability::MultisampleTexture;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
Optional<TextureInternalFormat> ResolveVulkanFallbackLogicalFormat(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::RGB:
|
||||
case TextureInternalFormat::RGB8:
|
||||
return TextureInternalFormat::RGBA8;
|
||||
case TextureInternalFormat::SRGB8:
|
||||
return TextureInternalFormat::SRGB8Alpha8;
|
||||
case TextureInternalFormat::RGB8Snorm:
|
||||
return TextureInternalFormat::RGBA8Snorm;
|
||||
case TextureInternalFormat::RGB16:
|
||||
return TextureInternalFormat::RGBA16;
|
||||
case TextureInternalFormat::RGB16Snorm:
|
||||
return TextureInternalFormat::RGBA16Snorm;
|
||||
case TextureInternalFormat::RGB16F:
|
||||
return TextureInternalFormat::RGBA16F;
|
||||
case TextureInternalFormat::R11FG11FB10F:
|
||||
if (IsR11G11B10FFallbackEnabled()) {
|
||||
return TextureInternalFormat::RGBA16F;
|
||||
}
|
||||
return Nullopt;
|
||||
case TextureInternalFormat::RGB32F:
|
||||
return TextureInternalFormat::RGBA32F;
|
||||
case TextureInternalFormat::RGB8I:
|
||||
return TextureInternalFormat::RGBA8I;
|
||||
case TextureInternalFormat::RGB8UI:
|
||||
return TextureInternalFormat::RGBA8UI;
|
||||
case TextureInternalFormat::RGB16I:
|
||||
return TextureInternalFormat::RGBA16I;
|
||||
case TextureInternalFormat::RGB16UI:
|
||||
return TextureInternalFormat::RGBA16UI;
|
||||
case TextureInternalFormat::RGB32I:
|
||||
return TextureInternalFormat::RGBA32I;
|
||||
case TextureInternalFormat::RGB32UI:
|
||||
return TextureInternalFormat::RGBA32UI;
|
||||
default:
|
||||
return Nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<VkFormat> ResolveVulkanFallbackFormat(TextureInternalFormat format) {
|
||||
const Optional<TextureInternalFormat> fallbackLogicalFormat = ResolveVulkanFallbackLogicalFormat(format);
|
||||
if (!fallbackLogicalFormat) {
|
||||
return Nullopt;
|
||||
}
|
||||
return MG_Util::ConvertTextureInternalFormatToVkEnum(*fallbackLogicalFormat);
|
||||
}
|
||||
|
||||
Bool HasNewCaveatFormatCaps(FormatCapabilityFlags nativeCaps, FormatCapabilityFlags fallbackCaps) {
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
if (HasFormatCapability(fallbackCaps, capability) &&
|
||||
!HasFormatCapability(nativeCaps, capability)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat,
|
||||
SizeT targetIndex,
|
||||
TextureInternalFormat fallbackFormat) {
|
||||
MGLOG_D("Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s",
|
||||
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
|
||||
MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str());
|
||||
}
|
||||
|
||||
Vector<Int> BuildSampleCounts(Int maxSamples) {
|
||||
Vector<Int> counts;
|
||||
for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) {
|
||||
counts.push_back(samples);
|
||||
}
|
||||
counts.push_back(1);
|
||||
return counts;
|
||||
}
|
||||
|
||||
void PopulateFormatCapabilitiesImpl(VkPhysicalDevice physicalDevice,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||
const MG_External::VulkanCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache) {
|
||||
cache.Clear();
|
||||
if (physicalDevice == VK_NULL_HANDLE || getFormatProperties == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const auto logicalFormat = static_cast<TextureInternalFormat>(formatIndex);
|
||||
if (!IsFormatIndexValid(logicalFormat)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VkFormat nativeFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(logicalFormat);
|
||||
const Optional<TextureInternalFormat> fallbackLogicalFormat =
|
||||
ResolveVulkanFallbackLogicalFormat(logicalFormat);
|
||||
VkFormat fallbackFormat = ResolveVulkanFallbackFormat(logicalFormat).value_or(VK_FORMAT_UNDEFINED);
|
||||
|
||||
VkFormatProperties nativeProperties{};
|
||||
if (nativeFormat != VK_FORMAT_UNDEFINED) {
|
||||
getFormatProperties(physicalDevice, nativeFormat, &nativeProperties);
|
||||
}
|
||||
|
||||
VkFormatProperties fallbackProperties{};
|
||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||
getFormatProperties(physicalDevice, fallbackFormat, &fallbackProperties);
|
||||
}
|
||||
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
||||
const auto target = static_cast<TextureTarget>(targetIndex);
|
||||
const VkFormatFeatureFlags nativeFeatures =
|
||||
IsTextureBufferTarget(target) ? nativeProperties.bufferFeatures
|
||||
: nativeProperties.optimalTilingFeatures;
|
||||
FormatCapabilityFlags nativeCaps = BuildVulkanCaps(logicalFormat, target, nativeFeatures);
|
||||
cache.FullCaps[targetIndex][formatIndex] |= nativeCaps;
|
||||
|
||||
const VkFormatFeatureFlags fallbackFeatures =
|
||||
IsTextureBufferTarget(target) ? fallbackProperties.bufferFeatures
|
||||
: fallbackProperties.optimalTilingFeatures;
|
||||
FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures);
|
||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||
cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps;
|
||||
if (fallbackLogicalFormat && HasNewCaveatFormatCaps(nativeCaps, fallbackCaps)) {
|
||||
LogVulkanFormatCaveat(logicalFormat, targetIndex, *fallbackLogicalFormat);
|
||||
}
|
||||
}
|
||||
|
||||
if (HasFormatCapability(nativeCaps | fallbackCaps, FormatCapability::MultisampleTexture)) {
|
||||
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
|
||||
const Bool isInteger = IsIntegerInternalFormat(logicalFormat);
|
||||
Int maxSamples = capabilities.MaxColorTextureSamples;
|
||||
if (isDepth || isStencil) {
|
||||
maxSamples = capabilities.MaxDepthTextureSamples;
|
||||
} else if (isInteger) {
|
||||
maxSamples = capabilities.MaxIntegerSamples;
|
||||
}
|
||||
cache.SampleCounts[targetIndex][formatIndex] = BuildSampleCounts(maxSamples);
|
||||
}
|
||||
}
|
||||
|
||||
const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex();
|
||||
FormatCapabilityFlags renderbufferCaps =
|
||||
BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D, nativeProperties.optimalTilingFeatures);
|
||||
renderbufferCaps &= FormatCapability::Creatable;
|
||||
if ((nativeProperties.optimalTilingFeatures &
|
||||
(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0) {
|
||||
renderbufferCaps |= GetAttachmentCaps(logicalFormat);
|
||||
renderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
||||
}
|
||||
cache.FullCaps[renderbufferTargetIndex][formatIndex] |= renderbufferCaps;
|
||||
|
||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||
FormatCapabilityFlags fallbackRenderbufferCaps =
|
||||
BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D,
|
||||
fallbackProperties.optimalTilingFeatures);
|
||||
fallbackRenderbufferCaps &= FormatCapability::Creatable;
|
||||
if ((fallbackProperties.optimalTilingFeatures &
|
||||
(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) !=
|
||||
0) {
|
||||
fallbackRenderbufferCaps |= GetAttachmentCaps(logicalFormat);
|
||||
fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
||||
}
|
||||
cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps;
|
||||
if (fallbackLogicalFormat &&
|
||||
HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) {
|
||||
LogVulkanFormatCaveat(logicalFormat, renderbufferTargetIndex, *fallbackLogicalFormat);
|
||||
}
|
||||
}
|
||||
|
||||
const FormatCapabilityFlags rbCaps = cache.FullCaps[renderbufferTargetIndex][formatIndex] |
|
||||
cache.CaveatCaps[renderbufferTargetIndex][formatIndex];
|
||||
if (HasFormatCapability(rbCaps, FormatCapability::MultisampleRenderbuffer)) {
|
||||
cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
|
||||
BuildSampleCounts(capabilities.MaxFramebufferSamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||
const MG_External::VulkanCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache) {
|
||||
PopulateFormatCapabilitiesImpl(physicalDevice, getFormatProperties, capabilities, cache);
|
||||
}
|
||||
|
||||
BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default;
|
||||
|
||||
BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
||||
if (!m_windowHandle.Handle) {
|
||||
MGLOG_E("Cannot initialize DirectVulkan window surface: native window handle is null");
|
||||
@@ -27,7 +331,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
auto nativeWindow = reinterpret_cast<NativeWindowType>(m_windowHandle.Handle);
|
||||
|
||||
// Any renderer instance this assignment replaces is destroyed here;
|
||||
// fence/timer-query handles stamped with the old generation go stale.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer = MakeUnique<MG_Backend::DirectVulkan::VulkanRenderer>(nativeWindow);
|
||||
MOBILEGL_ASSERT(pVulkanRenderer != nullptr, "InitWindowSurface: VulkanRenderer creation failed");
|
||||
pVulkanRenderer->Initialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::InitPbufferSurface(EGLint width, EGLint height) {
|
||||
VulkanRendererConfig config;
|
||||
config.SurfaceWidth = static_cast<Uint32>(std::max<EGLint>(width, 1));
|
||||
config.SurfaceHeight = static_cast<Uint32>(std::max<EGLint>(height, 1));
|
||||
// Any renderer instance this assignment replaces is destroyed here;
|
||||
// fence/timer-query handles stamped with the old generation go stale.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer = MakeUnique<MG_Backend::DirectVulkan::VulkanRenderer>(NativeWindowType{}, config);
|
||||
MOBILEGL_ASSERT(pVulkanRenderer != nullptr, "InitPbufferSurface: VulkanRenderer creation failed");
|
||||
pVulkanRenderer->Initialize();
|
||||
return true;
|
||||
}
|
||||
@@ -46,8 +367,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return false;
|
||||
}
|
||||
|
||||
MG_Util::BackendLoader::FillInVulkanCapabilities(m_vulkanCaps, pVulkanRenderer->GetPhysicalDevice().properties);
|
||||
const auto& physicalDevice = pVulkanRenderer->GetPhysicalDevice();
|
||||
if (!MG_Util::BackendLoader::QueryVulkanCapabilities(m_vulkanCaps, pVulkanRenderer->GetInstance(),
|
||||
physicalDevice.handle)) {
|
||||
MGLOG_W("DirectVulkan: failed to query extended Vulkan capabilities, using basic properties");
|
||||
MG_Util::BackendLoader::FillInVulkanCapabilities(m_vulkanCaps, physicalDevice.properties);
|
||||
}
|
||||
UpdateDynamicBackendParameters();
|
||||
UpdateAdvertisedExtensions();
|
||||
PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
|
||||
MutableFormatCapabilities());
|
||||
PrintFormatCapabilities(GetFormatCapabilities());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -59,40 +389,48 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return BackendObject::InitializeEGLDisplay(dpy, major, minor);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::CreateEGLWindowSurface(const WindowHandle& handle) {
|
||||
Bool BackendObject_DirectVulkan::CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
if (handle.Backend != WindowBackend::Android || !handle.Handle) {
|
||||
MGLOG_E("DirectVulkan backend only supports Android native windows");
|
||||
if (!handle.Handle || (handle.Backend != WindowBackend::Android &&
|
||||
handle.Backend != WindowBackend::X11 &&
|
||||
handle.Backend != WindowBackend::MetalLayer)) {
|
||||
MGLOG_E("DirectVulkan backend only supports Android, X11, and CAMetalLayer native windows");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Bool sameHandle =
|
||||
m_eglWindowSurfaceInitialized && m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle;
|
||||
if (sameHandle) {
|
||||
return true;
|
||||
}
|
||||
return RegisterEGLWindowSurface(surface, handle);
|
||||
}
|
||||
|
||||
if (m_eglWindowSurfaceInitialized || pVulkanRenderer) {
|
||||
pVulkanRenderer.reset();
|
||||
ResetEGLRuntimeState();
|
||||
Bool BackendObject_DirectVulkan::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!BackendObject::ResizeEGLWindowSurface(surface, width, height)) {
|
||||
return false;
|
||||
}
|
||||
if (pVulkanRenderer && m_eglSurface == surface) {
|
||||
pVulkanRenderer->RequestSwapchainResize(width, height);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return BackendObject::CreateEGLWindowSurface(handle);
|
||||
Bool BackendObject_DirectVulkan::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_initialized) {
|
||||
MGLOG_E("DirectVulkan backend not initialized");
|
||||
return false;
|
||||
}
|
||||
return RegisterEGLPbufferSurface(surface, width, height);
|
||||
}
|
||||
|
||||
Bool BackendObject_DirectVulkan::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
if (!pVulkanRenderer) {
|
||||
MGLOG_E("DirectVulkan renderer is not initialized");
|
||||
return false;
|
||||
}
|
||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
|
||||
@@ -105,33 +443,95 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return BackendObject::SwapEGLBuffers(dpy, draw);
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::ReleaseEGLSurface(EGLSurface surface) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
BackendObject::ReleaseEGLSurface(surface);
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::ReleaseEGLResources() {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
// Outstanding fence/timer-query handles now refer to a dead renderer;
|
||||
// treat them as signaled/available with zero results from here on.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer.reset();
|
||||
BackendObject::ReleaseEGLResources();
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||
(void)surface;
|
||||
// Outstanding fence/timer-query handles now refer to a dead renderer;
|
||||
// treat them as signaled/available with zero results from here on.
|
||||
BumpRendererGeneration();
|
||||
pVulkanRenderer.reset();
|
||||
}
|
||||
|
||||
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
||||
static RendererInfo RendererInfo = {
|
||||
.RendererName = "Magma", // Renderer Name
|
||||
.BackendName = "Direct (Vulkan)", // Backend Name
|
||||
.ExtraVendor = Nullopt, // Extra vendor
|
||||
.RendererGLInfo =
|
||||
{
|
||||
.TargetGLVersion = {3, 3, 0}, // Target OpenGL Version
|
||||
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
||||
.Extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32, // OpenGL Extensions
|
||||
V_OpenGL33},
|
||||
.IsCompatibilityProfile = false // Is Compatibility Profile
|
||||
},
|
||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
|
||||
};
|
||||
return RendererInfo;
|
||||
return m_rendererInfo;
|
||||
}
|
||||
|
||||
String BackendObject_DirectVulkan::GetBackendAPIVersionString() const {
|
||||
if (!m_initialized) {
|
||||
return "<uninitialized DirectVulkan backend>";
|
||||
}
|
||||
return FormatBackendAPIVersionString(m_vulkanCaps.DeviceName, m_vulkanCaps.VulkanAPIVersion.toString(),
|
||||
m_vulkanCaps.DriverVersionString);
|
||||
}
|
||||
|
||||
const RendererInfo& GetRendererIdentity() {
|
||||
static const RendererInfo rendererInfo = {
|
||||
.RendererName = "Magma",
|
||||
.BackendName = "Direct (Vulkan)",
|
||||
.ExtraVendor = Nullopt,
|
||||
.RendererGLInfo =
|
||||
{
|
||||
.TargetGLVersion = {3, 3, 0},
|
||||
.TargetGLSLVersion = {4, 6, 0},
|
||||
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
||||
.IsCompatibilityProfile = false
|
||||
},
|
||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
||||
return rendererInfo;
|
||||
}
|
||||
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||
Bool anisotropicFilteringSupported) {
|
||||
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object,
|
||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample,
|
||||
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug,
|
||||
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
|
||||
E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size};
|
||||
if (shaderSubgroupSupported && !MG_Config::Features.DisableSubgroup) {
|
||||
extensions.push_back(E_GL_KHR_shader_subgroup);
|
||||
}
|
||||
// GL_ARB_timer_query gates MC's F3 GPU% (LWJGL checks the extension string);
|
||||
// only advertised when the device actually supports timestamp queries and the
|
||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is off.
|
||||
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
|
||||
extensions.push_back(E_GL_ARB_timer_query);
|
||||
}
|
||||
// Only advertised when the samplerAnisotropy device feature was granted: without it the
|
||||
// sampler state is accepted but never applied, and an app trusting the string (LWJGL builds
|
||||
// GLCapabilities from it) would think it enabled anisotropic filtering.
|
||||
if (anisotropicFilteringSupported) {
|
||||
extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
|
||||
extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
String FormatBackendAPIVersionString(const String& deviceName, const String& vulkanApiVersionString,
|
||||
const String& driverVersionString) {
|
||||
// Format:
|
||||
// <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version>
|
||||
String str = m_vulkanCaps.DeviceName + ", Vulkan " + m_vulkanCaps.VulkanAPIVersion.toString() + ", Driver " +
|
||||
m_vulkanCaps.DriverVersionString;
|
||||
return str;
|
||||
return deviceName + ", Vulkan " + vulkanApiVersionString + ", Driver " + driverVersionString;
|
||||
}
|
||||
|
||||
BackendType BackendObject_DirectVulkan::GetBackendType() const {
|
||||
@@ -146,10 +546,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
funcsTable.GL.DrawArrays = DrawArrays;
|
||||
funcsTable.GL.DrawElements = DrawElements;
|
||||
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
||||
funcsTable.GL.MultiDrawArrays = MultiDrawArrays;
|
||||
funcsTable.GL.MultiDrawElements = MultiDrawElements;
|
||||
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
||||
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
||||
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
||||
funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
|
||||
funcsTable.GL.MultiDrawArraysIndirectCount = MultiDrawArraysIndirectCount;
|
||||
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
|
||||
funcsTable.GL.DrawRangeElements = DrawRangeElements;
|
||||
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
|
||||
@@ -165,12 +568,51 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
funcsTable.GL.ClearBufferfv = ClearBufferfv;
|
||||
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
||||
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
||||
funcsTable.GL.CopyImageSubData = CopyImageSubData;
|
||||
funcsTable.GL.GenerateMipmap = GenerateMipmap;
|
||||
funcsTable.GL.ReadPixels = ReadPixels;
|
||||
funcsTable.GL.GetTexImage = GetTexImage;
|
||||
funcsTable.GL.GetTextureImage = GetTextureImage;
|
||||
funcsTable.GL.DispatchCompute = DispatchCompute;
|
||||
funcsTable.GL.DispatchComputeIndirect = DispatchComputeIndirect;
|
||||
funcsTable.GL.MemoryBarrier = MemoryBarrier;
|
||||
funcsTable.GL.MemoryBarrierByRegion = MemoryBarrierByRegion;
|
||||
funcsTable.GL.BindImageTexture = BindImageTexture;
|
||||
funcsTable.GL.GetIntegeri_v = GetIntegeri_v;
|
||||
funcsTable.GL.GetInteger64i_v = GetInteger64i_v;
|
||||
funcsTable.GL.GetProgramiv = GetProgramiv;
|
||||
funcsTable.GL.GetProgramInterfaceiv = GetProgramInterfaceiv;
|
||||
funcsTable.GL.GetProgramResourceIndex = GetProgramResourceIndex;
|
||||
funcsTable.GL.GetProgramResourceName = GetProgramResourceName;
|
||||
funcsTable.GL.GetProgramResourceiv = GetProgramResourceiv;
|
||||
funcsTable.GL.GetProgramResourceLocation = GetProgramResourceLocation;
|
||||
funcsTable.GL.GetProgramResourceLocationIndex = GetProgramResourceLocationIndex;
|
||||
funcsTable.GL.ShaderStorageBlockBinding = ShaderStorageBlockBinding;
|
||||
funcsTable.GL.FenceSync = FenceSync;
|
||||
funcsTable.GL.ClientWaitSync = ClientWaitSync;
|
||||
funcsTable.GL.WaitSync = WaitSync;
|
||||
funcsTable.GL.DeleteSync = DeleteSync;
|
||||
funcsTable.GL.GetSyncStatus = GetSyncStatus;
|
||||
// Optional timer-query group: left null (the frontend then falls
|
||||
// back) when disabled via MOBILEGL_DISABLE_TIMERQUERY. The hooks
|
||||
// themselves additionally degrade to null handles when the device
|
||||
// lacks timestamp support.
|
||||
if (!MG_Config::Features.DisableTimerQuery) {
|
||||
funcsTable.GL.IsTimerQuerySupported = IsTimerQuerySupported;
|
||||
funcsTable.GL.BeginTimeElapsedQuery = BeginTimeElapsedQuery;
|
||||
funcsTable.GL.EndTimeElapsedQuery = EndTimeElapsedQuery;
|
||||
funcsTable.GL.QueryCounterTimestamp = QueryCounterTimestamp;
|
||||
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
||||
}
|
||||
funcsTableInitialized = true;
|
||||
}
|
||||
return funcsTable;
|
||||
@@ -180,7 +622,177 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return m_dynamicParameters;
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::ApplyVulkanCapabilitiesForTesting(
|
||||
const MG_External::VulkanCapabilities& capabilities) {
|
||||
m_vulkanCaps = capabilities;
|
||||
UpdateDynamicBackendParameters();
|
||||
UpdateAdvertisedExtensions();
|
||||
MutableFormatCapabilities().Clear();
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::UpdateAdvertisedExtensions() {
|
||||
// GL_ARB_timer_query gates MC's F3 GPU% (LWJGL checks the extension
|
||||
// string). InitCapabilities runs after InitWindowSurface has created
|
||||
// and initialized the renderer, so the advertisement can be gated on
|
||||
// real device timestamp support. ApplyVulkanCapabilitiesForTesting may
|
||||
// run without a renderer; no timer query is advertised then. Rebuilding
|
||||
// the whole list keeps re-runs idempotent.
|
||||
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
|
||||
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
|
||||
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported());
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
||||
const auto mapShaderStages = [](Uint32 vkStages) {
|
||||
Uint32 glStages = 0;
|
||||
if ((vkStages & VK_SHADER_STAGE_VERTEX_BIT) != 0) glStages |= GL_VERTEX_SHADER_BIT;
|
||||
if ((vkStages & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) != 0) glStages |= GL_TESS_CONTROL_SHADER_BIT;
|
||||
if ((vkStages & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) != 0) {
|
||||
glStages |= GL_TESS_EVALUATION_SHADER_BIT;
|
||||
}
|
||||
if ((vkStages & VK_SHADER_STAGE_GEOMETRY_BIT) != 0) glStages |= GL_GEOMETRY_SHADER_BIT;
|
||||
if ((vkStages & VK_SHADER_STAGE_FRAGMENT_BIT) != 0) glStages |= GL_FRAGMENT_SHADER_BIT;
|
||||
if ((vkStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0) glStages |= GL_COMPUTE_SHADER_BIT;
|
||||
return glStages;
|
||||
};
|
||||
|
||||
const auto mapSubgroupFeatures = [](Uint32 vkFeatures) {
|
||||
Uint32 glFeatures = 0;
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_BASIC_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_VOTE_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_VOTE_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_BALLOT_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_SHUFFLE_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_CLUSTERED_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR;
|
||||
}
|
||||
if ((vkFeatures & VK_SUBGROUP_FEATURE_QUAD_BIT) != 0) {
|
||||
glFeatures |= GL_SUBGROUP_FEATURE_QUAD_BIT_KHR;
|
||||
}
|
||||
return glFeatures;
|
||||
};
|
||||
|
||||
static constexpr SizeT kMaxAdvertisedShaderStorageBlockSize = 512ull * 1024ull * 1024ull;
|
||||
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
|
||||
m_dynamicParameters.AliasedLineWidthRangeMin = m_vulkanCaps.AliasedLineWidthRangeMin;
|
||||
m_dynamicParameters.AliasedLineWidthRangeMax = m_vulkanCaps.AliasedLineWidthRangeMax;
|
||||
// Without the samplerAnisotropy feature the limit is unusable, so report 1.0 (no anisotropy)
|
||||
// rather than a maximum the sampler manager will never apply.
|
||||
m_dynamicParameters.MaxTextureMaxAnisotropy =
|
||||
(pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()) ? m_vulkanCaps.MaxSamplerAnisotropy
|
||||
: 1.0f;
|
||||
m_dynamicParameters.SmoothLineWidthRangeMin = m_vulkanCaps.SmoothLineWidthRangeMin;
|
||||
m_dynamicParameters.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
|
||||
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
|
||||
m_dynamicParameters.PointSizeRangeMin = m_vulkanCaps.PointSizeRangeMin;
|
||||
m_dynamicParameters.PointSizeRangeMax = m_vulkanCaps.PointSizeRangeMax;
|
||||
m_dynamicParameters.PointSizeGranularity = m_vulkanCaps.PointSizeGranularity;
|
||||
m_dynamicParameters.Max3DTextureSize = m_vulkanCaps.Max3DTextureSize;
|
||||
m_dynamicParameters.MaxArrayTextureLayers = m_vulkanCaps.MaxArrayTextureLayers;
|
||||
m_dynamicParameters.MaxCubeMapTextureSize = m_vulkanCaps.MaxCubeMapTextureSize;
|
||||
m_dynamicParameters.MaxFramebufferWidth = m_vulkanCaps.MaxFramebufferWidth;
|
||||
m_dynamicParameters.MaxFramebufferHeight = m_vulkanCaps.MaxFramebufferHeight;
|
||||
m_dynamicParameters.MaxFramebufferLayers = m_vulkanCaps.MaxFramebufferLayers;
|
||||
m_dynamicParameters.MaxRenderbufferSize = m_vulkanCaps.MaxRenderbufferSize;
|
||||
m_dynamicParameters.MaxTextureSize = m_vulkanCaps.MaxTextureSize;
|
||||
m_dynamicParameters.MaxColorTextureSamples = m_vulkanCaps.MaxColorTextureSamples;
|
||||
m_dynamicParameters.MaxDepthTextureSamples = m_vulkanCaps.MaxDepthTextureSamples;
|
||||
m_dynamicParameters.MaxFramebufferSamples = m_vulkanCaps.MaxFramebufferSamples;
|
||||
m_dynamicParameters.MaxIntegerSamples = m_vulkanCaps.MaxIntegerSamples;
|
||||
m_dynamicParameters.MaxSamples = m_vulkanCaps.MaxSamples;
|
||||
m_dynamicParameters.MaxSampleMaskWords = m_vulkanCaps.MaxSampleMaskWords;
|
||||
const Int maxSupportedTextureUnits =
|
||||
static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||
// GL_MAX_TEXTURE_IMAGE_UNITS is a *per-stage* sampler limit. Adreno/Qualcomm report a huge
|
||||
// maxPerStageDescriptorSampledImages (descriptor-indexing scale), so clamping it only to our
|
||||
// combined array capacity (192) still advertises 192 per stage. Host code treats this value as
|
||||
// an array bound: Minecraft's Blaze3D GlStateManager.TEXTURES[] holds 128 entries and Iris
|
||||
// iterates [0, GL_MAX_TEXTURE_IMAGE_UNITS) over it (CompositeRenderer.renderAll), so any value
|
||||
// > 128 throws ArrayIndexOutOfBoundsException. Match desktop drivers (32) for the per-stage
|
||||
// limits while keeping the combined limit at our texture-unit array capacity.
|
||||
constexpr Int maxPerStageTextureUnits =
|
||||
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
|
||||
m_dynamicParameters.MaxTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxVertexTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxVertexTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxComputeTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxComputeTextureImageUnits, maxPerStageTextureUnits);
|
||||
m_dynamicParameters.MaxCombinedTextureImageUnits =
|
||||
std::min(m_vulkanCaps.MaxCombinedTextureImageUnits, maxSupportedTextureUnits);
|
||||
// Never advertise more attributes than the state layer can store: the current-value array and
|
||||
// the Uint32 attribute masks the draw path passes around are both bounded by MAX_VERTEX_ATTRIBS.
|
||||
m_dynamicParameters.MaxVertexAttribs =
|
||||
std::min(m_vulkanCaps.MaxVertexAttribs,
|
||||
static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS));
|
||||
m_dynamicParameters.MaxComputeShaderStorageBlocks = m_vulkanCaps.MaxComputeShaderStorageBlocks;
|
||||
m_dynamicParameters.MaxCombinedShaderStorageBlocks = m_vulkanCaps.MaxCombinedShaderStorageBlocks;
|
||||
m_dynamicParameters.MaxComputeUniformBlocks = m_vulkanCaps.MaxComputeUniformBlocks;
|
||||
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations;
|
||||
m_dynamicParameters.MaxShaderStorageBufferBindings = m_vulkanCaps.MaxShaderStorageBufferBindings;
|
||||
m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize;
|
||||
m_dynamicParameters.MaxUniformBufferBindings = m_vulkanCaps.MaxUniformBufferBindings;
|
||||
m_dynamicParameters.MaxUniformBlockSize = m_vulkanCaps.MaxUniformBlockSize;
|
||||
m_dynamicParameters.MaxImageUnits =
|
||||
std::max(std::min(m_vulkanCaps.MaxImageUnits, maxSupportedTextureUnits), 0);
|
||||
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_vulkanCaps.MaxCombinedImageUniforms, 0);
|
||||
const Int maxPerStageImageUniforms =
|
||||
std::min(m_dynamicParameters.MaxImageUnits, m_dynamicParameters.MaxCombinedImageUniforms);
|
||||
// Vulkan uses one descriptor limit for every stage, but non-compute stores/atomics are
|
||||
// optional device features. VulkanRenderer enables each feature whenever the physical
|
||||
// device reports it, so these are the exact limits the logical device can compile and run.
|
||||
m_dynamicParameters.MaxVertexImageUniforms =
|
||||
m_vulkanCaps.SupportsVertexPipelineStoresAndAtomics ? maxPerStageImageUniforms : 0;
|
||||
m_dynamicParameters.MaxGeometryImageUniforms =
|
||||
m_vulkanCaps.SupportsVertexPipelineStoresAndAtomics && m_vulkanCaps.SupportsGeometryShader
|
||||
? maxPerStageImageUniforms
|
||||
: 0;
|
||||
m_dynamicParameters.MaxFragmentImageUniforms =
|
||||
m_vulkanCaps.SupportsFragmentStoresAndAtomics ? maxPerStageImageUniforms : 0;
|
||||
m_dynamicParameters.MaxComputeImageUniforms =
|
||||
std::min(std::max(m_vulkanCaps.MaxComputeImageUniforms, 0), maxPerStageImageUniforms);
|
||||
const Int maxSupportedDrawBuffers =
|
||||
static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);
|
||||
m_dynamicParameters.MaxDrawBuffers = std::min(m_vulkanCaps.MaxDrawBuffers, maxSupportedDrawBuffers);
|
||||
m_dynamicParameters.MaxColorAttachments = std::min(m_vulkanCaps.MaxColorAttachments, maxSupportedDrawBuffers);
|
||||
m_dynamicParameters.MaxClipDistances = m_vulkanCaps.MaxClipDistances;
|
||||
m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports;
|
||||
m_dynamicParameters.MaxViewportWidth = m_vulkanCaps.MaxViewportWidth;
|
||||
m_dynamicParameters.MaxViewportHeight = m_vulkanCaps.MaxViewportHeight;
|
||||
m_dynamicParameters.ViewportBoundsRangeMin = m_vulkanCaps.ViewportBoundsRangeMin;
|
||||
m_dynamicParameters.ViewportBoundsRangeMax = m_vulkanCaps.ViewportBoundsRangeMax;
|
||||
m_dynamicParameters.ViewportSubpixelBits = m_vulkanCaps.ViewportSubpixelBits;
|
||||
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
||||
m_dynamicParameters.MaxShaderStorageBlockSize =
|
||||
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
||||
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
||||
m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize;
|
||||
m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages);
|
||||
m_dynamicParameters.SubgroupSupportedFeatures = mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
||||
m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages;
|
||||
} else {
|
||||
m_dynamicParameters.SubgroupSize = 0;
|
||||
m_dynamicParameters.SubgroupSupportedStages = 0;
|
||||
m_dynamicParameters.SubgroupSupportedFeatures = 0;
|
||||
m_dynamicParameters.SubgroupQuadOperationsInAllStages = false;
|
||||
}
|
||||
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
|
||||
MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu",
|
||||
m_vulkanCaps.MaxShaderStorageBlockSize,
|
||||
m_dynamicParameters.MaxShaderStorageBlockSize);
|
||||
}
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -12,29 +12,73 @@
|
||||
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Populates the same format-capability cache used by backend startup. Passing the
|
||||
// instance-resolved function keeps standalone callers independent of global loader
|
||||
// initialization; the physical device must remain valid for the duration of the call.
|
||||
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||
const MG_External::VulkanCapabilities& capabilities,
|
||||
FormatCapabilityCache& cache);
|
||||
|
||||
class BackendObject_DirectVulkan : public BackendObject {
|
||||
public:
|
||||
BackendObject_DirectVulkan();
|
||||
~BackendObject_DirectVulkan() override;
|
||||
|
||||
void Initialize() override;
|
||||
Bool InitWindowSurface() override;
|
||||
Bool InitCapabilities() override;
|
||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||
Bool CreateEGLWindowSurface(const WindowHandle& handle) override;
|
||||
Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override;
|
||||
Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) override;
|
||||
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
|
||||
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||
void ReleaseEGLResources() override;
|
||||
|
||||
const RendererInfo& GetRendererInfo() const override;
|
||||
String GetBackendAPIVersionString() const override;
|
||||
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
|
||||
const DynamicBackendParameters& GetDynamicParameters() const override;
|
||||
BackendType GetBackendType() const override;
|
||||
void ApplyVulkanCapabilitiesForTesting(const MG_External::VulkanCapabilities& capabilities);
|
||||
|
||||
private:
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||
void UpdateAdvertisedExtensions();
|
||||
void UpdateDynamicBackendParameters();
|
||||
|
||||
Bool m_initialized = false;
|
||||
DynamicBackendParameters m_dynamicParameters;
|
||||
MG_External::VulkanCapabilities m_vulkanCaps;
|
||||
RendererInfo m_rendererInfo;
|
||||
};
|
||||
|
||||
// Single-source-of-truth helpers shared with the driver POST
|
||||
// (MG_Util/SelfTest/DriverPost.cpp), so the identity strings and extension list
|
||||
// MobileGL reports to applications on this backend cannot drift from what the
|
||||
// POST screen shows.
|
||||
|
||||
// Static identity of the Magma renderer (renderer/backend names, target GL/GLSL
|
||||
// versions, ExtraVendor) with the baseline extension advertisement (no shader
|
||||
// subgroup, no timer queries). A live backend copies this in its constructor and
|
||||
// reconciles the Extensions in UpdateAdvertisedExtensions once real capabilities
|
||||
// exist; callers that need the advertised list for a known capability set must
|
||||
// use BuildAdvertisedExtensions instead.
|
||||
const RendererInfo& GetRendererIdentity();
|
||||
|
||||
// The full OpenGL extension list Magma advertises (glGetString(GL_EXTENSIONS)) for
|
||||
// a device with the given raw capabilities. The MOBILEGL_DISABLE_SUBGROUP and
|
||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
|
||||
// the detected device support (passing an already-gated value is harmless).
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||
Bool anisotropicFilteringSupported);
|
||||
|
||||
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
|
||||
// string an initialized backend returns from GetBackendAPIVersionString (and that
|
||||
// ends up inside the application-visible GL_RENDERER string).
|
||||
String FormatBackendAPIVersionString(const String& deviceName, const String& vulkanApiVersionString,
|
||||
const String& driverVersionString);
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,25 +8,44 @@
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include "Renderer/VulkanRenderer.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
extern UniquePtr<VulkanRenderer> pVulkanRenderer;
|
||||
|
||||
// Generation of the live VulkanRenderer instance, mirroring DirectGLES's
|
||||
// g_syncContextGeneration. BackendObject_DirectVulkan bumps it wherever
|
||||
// pVulkanRenderer is reset or recreated; fence and timer-query handles
|
||||
// stamped with an older generation are stale and resolve as signaled /
|
||||
// available with zero results instead of dereferencing the destroyed
|
||||
// renderer's frame serials and query-pool slots.
|
||||
Uint64 GetRendererGeneration();
|
||||
void BumpRendererGeneration();
|
||||
|
||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void Clear(GLbitfield mask);
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount);
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex);
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex);
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
||||
@@ -44,12 +63,72 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height, GLint border);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height);
|
||||
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||
void GenerateMipmap(GLenum target);
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void DispatchComputeIndirect(GLintptr indirect);
|
||||
void MemoryBarrier(GLbitfield barriers);
|
||||
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||
void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access,
|
||||
GLenum format);
|
||||
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||
void GetInteger64i_v(GLenum target, GLuint index, GLint64* data);
|
||||
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
||||
GLsizei* length, GLchar* name);
|
||||
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget uploadTarget,
|
||||
GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels);
|
||||
// GL fence sync objects, mapped onto the renderer's frame-serial busy
|
||||
// tracking: a fence captures the frame serial current at creation and is
|
||||
// signaled once every command recorded under that serial has completed on
|
||||
// the GPU.
|
||||
BackendSyncHandle FenceSync();
|
||||
GLenum ClientWaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void WaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||
void DeleteSync(BackendSyncHandle sync);
|
||||
Bool GetSyncStatus(BackendSyncHandle sync);
|
||||
// GPU timer queries (GL_TIME_ELAPSED spans and GL_TIMESTAMP one-shots),
|
||||
// backed by per-frame VkQueryPool timestamp slots. All hooks degrade
|
||||
// gracefully: null handles when the renderer is absent, the device lacks
|
||||
// timestamp support, or the frame's pool is exhausted.
|
||||
// Dynamic support check (GLFunctionsTable::IsTimerQuerySupported): true
|
||||
// only while a live renderer exists whose device can actually time.
|
||||
Bool IsTimerQuerySupported();
|
||||
BackendQueryHandle BeginTimeElapsedQuery();
|
||||
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||
BackendQueryHandle QueryCounterTimestamp();
|
||||
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||
// Returns true when a final value was produced (outNanoseconds set; the
|
||||
// frontend may cache it and release the handle), false when the result
|
||||
// cannot be obtained yet (e.g. a wait refused because the records' frame
|
||||
// serial is the current unsubmitted frame) - the handle then stays
|
||||
// readable later.
|
||||
Bool GetQueryResult64(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||
void DeleteBackendQuery(BackendQueryHandle query);
|
||||
// Always 0: Vulkan cannot synchronously sample the GPU clock (timestamps
|
||||
// only exist as vkCmdWriteTimestamp results); the frontend falls back.
|
||||
Int64 GetGpuTimestampNs();
|
||||
void Present();
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ProgramObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name);
|
||||
GLuint GetShaderStorageBlockBinding(const MG_State::GLState::ProgramObject& program, GLuint blockIndex);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "BufferArena.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool BufferArena::Initialize(const BufferArenaDesc& desc) {
|
||||
Shutdown();
|
||||
|
||||
MOBILEGL_ASSERT(desc.allocator != nullptr, "BufferArena::Initialize requires valid allocator");
|
||||
MOBILEGL_ASSERT(desc.frameCount > 0, "BufferArena::Initialize requires non-zero frame count");
|
||||
MOBILEGL_ASSERT(desc.usage != 0, "BufferArena::Initialize requires non-zero buffer usage");
|
||||
|
||||
m_desc = desc;
|
||||
m_frames.clear();
|
||||
m_frames.resize(desc.frameCount);
|
||||
m_deferredReleases.resize(desc.frameCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
void BufferArena::Shutdown() {
|
||||
for (auto& frame : m_frames) {
|
||||
frame.buffer.Destroy();
|
||||
frame.writeCursor = 0;
|
||||
}
|
||||
m_frames.clear();
|
||||
m_deferredReleases.clear();
|
||||
m_desc = {};
|
||||
}
|
||||
|
||||
void BufferArena::BeginFrame(Uint32 frameIndex) {
|
||||
CollectDeferredReleases(frameIndex);
|
||||
ResetFrame(frameIndex);
|
||||
}
|
||||
|
||||
void BufferArena::ResetFrame(Uint32 frameIndex) {
|
||||
AssertValidFrameIndex(frameIndex);
|
||||
m_frames[frameIndex].writeCursor = 0;
|
||||
}
|
||||
|
||||
void BufferArena::CollectDeferredReleases(Uint32 frameIndex) {
|
||||
AssertValidFrameIndex(frameIndex);
|
||||
m_deferredReleases[frameIndex].clear();
|
||||
}
|
||||
|
||||
Bool BufferArena::Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) {
|
||||
AssertValidFrameIndex(frameIndex);
|
||||
MOBILEGL_ASSERT(size > 0, "BufferArena::Allocate requires non-zero size");
|
||||
|
||||
auto& frame = m_frames[frameIndex];
|
||||
const VkDeviceSize resolvedAlignment = alignment > 0 ? alignment : 1;
|
||||
const VkDeviceSize offset = (frame.writeCursor + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
|
||||
const VkDeviceSize endOffset = offset + size;
|
||||
|
||||
if (!EnsureCapacity(frameIndex, endOffset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
frame.writeCursor = endOffset;
|
||||
outSlice = frame.buffer.GetSlice(offset, size);
|
||||
return outSlice.IsValid();
|
||||
}
|
||||
|
||||
Bool BufferArena::Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment,
|
||||
BufferSlice& outSlice) {
|
||||
MOBILEGL_ASSERT(data != nullptr || size == 0, "BufferArena::Upload data pointer is null");
|
||||
if (!Allocate(frameIndex, size, alignment, outSlice)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outSlice.mapped != nullptr) {
|
||||
Memcpy(outSlice.mapped, data, static_cast<SizeT>(size));
|
||||
return true;
|
||||
}
|
||||
|
||||
return m_frames[frameIndex].buffer.Upload(data, size, outSlice.offset);
|
||||
}
|
||||
|
||||
VkDeviceSize BufferArena::GetWriteCursor(Uint32 frameIndex) const {
|
||||
AssertValidFrameIndex(frameIndex);
|
||||
return m_frames[frameIndex].writeCursor;
|
||||
}
|
||||
|
||||
Uint32 BufferArena::GetFrameCount() const {
|
||||
return static_cast<Uint32>(m_frames.size());
|
||||
}
|
||||
|
||||
Bool BufferArena::EnsureCapacity(Uint32 frameIndex, VkDeviceSize requiredEndOffset) {
|
||||
AssertValidFrameIndex(frameIndex);
|
||||
auto& frame = m_frames[frameIndex];
|
||||
auto& buffer = frame.buffer;
|
||||
|
||||
if (buffer.IsValid() && buffer.GetSize() >= requiredEndOffset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
VkDeviceSize newCapacity = buffer.IsValid() ? buffer.GetSize() : 0;
|
||||
if (newCapacity < m_desc.minBufferSize) {
|
||||
newCapacity = m_desc.minBufferSize;
|
||||
}
|
||||
if (newCapacity == 0) {
|
||||
newCapacity = requiredEndOffset;
|
||||
}
|
||||
while (newCapacity < requiredEndOffset) {
|
||||
newCapacity *= 2;
|
||||
}
|
||||
|
||||
if (buffer.IsValid()) {
|
||||
m_deferredReleases[frameIndex].push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
VkBufferObjectDesc bufferDesc{};
|
||||
bufferDesc.allocator = m_desc.allocator;
|
||||
bufferDesc.size = newCapacity;
|
||||
bufferDesc.usage = m_desc.usage;
|
||||
bufferDesc.memoryUsage = m_desc.memoryUsage;
|
||||
bufferDesc.allocationFlags = m_desc.allocationFlags;
|
||||
if (!buffer.Create(bufferDesc)) {
|
||||
return false;
|
||||
}
|
||||
if (m_desc.persistentlyMapped && buffer.Map() == nullptr) {
|
||||
buffer.Destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
frame.writeCursor = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BufferArena::AssertValidFrameIndex(Uint32 frameIndex) const {
|
||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "BufferArena frame index out of range");
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,56 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BufferSlice.h"
|
||||
#include "VkBufferObject.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct BufferArenaDesc {
|
||||
VmaAllocator allocator = nullptr;
|
||||
Uint32 frameCount = 0;
|
||||
VkBufferUsageFlags usage = 0;
|
||||
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO;
|
||||
VmaAllocationCreateFlags allocationFlags = 0;
|
||||
VkDeviceSize minBufferSize = 0;
|
||||
Bool persistentlyMapped = false;
|
||||
};
|
||||
|
||||
class BufferArena {
|
||||
public:
|
||||
Bool Initialize(const BufferArenaDesc& desc);
|
||||
void Shutdown();
|
||||
|
||||
void BeginFrame(Uint32 frameIndex);
|
||||
void ResetFrame(Uint32 frameIndex);
|
||||
void CollectDeferredReleases(Uint32 frameIndex);
|
||||
|
||||
Bool Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice);
|
||||
Bool Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice);
|
||||
|
||||
VkDeviceSize GetWriteCursor(Uint32 frameIndex) const;
|
||||
Uint32 GetFrameCount() const;
|
||||
|
||||
private:
|
||||
struct FrameResources {
|
||||
VkBufferObject buffer;
|
||||
VkDeviceSize writeCursor = 0;
|
||||
};
|
||||
|
||||
Bool EnsureCapacity(Uint32 frameIndex, VkDeviceSize requiredEndOffset);
|
||||
void AssertValidFrameIndex(Uint32 frameIndex) const;
|
||||
|
||||
BufferArenaDesc m_desc{};
|
||||
Vector<FrameResources> m_frames;
|
||||
Vector<Vector<VkBufferObject>> m_deferredReleases;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,23 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct BufferSlice {
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceSize offset = 0;
|
||||
VkDeviceSize size = 0;
|
||||
void* mapped = nullptr;
|
||||
|
||||
Bool IsValid() const { return buffer != VK_NULL_HANDLE; }
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -13,6 +13,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Destroy(device, commandPool);
|
||||
m_frames.assign(frameCount, {});
|
||||
currentFrameIndex = 0;
|
||||
m_device = device;
|
||||
m_commandPool = commandPool;
|
||||
|
||||
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
@@ -55,10 +57,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
DestroySwapchainSemaphores(device);
|
||||
if (device != VK_NULL_HANDLE && commandPool != VK_NULL_HANDLE && !m_frames.empty()) {
|
||||
for (auto& frame : m_frames) {
|
||||
FreeRetiredCommandBuffers(frame);
|
||||
}
|
||||
vkFreeCommandBuffers(device, commandPool, frameCount, commandBuffers.data());
|
||||
}
|
||||
m_frames.clear();
|
||||
currentFrameIndex = 0;
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_commandPool = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
FrameContext::FrameData& FrameContext::GetCurrent() {
|
||||
@@ -97,6 +104,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VK_VERIFY(vkBeginCommandBuffer(frame.commandBuffer, &beginInfo), "BeginCommandRecording, vkBeginCommandBuffer");
|
||||
|
||||
frame.isCommandRecording = true;
|
||||
if (m_recordingObserver != nullptr) {
|
||||
m_recordingObserver->OnFrameCommandRecordingBegan(frame.commandBuffer);
|
||||
}
|
||||
return frame.commandBuffer;
|
||||
}
|
||||
|
||||
@@ -178,9 +188,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
||||
packet.commandBuffer = frame.commandBuffer;
|
||||
|
||||
packet.submitInfo.waitSemaphoreCount = 1;
|
||||
packet.submitInfo.pWaitSemaphores = &packet.waitSemaphore;
|
||||
packet.submitInfo.pWaitDstStageMask = &packet.waitDstStageMask;
|
||||
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
|
||||
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
|
||||
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
|
||||
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
|
||||
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
|
||||
packet.submitInfo.signalSemaphoreCount = 1;
|
||||
@@ -188,18 +198,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return packet;
|
||||
}
|
||||
|
||||
FrameContext::PresentInfoPacket FrameContext::GetPresentInfo(VkSwapchainKHR swapchain, const Uint32& imageIndex) const {
|
||||
FrameContext::PresentInfoPacket FrameContext::GetPresentInfo(VkSwapchainKHR swapchain, Uint32 imageIndex) const {
|
||||
AssertValidSwapchainImageIndex(imageIndex);
|
||||
PresentInfoPacket packet{};
|
||||
packet.waitSemaphore = m_swapchainImageRenderFinishedSemaphores[imageIndex];
|
||||
packet.swapchain = swapchain;
|
||||
packet.imageIndex = &imageIndex;
|
||||
packet.imageIndex = imageIndex;
|
||||
|
||||
packet.presentInfo.waitSemaphoreCount = 1;
|
||||
packet.presentInfo.pWaitSemaphores = &packet.waitSemaphore;
|
||||
packet.presentInfo.swapchainCount = 1;
|
||||
packet.presentInfo.pSwapchains = &packet.swapchain;
|
||||
packet.presentInfo.pImageIndices = packet.imageIndex;
|
||||
packet.presentInfo.pImageIndices = &packet.imageIndex;
|
||||
packet.presentInfo.pResults = nullptr;
|
||||
return packet;
|
||||
}
|
||||
@@ -211,14 +221,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (result != VK_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
// The slot's fence has been waited: every command buffer this slot
|
||||
// submitted (including mid-frame flushes) has finished executing.
|
||||
FreeRetiredCommandBuffers(frame);
|
||||
|
||||
result = vkResetFences(device, 1, &frame.imageInFlightFence);
|
||||
result = vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
||||
&outImageIndex);
|
||||
if (result != VK_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
||||
&outImageIndex);
|
||||
frame.imageAvailableSemaphoreConsumed = false;
|
||||
return vkResetFences(device, 1, &frame.imageInFlightFence);
|
||||
}
|
||||
|
||||
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
||||
@@ -229,6 +243,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return static_cast<Uint32>(m_frames.size());
|
||||
}
|
||||
|
||||
void FrameContext::SetRecordingObserver(IRecordingObserver* observer) {
|
||||
m_recordingObserver = observer;
|
||||
}
|
||||
|
||||
VkResult FrameContext::RetireCurrentCommandBuffer() {
|
||||
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE,
|
||||
"RetireCurrentCommandBuffer requires an initialized FrameContext");
|
||||
auto& frame = GetCurrent();
|
||||
MOBILEGL_ASSERT(!frame.isCommandRecording,
|
||||
"RetireCurrentCommandBuffer called while the command buffer is still recording");
|
||||
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = m_commandPool;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
VkCommandBuffer replacement = VK_NULL_HANDLE;
|
||||
const VkResult result = vkAllocateCommandBuffers(m_device, &allocInfo, &replacement);
|
||||
if (result != VK_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
frame.retiredCommandBuffers.push_back(frame.commandBuffer);
|
||||
frame.commandBuffer = replacement;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
void FrameContext::FreeRetiredCommandBuffers(FrameData& frame) {
|
||||
if (frame.retiredCommandBuffers.empty()) {
|
||||
return;
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE) {
|
||||
vkFreeCommandBuffers(m_device, m_commandPool, static_cast<Uint32>(frame.retiredCommandBuffers.size()),
|
||||
frame.retiredCommandBuffers.data());
|
||||
}
|
||||
frame.retiredCommandBuffers.clear();
|
||||
}
|
||||
|
||||
void FrameContext::AssertValidFrameIndex(Uint32 frameIndex) const {
|
||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "FrameContext index out of range");
|
||||
}
|
||||
@@ -260,6 +311,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
frame.hasCommandBufferRecorded = false;
|
||||
frame.isCommandRecording = false;
|
||||
frame.imageAvailableSemaphoreConsumed = false;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -277,5 +329,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
frame.imageAvailableSemaphore = VK_NULL_HANDLE;
|
||||
frame.isCommandRecording = false;
|
||||
frame.hasCommandBufferRecorded = false;
|
||||
frame.imageAvailableSemaphoreConsumed = false;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -14,6 +14,17 @@
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class FrameContext {
|
||||
public:
|
||||
// Notified immediately after a frame command buffer begins recording
|
||||
// (before any render pass has been begun); every BeginCommandRecording
|
||||
// caller funnels through this single seam. Implemented by the renderer
|
||||
// to prepare per-frame timer-query pools (vkCmdResetQueryPool must be
|
||||
// recorded outside a render pass).
|
||||
class IRecordingObserver {
|
||||
public:
|
||||
virtual ~IRecordingObserver() = default;
|
||||
virtual void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) = 0;
|
||||
};
|
||||
|
||||
struct SubmitInfoPacket {
|
||||
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||
@@ -25,7 +36,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct PresentInfoPacket {
|
||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
const Uint32* imageIndex = nullptr;
|
||||
Uint32 imageIndex = 0;
|
||||
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
||||
};
|
||||
|
||||
@@ -35,6 +46,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
||||
Bool isCommandRecording = false;
|
||||
Bool hasCommandBufferRecorded = false;
|
||||
Bool imageAvailableSemaphoreConsumed = false;
|
||||
// Command buffers submitted mid-frame (FlushPendingCommands) whose
|
||||
// execution is only known complete once this slot's fence has been
|
||||
// waited again; freed at that point.
|
||||
Vector<VkCommandBuffer> retiredCommandBuffers;
|
||||
// Submit-tracker index of this slot's most recent queue submission
|
||||
// (written by the renderer at submit time).
|
||||
Uint64 lastSubmitIndex = 0;
|
||||
};
|
||||
|
||||
VkResult Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount);
|
||||
@@ -53,13 +72,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
||||
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
SubmitInfoPacket GetSubmitInfo(Bool shouldSubmitCommandBuffer, Uint32 swapchainImageIndex) const;
|
||||
PresentInfoPacket GetPresentInfo(VkSwapchainKHR swapchain, const Uint32& imageIndex) const;
|
||||
PresentInfoPacket GetPresentInfo(VkSwapchainKHR swapchain, Uint32 imageIndex) const;
|
||||
VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
||||
Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE);
|
||||
|
||||
// Parks the current (already ended and submitted) command buffer on the
|
||||
// slot's retired list and installs a freshly allocated one, so recording
|
||||
// can restart while the submitted buffer is still executing. Retired
|
||||
// buffers are freed after the slot's fence is next waited.
|
||||
VkResult RetireCurrentCommandBuffer();
|
||||
|
||||
Uint32 GetCurrentFrameIndex() const;
|
||||
Uint32 GetFrameCount() const;
|
||||
|
||||
// Observer may be null (no notifications). Not owned.
|
||||
void SetRecordingObserver(IRecordingObserver* observer);
|
||||
|
||||
private:
|
||||
void AssertValidFrameIndex(Uint32 frameIndex) const;
|
||||
void AssertValidSwapchainImageIndex(Uint32 imageIndex) const;
|
||||
@@ -68,9 +96,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const VkSemaphoreCreateInfo& semaphoreInfo,
|
||||
const VkFenceCreateInfo& fenceInfo);
|
||||
void DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex);
|
||||
void FreeRetiredCommandBuffers(FrameData& frame);
|
||||
|
||||
Vector<FrameData> m_frames;
|
||||
Vector<VkSemaphore> m_swapchainImageRenderFinishedSemaphores;
|
||||
Uint32 currentFrameIndex = 0;
|
||||
IRecordingObserver* m_recordingObserver = nullptr;
|
||||
// Stored at Initialize for retired-command-buffer management.
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -9,8 +9,111 @@
|
||||
#include "PipelineFactory.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static const char* PrimitiveTopologyToString(VkPrimitiveTopology topology) {
|
||||
switch (topology) {
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_LIST)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY)
|
||||
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
|
||||
default:
|
||||
return "VK_PRIMITIVE_TOPOLOGY_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* SampleCountToString(VkSampleCountFlagBits sampleCount) {
|
||||
switch (sampleCount) {
|
||||
ENUM_STR_CASE(VK_SAMPLE_COUNT_1_BIT)
|
||||
ENUM_STR_CASE(VK_SAMPLE_COUNT_2_BIT)
|
||||
ENUM_STR_CASE(VK_SAMPLE_COUNT_4_BIT)
|
||||
ENUM_STR_CASE(VK_SAMPLE_COUNT_8_BIT)
|
||||
ENUM_STR_CASE(VK_SAMPLE_COUNT_16_BIT)
|
||||
ENUM_STR_CASE(VK_SAMPLE_COUNT_32_BIT)
|
||||
ENUM_STR_CASE(VK_SAMPLE_COUNT_64_BIT)
|
||||
default:
|
||||
return "VK_SAMPLE_COUNT_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* CullModeToString(VkCullModeFlags cullMode) {
|
||||
switch (cullMode) {
|
||||
case VK_CULL_MODE_NONE:
|
||||
return "VK_CULL_MODE_NONE";
|
||||
case VK_CULL_MODE_FRONT_BIT:
|
||||
return "VK_CULL_MODE_FRONT_BIT";
|
||||
case VK_CULL_MODE_BACK_BIT:
|
||||
return "VK_CULL_MODE_BACK_BIT";
|
||||
case VK_CULL_MODE_FRONT_AND_BACK:
|
||||
return "VK_CULL_MODE_FRONT_AND_BACK";
|
||||
default:
|
||||
return "VK_CULL_MODE_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* CompareOpToString(VkCompareOp compareOp) {
|
||||
switch (compareOp) {
|
||||
ENUM_STR_CASE(VK_COMPARE_OP_NEVER)
|
||||
ENUM_STR_CASE(VK_COMPARE_OP_LESS)
|
||||
ENUM_STR_CASE(VK_COMPARE_OP_EQUAL)
|
||||
ENUM_STR_CASE(VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
ENUM_STR_CASE(VK_COMPARE_OP_GREATER)
|
||||
ENUM_STR_CASE(VK_COMPARE_OP_NOT_EQUAL)
|
||||
ENUM_STR_CASE(VK_COMPARE_OP_GREATER_OR_EQUAL)
|
||||
ENUM_STR_CASE(VK_COMPARE_OP_ALWAYS)
|
||||
default:
|
||||
return "VK_COMPARE_OP_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* LogicOpToString(VkLogicOp logicOp) {
|
||||
switch (logicOp) {
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_CLEAR)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_AND)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_AND_REVERSE)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_COPY)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_AND_INVERTED)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_NO_OP)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_XOR)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_OR)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_NOR)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_EQUIVALENT)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_INVERT)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_OR_REVERSE)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_COPY_INVERTED)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_OR_INVERTED)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_NAND)
|
||||
ENUM_STR_CASE(VK_LOGIC_OP_SET)
|
||||
default:
|
||||
return "VK_LOGIC_OP_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
PipelineFactory::PipelineFactory(VkDevice device, const VulkanRendererConfig& config):
|
||||
m_device(device), m_config(config) {
|
||||
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "PipelineFactory: device is null");
|
||||
|
||||
if (m_config.DisablePipelineCache) {
|
||||
MGLOG_I("DirectVulkan: pipeline cache disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
VkPipelineCacheCreateInfo pipelineCacheInfo{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO};
|
||||
VK_VERIFY(vkCreatePipelineCache(m_device, &pipelineCacheInfo, nullptr, &m_pipelineCache),
|
||||
"vkCreatePipelineCache");
|
||||
}
|
||||
|
||||
PipelineFactory::~PipelineFactory() {
|
||||
DestroyAll();
|
||||
if (m_pipelineCache != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineCache(m_device, m_pipelineCache, nullptr);
|
||||
m_pipelineCache = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const {
|
||||
@@ -19,17 +122,42 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.rasterizerDiscardEnable, sizeof(payload.rasterizerDiscardEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOpEnable, sizeof(payload.logicOpEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.stencilTestEnable, sizeof(payload.stencilTestEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthCompareOp, sizeof(payload.depthCompareOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.blendEnable, sizeof(payload.blendEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.srcColorBlendFactor, sizeof(payload.srcColorBlendFactor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.dstColorBlendFactor, sizeof(payload.dstColorBlendFactor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.srcAlphaBlendFactor, sizeof(payload.srcAlphaBlendFactor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.dstAlphaBlendFactor, sizeof(payload.dstAlphaBlendFactor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorWriteMask, sizeof(payload.colorWriteMask)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOp, sizeof(payload.logicOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilFailOp, sizeof(payload.backStencilFailOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilPassOp, sizeof(payload.backStencilPassOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
|
||||
if (payload.colorAttachmentCount > 0) {
|
||||
XXHASH_VERIFY(XXH64_update(
|
||||
m_hashState,
|
||||
payload.colorBlendAttachments.data(),
|
||||
sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount));
|
||||
}
|
||||
return XXH64_digest(m_hashState);
|
||||
}
|
||||
|
||||
@@ -59,10 +187,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
||||
MOBILEGL_ASSERT(payload.pipelineLayout != VK_NULL_HANDLE, "PipelineFactory: pipelineLayout is null");
|
||||
MOBILEGL_ASSERT(payload.renderPass != VK_NULL_HANDLE, "PipelineFactory: renderPass is null");
|
||||
MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineCreatePayload::kMaxColorAttachments,
|
||||
"PipelineFactory: colorAttachmentCount=%u is unexpectedly large",
|
||||
payload.colorAttachmentCount);
|
||||
MGLOG_D("PipelineFactory::CreatePipeline: programHash=0x%llx vertexInputHash=0x%llx colorAttachmentCount=%u subpass=%u",
|
||||
static_cast<unsigned long long>(payload.programHash),
|
||||
static_cast<unsigned long long>(payload.vertexInputHash),
|
||||
payload.colorAttachmentCount,
|
||||
payload.subpass);
|
||||
|
||||
static constexpr VkDynamicState kDynamicStates[] = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS,
|
||||
VK_DYNAMIC_STATE_LINE_WIDTH,
|
||||
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_REFERENCE
|
||||
};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamicState{};
|
||||
@@ -72,39 +214,55 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||
ia.topology = payload.topology;
|
||||
ia.primitiveRestartEnable = payload.primitiveRestartEnable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
||||
vpci.viewportCount = 1;
|
||||
vpci.scissorCount = 1;
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
||||
raster.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
raster.cullMode = VK_CULL_MODE_NONE;
|
||||
raster.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||
raster.polygonMode = payload.polygonMode;
|
||||
raster.cullMode = payload.cullMode;
|
||||
raster.frontFace = payload.frontFace;
|
||||
raster.depthBiasEnable = payload.depthBiasEnable ? VK_TRUE : VK_FALSE;
|
||||
raster.rasterizerDiscardEnable = payload.rasterizerDiscardEnable ? VK_TRUE : VK_FALSE;
|
||||
raster.lineWidth = 1.0f;
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
ms.rasterizationSamples = payload.rasterizationSamples;
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
||||
depthStencil.depthWriteEnable = payload.depthWriteEnable ? VK_TRUE : VK_FALSE;
|
||||
depthStencil.depthCompareOp = payload.depthCompareOp;
|
||||
depthStencil.depthBoundsTestEnable = VK_FALSE;
|
||||
depthStencil.stencilTestEnable = VK_FALSE;
|
||||
depthStencil.stencilTestEnable = payload.stencilTestEnable ? VK_TRUE : VK_FALSE;
|
||||
if (payload.stencilTestEnable) {
|
||||
depthStencil.front.failOp = payload.frontStencilFailOp;
|
||||
depthStencil.front.passOp = payload.frontStencilPassOp;
|
||||
depthStencil.front.depthFailOp = payload.frontStencilDepthFailOp;
|
||||
depthStencil.front.compareOp = payload.frontStencilCompareOp;
|
||||
depthStencil.front.compareMask = 0xffffffffu;
|
||||
depthStencil.front.writeMask = 0xffffffffu;
|
||||
depthStencil.front.reference = 0;
|
||||
depthStencil.back.failOp = payload.backStencilFailOp;
|
||||
depthStencil.back.passOp = payload.backStencilPassOp;
|
||||
depthStencil.back.depthFailOp = payload.backStencilDepthFailOp;
|
||||
depthStencil.back.compareOp = payload.backStencilCompareOp;
|
||||
depthStencil.back.compareMask = 0xffffffffu;
|
||||
depthStencil.back.writeMask = 0xffffffffu;
|
||||
depthStencil.back.reference = 0;
|
||||
}
|
||||
|
||||
VkPipelineColorBlendAttachmentState colorAttach{};
|
||||
colorAttach.colorWriteMask = payload.colorWriteMask;
|
||||
colorAttach.blendEnable = payload.blendEnable ? VK_TRUE : VK_FALSE;
|
||||
colorAttach.srcColorBlendFactor = payload.srcColorBlendFactor;
|
||||
colorAttach.dstColorBlendFactor = payload.dstColorBlendFactor;
|
||||
colorAttach.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
colorAttach.srcAlphaBlendFactor = payload.srcAlphaBlendFactor;
|
||||
colorAttach.dstAlphaBlendFactor = payload.dstAlphaBlendFactor;
|
||||
colorAttach.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
Vector<VkPipelineColorBlendAttachmentState> colorAttachments(payload.colorAttachmentCount);
|
||||
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
||||
colorAttachments[i] = payload.colorBlendAttachments[i];
|
||||
}
|
||||
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
||||
blend.attachmentCount = 1;
|
||||
blend.pAttachments = &colorAttach;
|
||||
blend.logicOpEnable = payload.logicOpEnable ? VK_TRUE : VK_FALSE;
|
||||
blend.logicOp = payload.logicOp;
|
||||
blend.attachmentCount = payload.colorAttachmentCount;
|
||||
blend.pAttachments = colorAttachments.empty() ? nullptr : colorAttachments.data();
|
||||
|
||||
VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
gpi.stageCount = static_cast<Uint32>(payload.stages->size());
|
||||
@@ -122,8 +280,52 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
gpi.subpass = payload.subpass;
|
||||
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &gpi, nullptr, &pipeline),
|
||||
"vkCreateGraphicsPipelines");
|
||||
const VkResult result = vkCreateGraphicsPipelines(m_device, m_pipelineCache, 1, &gpi, nullptr, &pipeline);
|
||||
if (result != VK_SUCCESS) {
|
||||
MGLOG_F("PipelineFactory::CreatePipeline failed: result=%s (%d) programHash=0x%llx vertexInputHash=0x%llx stageCount=%u topology=%s(%d) colorAttachmentCount=%u samples=%s(%d) subpass=%u",
|
||||
VkResultToString(result),
|
||||
result,
|
||||
static_cast<unsigned long long>(payload.programHash),
|
||||
static_cast<unsigned long long>(payload.vertexInputHash),
|
||||
gpi.stageCount,
|
||||
PrimitiveTopologyToString(payload.topology),
|
||||
payload.topology,
|
||||
payload.colorAttachmentCount,
|
||||
SampleCountToString(payload.rasterizationSamples),
|
||||
payload.rasterizationSamples,
|
||||
payload.subpass);
|
||||
MGLOG_F("PipelineFactory::CreatePipeline state: cullMode=%s(0x%x) frontFace=%d depthTest=%d depthWrite=%d depthCompare=%s(%d) depthBias=%d rasterizerDiscard=%d stencilTest=%d logicOpEnable=%d logicOp=%s(%d)",
|
||||
CullModeToString(payload.cullMode),
|
||||
static_cast<Uint32>(payload.cullMode),
|
||||
payload.frontFace,
|
||||
payload.depthTestEnable ? 1 : 0,
|
||||
payload.depthWriteEnable ? 1 : 0,
|
||||
CompareOpToString(payload.depthCompareOp),
|
||||
payload.depthCompareOp,
|
||||
payload.depthBiasEnable ? 1 : 0,
|
||||
payload.rasterizerDiscardEnable ? 1 : 0,
|
||||
payload.stencilTestEnable ? 1 : 0,
|
||||
payload.logicOpEnable ? 1 : 0,
|
||||
LogicOpToString(payload.logicOp),
|
||||
payload.logicOp);
|
||||
MGLOG_F("PipelineFactory::CreatePipeline vertex input: bindingCount=%u attributeCount=%u",
|
||||
payload.vertexInputState->vertexBindingDescriptionCount,
|
||||
payload.vertexInputState->vertexAttributeDescriptionCount);
|
||||
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
||||
const auto& attachment = payload.colorBlendAttachments[i];
|
||||
MGLOG_F("PipelineFactory::CreatePipeline colorAttachment[%u]: blend=%d colorWriteMask=0x%x srcColor=%d dstColor=%d colorOp=%d srcAlpha=%d dstAlpha=%d alphaOp=%d",
|
||||
i,
|
||||
attachment.blendEnable == VK_TRUE ? 1 : 0,
|
||||
static_cast<Uint32>(attachment.colorWriteMask),
|
||||
attachment.srcColorBlendFactor,
|
||||
attachment.dstColorBlendFactor,
|
||||
attachment.colorBlendOp,
|
||||
attachment.srcAlphaBlendFactor,
|
||||
attachment.dstAlphaBlendFactor,
|
||||
attachment.alphaBlendOp);
|
||||
}
|
||||
}
|
||||
VK_VERIFY(result, "vkCreateGraphicsPipelines");
|
||||
return pipeline;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "Config.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
@@ -18,29 +19,42 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
using HashType = Uint64;
|
||||
|
||||
struct PipelineCreatePayload {
|
||||
static constexpr Uint32 kMaxColorAttachments = MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS;
|
||||
|
||||
HashType programHash = 0;
|
||||
HashType vertexInputHash = 0;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
Uint32 colorAttachmentCount = 1;
|
||||
VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
Uint32 subpass = 0;
|
||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
Bool primitiveRestartEnable = false;
|
||||
VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
|
||||
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
|
||||
VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||
Bool depthTestEnable = false;
|
||||
Bool depthWriteEnable = false;
|
||||
Bool depthBiasEnable = false;
|
||||
Bool rasterizerDiscardEnable = false;
|
||||
Bool logicOpEnable = false;
|
||||
Bool stencilTestEnable = false;
|
||||
VkCompareOp depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
Bool blendEnable = false;
|
||||
VkBlendFactor srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
VkBlendFactor dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
VkBlendFactor srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
VkBlendFactor dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
VkColorComponentFlags colorWriteMask =
|
||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
VkLogicOp logicOp = VK_LOGIC_OP_COPY;
|
||||
VkStencilOp frontStencilFailOp = VK_STENCIL_OP_KEEP;
|
||||
VkStencilOp frontStencilPassOp = VK_STENCIL_OP_KEEP;
|
||||
VkStencilOp frontStencilDepthFailOp = VK_STENCIL_OP_KEEP;
|
||||
VkCompareOp frontStencilCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
VkStencilOp backStencilFailOp = VK_STENCIL_OP_KEEP;
|
||||
VkStencilOp backStencilPassOp = VK_STENCIL_OP_KEEP;
|
||||
VkStencilOp backStencilDepthFailOp = VK_STENCIL_OP_KEEP;
|
||||
VkCompareOp backStencilCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
Array<VkPipelineColorBlendAttachmentState, kMaxColorAttachments> colorBlendAttachments{};
|
||||
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
||||
const VkPipelineVertexInputStateCreateInfo* vertexInputState = nullptr;
|
||||
};
|
||||
|
||||
explicit PipelineFactory(VkDevice device, const VulkanRendererConfig& config):
|
||||
m_device(device), m_config(config) {}
|
||||
explicit PipelineFactory(VkDevice device, const VulkanRendererConfig& config);
|
||||
~PipelineFactory();
|
||||
PipelineFactory(const PipelineFactory&) = delete;
|
||||
|
||||
@@ -53,6 +67,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
const VulkanRendererConfig& m_config;
|
||||
VkPipelineCache m_pipelineCache = VK_NULL_HANDLE;
|
||||
UnorderedMap<HashType, VkPipeline> m_cache;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,11 +11,30 @@
|
||||
#include "../VkIncludes.h"
|
||||
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||
#include "MG_State/GLState/ProgramState/ShaderObject.h"
|
||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
|
||||
#include <Includes.h>
|
||||
#include <spirv_reflect.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
enum class SamplerNumericDomain : Uint8 {
|
||||
Unknown = 0,
|
||||
Float,
|
||||
SignedInteger,
|
||||
UnsignedInteger,
|
||||
};
|
||||
|
||||
class ProgramFactory {
|
||||
public:
|
||||
enum class DescriptorBindingKind : Uint8 {
|
||||
None = 0,
|
||||
UniformBufferDynamic,
|
||||
CombinedImageSampler,
|
||||
UniformTexelBuffer,
|
||||
StorageBuffer,
|
||||
StorageImage
|
||||
};
|
||||
|
||||
enum class CompileOptionBit : Uint {
|
||||
None = 0,
|
||||
PositionYFlip = 1 << 0,
|
||||
@@ -26,69 +45,194 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
};
|
||||
using CompileOptionFlags = Flags<CompileOptionBit>;
|
||||
using HashType = Uint64;
|
||||
struct BackendProgramObject {
|
||||
|
||||
struct VkProgramObject {
|
||||
static constexpr Uint32 kMaxVertexInputLocations = 32;
|
||||
|
||||
HashType hash = 0;
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
Vector<VkPipelineShaderStageCreateInfo> stages;
|
||||
Vector<VkShaderModule> modules;
|
||||
|
||||
BackendProgramObject() = default;
|
||||
BackendProgramObject(const BackendProgramObject&) = delete;
|
||||
BackendProgramObject& operator=(const BackendProgramObject&) = delete;
|
||||
BackendProgramObject(BackendProgramObject&& other) noexcept {
|
||||
// Layout data (previously in separate VkProgramLayout)
|
||||
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
Vector<DescriptorBindingKind> bindingKinds;
|
||||
Vector<Uint32> dynamicBindings;
|
||||
Vector<Int> uniformBlockIndexByBinding;
|
||||
Vector<String> samplerNameByBinding;
|
||||
Vector<Int> samplerUniformLocationByBinding;
|
||||
Vector<TextureTarget> samplerTextureTargetByBinding;
|
||||
Vector<SamplerNumericDomain> samplerNumericDomainByBinding;
|
||||
Vector<VkFormat> storageImageFormatByBinding;
|
||||
Vector<Bool> storageImageUsesBindingFormatByBinding;
|
||||
Vector<String> storageBlockNameByBinding;
|
||||
Vector<Int> storageBlockIndexByBinding;
|
||||
Int globalUboBinding = -1;
|
||||
Uint32 activeVertexInputLocationMask = 0;
|
||||
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
|
||||
Uint32 activeFragmentOutputLocationMask = 0;
|
||||
Array<GLenum, kMaxVertexInputLocations> fragmentOutputTypes{};
|
||||
ShaderStage rasterizationProducerStage = ShaderStage::Unknown;
|
||||
Uint32 producerOutputComponentCount = 0;
|
||||
Uint32 fragmentInputComponentCount = 0;
|
||||
|
||||
static inline VkDevice s_device = VK_NULL_HANDLE;
|
||||
|
||||
VkProgramObject() = default;
|
||||
VkProgramObject(const VkProgramObject&) = delete;
|
||||
VkProgramObject& operator=(const VkProgramObject&) = delete;
|
||||
VkProgramObject(VkProgramObject&& other) noexcept {
|
||||
hash = other.hash;
|
||||
device = other.device;
|
||||
stages = std::move(other.stages);
|
||||
modules = std::move(other.modules);
|
||||
descriptorSetLayout = other.descriptorSetLayout;
|
||||
pipelineLayout = other.pipelineLayout;
|
||||
bindingKinds = std::move(other.bindingKinds);
|
||||
dynamicBindings = std::move(other.dynamicBindings);
|
||||
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
||||
samplerNameByBinding = std::move(other.samplerNameByBinding);
|
||||
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
|
||||
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
|
||||
samplerNumericDomainByBinding = std::move(other.samplerNumericDomainByBinding);
|
||||
storageImageFormatByBinding = std::move(other.storageImageFormatByBinding);
|
||||
storageImageUsesBindingFormatByBinding =
|
||||
std::move(other.storageImageUsesBindingFormatByBinding);
|
||||
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
||||
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||
globalUboBinding = other.globalUboBinding;
|
||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||
vertexInputTypes = other.vertexInputTypes;
|
||||
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
|
||||
fragmentOutputTypes = other.fragmentOutputTypes;
|
||||
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||
other.hash = 0;
|
||||
other.device = VK_NULL_HANDLE;
|
||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
other.pipelineLayout = VK_NULL_HANDLE;
|
||||
other.globalUboBinding = -1;
|
||||
other.activeVertexInputLocationMask = 0;
|
||||
other.activeFragmentOutputLocationMask = 0;
|
||||
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||
other.producerOutputComponentCount = 0;
|
||||
other.fragmentInputComponentCount = 0;
|
||||
}
|
||||
BackendProgramObject& operator=(BackendProgramObject&& other) noexcept {
|
||||
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
DestroyModules();
|
||||
stages.clear();
|
||||
Destroy();
|
||||
hash = other.hash;
|
||||
device = other.device;
|
||||
stages = std::move(other.stages);
|
||||
modules = std::move(other.modules);
|
||||
descriptorSetLayout = other.descriptorSetLayout;
|
||||
pipelineLayout = other.pipelineLayout;
|
||||
bindingKinds = std::move(other.bindingKinds);
|
||||
dynamicBindings = std::move(other.dynamicBindings);
|
||||
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
||||
samplerNameByBinding = std::move(other.samplerNameByBinding);
|
||||
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
|
||||
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
|
||||
samplerNumericDomainByBinding = std::move(other.samplerNumericDomainByBinding);
|
||||
storageImageFormatByBinding = std::move(other.storageImageFormatByBinding);
|
||||
storageImageUsesBindingFormatByBinding =
|
||||
std::move(other.storageImageUsesBindingFormatByBinding);
|
||||
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
||||
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||
globalUboBinding = other.globalUboBinding;
|
||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||
vertexInputTypes = other.vertexInputTypes;
|
||||
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
|
||||
fragmentOutputTypes = other.fragmentOutputTypes;
|
||||
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||
other.hash = 0;
|
||||
other.device = VK_NULL_HANDLE;
|
||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
other.pipelineLayout = VK_NULL_HANDLE;
|
||||
other.globalUboBinding = -1;
|
||||
other.activeVertexInputLocationMask = 0;
|
||||
other.activeFragmentOutputLocationMask = 0;
|
||||
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||
other.producerOutputComponentCount = 0;
|
||||
other.fragmentInputComponentCount = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~BackendProgramObject() {
|
||||
DestroyModules();
|
||||
stages.clear();
|
||||
~VkProgramObject() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
private:
|
||||
void DestroyModules() {
|
||||
for (auto module : modules) {
|
||||
if (module != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(device, module, nullptr);
|
||||
void Destroy() {
|
||||
if (s_device != VK_NULL_HANDLE) {
|
||||
if (pipelineLayout != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(s_device, pipelineLayout, nullptr);
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
if (descriptorSetLayout != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorSetLayout(s_device, descriptorSetLayout, nullptr);
|
||||
descriptorSetLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
for (auto module : modules) {
|
||||
if (module != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(s_device, module, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
modules.clear();
|
||||
stages.clear();
|
||||
}
|
||||
};
|
||||
|
||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config)
|
||||
: m_device(device), m_config(config) {}
|
||||
~ProgramFactory();
|
||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16,
|
||||
Bool shaderDrawParametersEnabled = false,
|
||||
Bool unformattedFloatStorageImagesEnabled = false)
|
||||
: m_device(device), m_maxBindings(maxBindings), m_config(config),
|
||||
m_shaderDrawParametersEnabled(shaderDrawParametersEnabled),
|
||||
m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled) {
|
||||
VkProgramObject::s_device = device;
|
||||
}
|
||||
~ProgramFactory() = default;
|
||||
ProgramFactory(const ProgramFactory&) = delete;
|
||||
|
||||
HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const;
|
||||
Vector<VkPipelineShaderStageCreateInfo>& GetOrCreatePipelineShaderStages(
|
||||
const VkProgramObject& GetOrCreateProgram(
|
||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
||||
|
||||
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
||||
static VkFormat ConvertSpirvImageFormatToVkFormat(SpvImageFormat format);
|
||||
static SamplerNumericDomain UniformTypeToSamplerNumericDomain(GLenum glType);
|
||||
|
||||
private:
|
||||
struct ProgramLookupCache {
|
||||
const MG_State::GLState::ProgramObject* program = nullptr;
|
||||
Uint32 backendStateVersion = 0;
|
||||
CompileOptionFlags flags{};
|
||||
HashType hash = 0;
|
||||
};
|
||||
|
||||
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
|
||||
void ReflectVertexInputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const;
|
||||
void ReflectFragmentOutputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const;
|
||||
void ReflectLayout(const MG_State::GLState::ProgramObject& program, const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const;
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
UnorderedMap<HashType, BackendProgramObject> m_cache;
|
||||
Uint32 m_maxBindings = 0;
|
||||
UnorderedMap<HashType, VkProgramObject> m_cache;
|
||||
const VulkanRendererConfig& m_config;
|
||||
// True when the device enabled shaderDrawParameters; gates the InstanceIndex rebase pass
|
||||
// (which needs the DrawParameters capability / gl_BaseInstance builtin).
|
||||
Bool m_shaderDrawParametersEnabled = false;
|
||||
// True only when the logical device enabled both
|
||||
// shaderStorageImageReadWithoutFormat and shaderStorageImageWriteWithoutFormat.
|
||||
Bool m_unformattedFloatStorageImagesEnabled = false;
|
||||
mutable ProgramLookupCache m_lastLookup;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
#include "SwapchainObject.h"
|
||||
|
||||
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
|
||||
#include "MG_State/GLState/TextureState/TextureObject2D.h"
|
||||
|
||||
#if defined(__has_include)
|
||||
#if __has_include(<vulkan/vk_enum_string_helper.h>)
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
@@ -28,8 +31,19 @@ static const char* string_VkColorSpaceKHR(VkColorSpaceKHR) {
|
||||
return "VkColorSpaceKHR(unknown)";
|
||||
}
|
||||
|
||||
static const char* string_VkPresentModeKHR(VkPresentModeKHR) {
|
||||
return "VkPresentModeKHR(unknown)";
|
||||
static const char* string_VkPresentModeKHR(VkPresentModeKHR presentMode) {
|
||||
switch (presentMode) {
|
||||
case VK_PRESENT_MODE_IMMEDIATE_KHR:
|
||||
return "VK_PRESENT_MODE_IMMEDIATE_KHR";
|
||||
case VK_PRESENT_MODE_MAILBOX_KHR:
|
||||
return "VK_PRESENT_MODE_MAILBOX_KHR";
|
||||
case VK_PRESENT_MODE_FIFO_KHR:
|
||||
return "VK_PRESENT_MODE_FIFO_KHR";
|
||||
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
|
||||
return "VK_PRESENT_MODE_FIFO_RELAXED_KHR";
|
||||
default:
|
||||
return "VkPresentModeKHR(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* string_VkSurfaceTransformFlagBitsKHR(VkSurfaceTransformFlagBitsKHR) {
|
||||
@@ -38,6 +52,40 @@ static const char* string_VkSurfaceTransformFlagBitsKHR(VkSurfaceTransformFlagBi
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
Bool HasStencilComponent(VkFormat format) {
|
||||
return format == VK_FORMAT_D24_UNORM_S8_UINT || format == VK_FORMAT_D32_SFLOAT_S8_UINT;
|
||||
}
|
||||
|
||||
VkFormat FindSupportedDepthStencilFormat(VkPhysicalDevice physicalDevice) {
|
||||
const VkFormat candidates[] = {VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
VK_FORMAT_D32_SFLOAT};
|
||||
for (VkFormat format : candidates) {
|
||||
VkFormatProperties props{};
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
|
||||
if ((props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
Uint32 FindMemoryType(VkPhysicalDevice physicalDevice, Uint32 typeFilter, VkMemoryPropertyFlags properties) {
|
||||
VkPhysicalDeviceMemoryProperties memProperties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
|
||||
|
||||
for (Uint32 i = 0; i < memProperties.memoryTypeCount; i++) {
|
||||
if ((typeFilter & (1 << i)) &&
|
||||
(memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
MOBILEGL_ASSERT(false, "Failed to find suitable memory type.");
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SwapchainObject::SwapchainCapabilities SwapchainObject::GetSwapchainCapabilities(VkPhysicalDevice physicalDevice,
|
||||
VkSurfaceKHR surface) {
|
||||
SwapchainCapabilities swapchainCapabilities{};
|
||||
@@ -68,7 +116,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkSurfaceFormatKHR SwapchainObject::ChooseSwapchainSurfaceFormat(
|
||||
const Vector<VkSurfaceFormatKHR>& availableFormats) {
|
||||
for (const auto& availableFormat : availableFormats) {
|
||||
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB &&
|
||||
if ((availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM ||
|
||||
availableFormat.format == VK_FORMAT_R8G8B8A8_UNORM) &&
|
||||
availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
||||
return availableFormat;
|
||||
}
|
||||
}
|
||||
for (const auto& availableFormat : availableFormats) {
|
||||
if ((availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB ||
|
||||
availableFormat.format == VK_FORMAT_R8G8B8A8_SRGB) &&
|
||||
availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
||||
return availableFormat;
|
||||
}
|
||||
@@ -93,7 +149,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
void SwapchainObject::Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
|
||||
Uint32 graphicsQueueFamily, Uint32 presentQueueFamily, Uint32 minImageCountHint) {
|
||||
Uint32 graphicsQueueFamily, Uint32 presentQueueFamily, Uint32 minImageCountHint,
|
||||
VkExtent2D desiredExtent) {
|
||||
const auto swapchainCapabilities = GetSwapchainCapabilities(physicalDevice, surface);
|
||||
MOBILEGL_ASSERT(swapchainCapabilities.IsComplete(),
|
||||
"SwapchainObject::Create failed: incomplete swapchain capabilities");
|
||||
@@ -116,7 +173,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MGLOG_I("Picked present mode: %s", string_VkPresentModeKHR(presentMode));
|
||||
|
||||
const auto& swapchainCaps = swapchainCapabilities.capabilities;
|
||||
const auto targetImageCount = std::max<Uint32>(minImageCountHint, swapchainCaps.minImageCount);
|
||||
Uint32 targetImageCount = std::max<Uint32>(minImageCountHint, swapchainCaps.minImageCount);
|
||||
if (swapchainCaps.maxImageCount != 0) {
|
||||
targetImageCount = std::min(targetImageCount, swapchainCaps.maxImageCount);
|
||||
}
|
||||
MGLOG_I("Set minImageCount = %u", targetImageCount);
|
||||
MGLOG_I("Swapchain currentTransform = %s",
|
||||
string_VkSurfaceTransformFlagBitsKHR(swapchainCaps.currentTransform));
|
||||
@@ -127,6 +187,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
createInfo.imageFormat = pickedSurfaceFormat.format;
|
||||
createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace;
|
||||
createInfo.imageExtent = swapchainCaps.currentExtent;
|
||||
if (createInfo.imageExtent.width == UINT32_MAX || createInfo.imageExtent.height == UINT32_MAX) {
|
||||
createInfo.imageExtent.width = std::clamp(desiredExtent.width,
|
||||
swapchainCaps.minImageExtent.width,
|
||||
swapchainCaps.maxImageExtent.width);
|
||||
createInfo.imageExtent.height = std::clamp(desiredExtent.height,
|
||||
swapchainCaps.minImageExtent.height,
|
||||
swapchainCaps.maxImageExtent.height);
|
||||
}
|
||||
const VkExtent2D defaultFramebufferExtent = createInfo.imageExtent;
|
||||
if (swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
|
||||
swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
|
||||
std::swap(createInfo.imageExtent.width, createInfo.imageExtent.height);
|
||||
}
|
||||
|
||||
createInfo.imageArrayLayers = 1;
|
||||
const VkImageUsageFlags requiredImageUsage =
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
@@ -185,12 +259,163 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_imageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
|
||||
CreateImageViews(device);
|
||||
CreateDepthStencilResources(device, physicalDevice);
|
||||
|
||||
MGLOG_I("Swapchain created, extent = %dx%d, swapchain imageCount = %d", m_extent.width, m_extent.height,
|
||||
imageCount);
|
||||
|
||||
// Properly initialize Default FBO here
|
||||
auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
|
||||
const Int extentWidth = static_cast<Int>(defaultFramebufferExtent.width);
|
||||
const Int extentHeight = static_cast<Int>(defaultFramebufferExtent.height);
|
||||
const SizeT defaultAttachmentByteSize =
|
||||
static_cast<SizeT>(defaultFramebufferExtent.width) *
|
||||
static_cast<SizeT>(defaultFramebufferExtent.height) * 4;
|
||||
|
||||
auto* colorTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->colorAttachment.get());
|
||||
colorTex->AllocateStorage(
|
||||
TextureUploadTarget::Texture2D, 0, {
|
||||
{extentWidth, extentHeight, 1},
|
||||
defaultAttachmentByteSize}); // TODO: 4 is format size
|
||||
TextureInternalFormat depthFormat = TextureInternalFormat::Depth24Stencil8;
|
||||
switch (m_depthStencilFormat) {
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
depthFormat = TextureInternalFormat::Depth24Stencil8;
|
||||
break;
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
depthFormat = TextureInternalFormat::Depth32FStencil8;
|
||||
break;
|
||||
case VK_FORMAT_D32_SFLOAT:
|
||||
depthFormat = TextureInternalFormat::DepthComponent32F;
|
||||
break;
|
||||
default:
|
||||
depthFormat = TextureInternalFormat::Depth24Stencil8;
|
||||
break;
|
||||
}
|
||||
auto* depthTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->depthAttachment.get());
|
||||
depthTex->SetInternalFormat(depthFormat);
|
||||
depthTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {
|
||||
{extentWidth, extentHeight, 1},
|
||||
defaultAttachmentByteSize}); // TODO: 4 is format size
|
||||
|
||||
// The default FBO's stencil attachment must track the swapchain extent:
|
||||
// FramebufferObject::CheckCompleteness requires every valid attachment
|
||||
// to share the same dimensions, and Init.cpp leaves a 512x512 placeholder.
|
||||
// Without this the retrace-layer glReadPixels snapshot fails with
|
||||
// GL_INVALID_FRAMEBUFFER_OPERATION on DirectVulkan.
|
||||
TextureInternalFormat stencilFormat = TextureInternalFormat::Depth24Stencil8;
|
||||
switch (m_depthStencilFormat) {
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
stencilFormat = TextureInternalFormat::Depth32FStencil8;
|
||||
break;
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
stencilFormat = TextureInternalFormat::Depth24Stencil8;
|
||||
break;
|
||||
default:
|
||||
// No stencil plane; mirror the depth format for consistency.
|
||||
stencilFormat = depthFormat;
|
||||
break;
|
||||
}
|
||||
auto* stencilTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->stencilAttachment.get());
|
||||
stencilTex->SetInternalFormat(stencilFormat);
|
||||
stencilTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {
|
||||
{extentWidth, extentHeight, 1},
|
||||
defaultAttachmentByteSize}); // TODO: 4 is format size
|
||||
|
||||
}
|
||||
|
||||
void SwapchainObject::CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice) {
|
||||
DestroyDepthStencilResources(device);
|
||||
|
||||
const auto imageCount = static_cast<Uint32>(m_images.size());
|
||||
if (imageCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_depthStencilFormat = FindSupportedDepthStencilFormat(physicalDevice);
|
||||
MOBILEGL_ASSERT(m_depthStencilFormat != VK_FORMAT_UNDEFINED, "No supported depth/stencil format found.");
|
||||
|
||||
m_depthStencilImages.assign(imageCount, VK_NULL_HANDLE);
|
||||
m_depthStencilImageMemories.assign(imageCount, VK_NULL_HANDLE);
|
||||
m_depthStencilImageViews.assign(imageCount, VK_NULL_HANDLE);
|
||||
m_depthStencilImageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
|
||||
for (Uint32 i = 0; i < imageCount; ++i) {
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent.width = m_extent.width;
|
||||
imageInfo.extent.height = m_extent.height;
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.format = m_depthStencilFormat;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(device, &imageInfo, nullptr, &m_depthStencilImages[i]), "vkCreateImage(depth)");
|
||||
|
||||
VkMemoryRequirements memRequirements{};
|
||||
vkGetImageMemoryRequirements(device, m_depthStencilImages[i], &memRequirements);
|
||||
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = memRequirements.size;
|
||||
allocInfo.memoryTypeIndex =
|
||||
FindMemoryType(physicalDevice, memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(device, &allocInfo, nullptr, &m_depthStencilImageMemories[i]),
|
||||
"vkAllocateMemory(depth)");
|
||||
VK_VERIFY(vkBindImageMemory(device, m_depthStencilImages[i], m_depthStencilImageMemories[i], 0),
|
||||
"vkBindImageMemory(depth)");
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = m_depthStencilImages[i];
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = m_depthStencilFormat;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (HasStencilComponent(m_depthStencilFormat)) {
|
||||
viewInfo.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(device, &viewInfo, nullptr, &m_depthStencilImageViews[i]),
|
||||
"vkCreateImageView(depth)");
|
||||
}
|
||||
}
|
||||
|
||||
void SwapchainObject::DestroyDepthStencilResources(VkDevice device) {
|
||||
for (auto view : m_depthStencilImageViews) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(device, view, nullptr);
|
||||
}
|
||||
}
|
||||
m_depthStencilImageViews.clear();
|
||||
|
||||
for (auto image : m_depthStencilImages) {
|
||||
if (image != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(device, image, nullptr);
|
||||
}
|
||||
}
|
||||
m_depthStencilImages.clear();
|
||||
|
||||
for (auto memory : m_depthStencilImageMemories) {
|
||||
if (memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, memory, nullptr);
|
||||
}
|
||||
}
|
||||
m_depthStencilImageMemories.clear();
|
||||
m_depthStencilImageLayouts.clear();
|
||||
m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
void SwapchainObject::Shutdown(VkDevice device) {
|
||||
DestroyDepthStencilResources(device);
|
||||
|
||||
for (auto imageView : m_imageViews) {
|
||||
vkDestroyImageView(device, imageView, nullptr);
|
||||
}
|
||||
@@ -221,6 +446,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_imageLayouts[index] = layout;
|
||||
}
|
||||
|
||||
VkImage SwapchainObject::GetDepthStencilImage(Uint32 index) const {
|
||||
MOBILEGL_ASSERT(index < m_depthStencilImages.size(), "Swapchain depth/stencil image index out of range");
|
||||
return m_depthStencilImages[index];
|
||||
}
|
||||
|
||||
VkImageView SwapchainObject::GetDepthStencilImageView(Uint32 index) const {
|
||||
MOBILEGL_ASSERT(index < m_depthStencilImageViews.size(),
|
||||
"Swapchain depth/stencil image view index out of range");
|
||||
return m_depthStencilImageViews[index];
|
||||
}
|
||||
|
||||
VkImageLayout SwapchainObject::GetDepthStencilImageLayout(Uint32 index) const {
|
||||
MOBILEGL_ASSERT(index < m_depthStencilImageLayouts.size(),
|
||||
"Swapchain depth/stencil image layout index out of range");
|
||||
return m_depthStencilImageLayouts[index];
|
||||
}
|
||||
|
||||
void SwapchainObject::SetDepthStencilImageLayout(Uint32 index, VkImageLayout layout) {
|
||||
MOBILEGL_ASSERT(index < m_depthStencilImageLayouts.size(),
|
||||
"Swapchain depth/stencil image layout index out of range");
|
||||
m_depthStencilImageLayouts[index] = layout;
|
||||
}
|
||||
|
||||
void SwapchainObject::CreateImageViews(VkDevice device) {
|
||||
m_imageViews.resize(m_images.size(), VK_NULL_HANDLE);
|
||||
for (SizeT i = 0; i < m_imageViews.size(); i++) {
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector<VkPresentModeKHR>& availablePresentModes);
|
||||
|
||||
void Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, Uint32 graphicsQueueFamily,
|
||||
Uint32 presentQueueFamily, Uint32 minImageCountHint);
|
||||
Uint32 presentQueueFamily, Uint32 minImageCountHint, VkExtent2D desiredExtent);
|
||||
void Shutdown(VkDevice device);
|
||||
|
||||
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
||||
@@ -38,6 +38,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkSurfaceTransformFlagBitsKHR GetPreTransform() const { return m_preTransform; }
|
||||
const Vector<VkImage>& GetImages() const { return m_images; }
|
||||
const Vector<VkImageView>& GetImageViews() const { return m_imageViews; }
|
||||
VkFormat GetDepthStencilFormat() const { return m_depthStencilFormat; }
|
||||
const Vector<VkImageView>& GetDepthStencilImageViews() const { return m_depthStencilImageViews; }
|
||||
VkImage GetDepthStencilImage(Uint32 index) const;
|
||||
VkImageView GetDepthStencilImageView(Uint32 index) const;
|
||||
VkImageLayout GetDepthStencilImageLayout(Uint32 index) const;
|
||||
void SetDepthStencilImageLayout(Uint32 index, VkImageLayout layout);
|
||||
VkImage GetImage(Uint32 index) const;
|
||||
VkImageLayout GetImageLayout(Uint32 index) const;
|
||||
void SetImageLayout(Uint32 index, VkImageLayout layout);
|
||||
@@ -45,6 +51,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
private:
|
||||
void CreateImageViews(VkDevice device);
|
||||
void CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice);
|
||||
void DestroyDepthStencilResources(VkDevice device);
|
||||
static constexpr VkPresentModeKHR s_desiredPresentModes[] {
|
||||
VK_PRESENT_MODE_MAILBOX_KHR,
|
||||
VK_PRESENT_MODE_IMMEDIATE_KHR,
|
||||
@@ -59,5 +67,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Vector<VkImage> m_images;
|
||||
Vector<VkImageView> m_imageViews;
|
||||
Vector<VkImageLayout> m_imageLayouts;
|
||||
|
||||
VkFormat m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
Vector<VkImage> m_depthStencilImages;
|
||||
Vector<VkDeviceMemory> m_depthStencilImageMemories;
|
||||
Vector<VkImageView> m_depthStencilImageViews;
|
||||
Vector<VkImageLayout> m_depthStencilImageLayouts;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -1,875 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "UniformDescriptorBinder.h"
|
||||
|
||||
#include "VkFramebufferManager.h"
|
||||
#include "MG_State/GLState/Core.h"
|
||||
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||
#include "MG_Util/ShaderTranspiler/Types.h"
|
||||
#include <limits>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkDeviceSize UniformDescriptorBinder::AlignUp(VkDeviceSize value, VkDeviceSize alignment) {
|
||||
if (alignment == 0) {
|
||||
return value;
|
||||
}
|
||||
return (value + alignment - 1) / alignment * alignment;
|
||||
}
|
||||
|
||||
Uint64 UniformDescriptorBinder::ComputeProgramHash(const MG_State::GLState::ProgramObject& program) {
|
||||
XXH64_state_t* state = XXH64_createState();
|
||||
XXHASH_VERIFY(XXH64_reset(state, 0xC0D3A11ULL));
|
||||
const auto& spirv = program.GetGeneratedSpirv();
|
||||
for (const auto& module : spirv) {
|
||||
XXHASH_VERIFY(XXH64_update(state, module.data(), module.size() * sizeof(Uint)));
|
||||
}
|
||||
const Uint32 blockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
|
||||
XXHASH_VERIFY(XXH64_update(state, &blockCount, sizeof(blockCount)));
|
||||
for (Uint32 i = 0; i < blockCount; ++i) {
|
||||
const Uint32 binding = program.GetUniformBlockBinding(i);
|
||||
XXHASH_VERIFY(XXH64_update(state, &binding, sizeof(binding)));
|
||||
}
|
||||
const Uint64 hash = XXH64_digest(state);
|
||||
XXH64_freeState(state);
|
||||
return hash;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::IsSamplerUniformType(GLenum glType) {
|
||||
switch (glType) {
|
||||
case GL_SAMPLER_1D:
|
||||
case GL_SAMPLER_2D:
|
||||
case GL_SAMPLER_3D:
|
||||
case GL_SAMPLER_CUBE:
|
||||
case GL_SAMPLER_1D_SHADOW:
|
||||
case GL_SAMPLER_2D_SHADOW:
|
||||
case GL_SAMPLER_1D_ARRAY:
|
||||
case GL_SAMPLER_2D_ARRAY:
|
||||
case GL_SAMPLER_1D_ARRAY_SHADOW:
|
||||
case GL_SAMPLER_2D_ARRAY_SHADOW:
|
||||
case GL_SAMPLER_2D_MULTISAMPLE:
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
case GL_SAMPLER_CUBE_SHADOW:
|
||||
case GL_SAMPLER_BUFFER:
|
||||
case GL_SAMPLER_2D_RECT:
|
||||
case GL_SAMPLER_2D_RECT_SHADOW:
|
||||
case GL_INT_SAMPLER_1D:
|
||||
case GL_INT_SAMPLER_2D:
|
||||
case GL_INT_SAMPLER_3D:
|
||||
case GL_INT_SAMPLER_CUBE:
|
||||
case GL_INT_SAMPLER_1D_ARRAY:
|
||||
case GL_INT_SAMPLER_2D_ARRAY:
|
||||
case GL_INT_SAMPLER_2D_MULTISAMPLE:
|
||||
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
case GL_INT_SAMPLER_BUFFER:
|
||||
case GL_INT_SAMPLER_2D_RECT:
|
||||
case GL_UNSIGNED_INT_SAMPLER_1D:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D:
|
||||
case GL_UNSIGNED_INT_SAMPLER_3D:
|
||||
case GL_UNSIGNED_INT_SAMPLER_CUBE:
|
||||
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
case GL_UNSIGNED_INT_SAMPLER_BUFFER:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TextureTarget UniformDescriptorBinder::UniformTypeToTextureTarget(GLenum glType) {
|
||||
switch (glType) {
|
||||
case GL_SAMPLER_1D:
|
||||
case GL_INT_SAMPLER_1D:
|
||||
case GL_UNSIGNED_INT_SAMPLER_1D:
|
||||
return TextureTarget::Texture1D;
|
||||
case GL_SAMPLER_3D:
|
||||
case GL_INT_SAMPLER_3D:
|
||||
case GL_UNSIGNED_INT_SAMPLER_3D:
|
||||
return TextureTarget::Texture3D;
|
||||
case GL_SAMPLER_CUBE:
|
||||
case GL_SAMPLER_CUBE_SHADOW:
|
||||
case GL_INT_SAMPLER_CUBE:
|
||||
case GL_UNSIGNED_INT_SAMPLER_CUBE:
|
||||
return TextureTarget::TextureCubeMap;
|
||||
case GL_SAMPLER_2D_MULTISAMPLE:
|
||||
case GL_INT_SAMPLER_2D_MULTISAMPLE:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
|
||||
return TextureTarget::Texture2DMultisample;
|
||||
case GL_SAMPLER_BUFFER:
|
||||
case GL_INT_SAMPLER_BUFFER:
|
||||
case GL_UNSIGNED_INT_SAMPLER_BUFFER:
|
||||
return TextureTarget::TextureBuffer;
|
||||
case GL_SAMPLER_1D_ARRAY:
|
||||
case GL_SAMPLER_1D_ARRAY_SHADOW:
|
||||
case GL_INT_SAMPLER_1D_ARRAY:
|
||||
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
|
||||
return TextureTarget::Texture1DArray;
|
||||
case GL_SAMPLER_2D_ARRAY:
|
||||
case GL_SAMPLER_2D_ARRAY_SHADOW:
|
||||
case GL_INT_SAMPLER_2D_ARRAY:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
|
||||
return TextureTarget::Texture2DArray;
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
return TextureTarget::Texture2DMultisampleArray;
|
||||
case GL_SAMPLER_2D_RECT:
|
||||
case GL_SAMPLER_2D_RECT_SHADOW:
|
||||
case GL_INT_SAMPLER_2D_RECT:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
|
||||
return TextureTarget::TextureRectangle;
|
||||
case GL_SAMPLER_2D:
|
||||
case GL_SAMPLER_2D_SHADOW:
|
||||
case GL_INT_SAMPLER_2D:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D:
|
||||
default:
|
||||
return TextureTarget::Texture2D;
|
||||
}
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::Initialize(VkDevice device, VmaAllocator allocator,
|
||||
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
|
||||
Uint32 maxBindings, Uint32 setsPerFrame, VkDeviceSize perFrameUploadBytes,
|
||||
VkTextureSamplerManager* textureSamplerManager,
|
||||
VkFramebufferManager* framebufferManager) {
|
||||
Shutdown();
|
||||
|
||||
MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice");
|
||||
MOBILEGL_ASSERT(allocator != nullptr, "UniformDescriptorBinder::Initialize requires valid VMA allocator");
|
||||
MOBILEGL_ASSERT(frameCount > 0, "UniformDescriptorBinder::Initialize requires frameCount > 0");
|
||||
MOBILEGL_ASSERT(maxBindings > 0, "UniformDescriptorBinder::Initialize requires maxBindings > 0");
|
||||
MOBILEGL_ASSERT(setsPerFrame > 0, "UniformDescriptorBinder::Initialize requires setsPerFrame > 0");
|
||||
|
||||
m_device = device;
|
||||
m_allocator = allocator;
|
||||
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
|
||||
m_perFrameUploadBytes = perFrameUploadBytes;
|
||||
m_frameCount = frameCount;
|
||||
m_maxBindings = maxBindings;
|
||||
m_setsPerFrame = setsPerFrame;
|
||||
m_peakDescriptorSetsObserved = 0;
|
||||
m_textureSamplerManager = textureSamplerManager;
|
||||
m_framebufferManager = framebufferManager;
|
||||
|
||||
m_frames.resize(m_frameCount);
|
||||
for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) {
|
||||
auto& frame = m_frames[frameIndex];
|
||||
frame.writeCursor = 0;
|
||||
frame.activeDescriptorPoolIndex = 0;
|
||||
frame.allocatedSetsThisFrame = 0;
|
||||
frame.peakAllocatedSetsThisFrame = 0;
|
||||
frame.descriptorPools.clear();
|
||||
|
||||
VkDescriptorPool initialPool = VK_NULL_HANDLE;
|
||||
if (!CreateDescriptorPool(m_setsPerFrame, initialPool)) {
|
||||
MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u",
|
||||
frameIndex);
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0});
|
||||
MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex, m_setsPerFrame);
|
||||
|
||||
const Bool created = frame.uploadBuffer.Create(
|
||||
m_allocator, m_perFrameUploadBytes, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO,
|
||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT);
|
||||
if (!created) {
|
||||
MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame upload buffer %u", frameIndex);
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UniformDescriptorBinder::Shutdown() {
|
||||
for (auto& frame : m_frames) {
|
||||
frame.uploadBuffer.Destroy();
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
for (auto& bucket : frame.descriptorPools) {
|
||||
if (bucket.handle != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(m_device, bucket.handle, nullptr);
|
||||
bucket.handle = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
frame.descriptorPools.clear();
|
||||
frame.activeDescriptorPoolIndex = 0;
|
||||
frame.allocatedSetsThisFrame = 0;
|
||||
frame.peakAllocatedSetsThisFrame = 0;
|
||||
frame.writeCursor = 0;
|
||||
}
|
||||
m_frames.clear();
|
||||
DestroyProgramLayouts();
|
||||
|
||||
m_allocator = nullptr;
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_minDynamicOffsetAlignment = 1;
|
||||
m_perFrameUploadBytes = 0;
|
||||
m_frameCount = 0;
|
||||
m_maxBindings = 0;
|
||||
m_setsPerFrame = 0;
|
||||
m_peakDescriptorSetsObserved = 0;
|
||||
m_textureSamplerManager = nullptr;
|
||||
m_framebufferManager = nullptr;
|
||||
}
|
||||
|
||||
void UniformDescriptorBinder::BeginFrame(Uint32 frameIndex) {
|
||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index");
|
||||
auto& frame = m_frames[frameIndex];
|
||||
if (frame.peakAllocatedSetsThisFrame > m_peakDescriptorSetsObserved) {
|
||||
m_peakDescriptorSetsObserved = frame.peakAllocatedSetsThisFrame;
|
||||
MGLOG_D(
|
||||
"UniformDescriptorBinder: new descriptor set peak observed=%u (base setsPerFrame=%u, frame=%u, pools=%zu)",
|
||||
m_peakDescriptorSetsObserved, m_setsPerFrame, frameIndex, frame.descriptorPools.size());
|
||||
}
|
||||
frame.writeCursor = 0;
|
||||
frame.activeDescriptorPoolIndex = 0;
|
||||
frame.allocatedSetsThisFrame = 0;
|
||||
frame.peakAllocatedSetsThisFrame = 0;
|
||||
for (auto& bucket : frame.descriptorPools) {
|
||||
bucket.allocatedSets = 0;
|
||||
if (bucket.handle == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
VK_VERIFY(vkResetDescriptorPool(m_device, bucket.handle, 0),
|
||||
"UniformDescriptorBinder::BeginFrame, vkResetDescriptorPool");
|
||||
}
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::ReflectBindingKinds(const MG_State::GLState::ProgramObject& program,
|
||||
Vector<BindingKind>& outKinds) const {
|
||||
outKinds.assign(m_maxBindings, BindingKind::None);
|
||||
|
||||
const auto& spirv = program.GetGeneratedSpirv();
|
||||
for (const auto& module : spirv) {
|
||||
if (module.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spvc_context context = nullptr;
|
||||
spvc_parsed_ir ir = nullptr;
|
||||
spvc_compiler compiler = nullptr;
|
||||
spvc_resources resources = nullptr;
|
||||
|
||||
if (spvc_context_create(&context) != SPVC_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const spvc_result parseResult = spvc_context_parse_spirv(context, module.data(), module.size(), &ir);
|
||||
if (parseResult != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
|
||||
const spvc_result compilerResult =
|
||||
spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler);
|
||||
if (compilerResult != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto applyBindings = [&](spvc_resource_type resourceType, BindingKind kind) {
|
||||
const spvc_reflected_resource* list = nullptr;
|
||||
size_t count = 0;
|
||||
if (spvc_resources_get_resource_list_for_type(resources, resourceType, &list, &count) != SPVC_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const Uint32 binding =
|
||||
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
|
||||
if (binding >= m_maxBindings) {
|
||||
continue;
|
||||
}
|
||||
if (kind == BindingKind::CombinedImageSampler) {
|
||||
outKinds[binding] = BindingKind::CombinedImageSampler;
|
||||
} else if (outKinds[binding] == BindingKind::None) {
|
||||
outKinds[binding] = kind;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
applyBindings(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, BindingKind::UniformBufferDynamic);
|
||||
applyBindings(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, BindingKind::CombinedImageSampler);
|
||||
|
||||
spvc_context_destroy(context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program,
|
||||
ProgramLayout& layout) const {
|
||||
layout.samplerUniformLocationByBinding.assign(m_maxBindings, -1);
|
||||
layout.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D);
|
||||
|
||||
const auto& spirv = program.GetGeneratedSpirv();
|
||||
for (const auto& module : spirv) {
|
||||
if (module.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spvc_context context = nullptr;
|
||||
spvc_parsed_ir ir = nullptr;
|
||||
spvc_compiler compiler = nullptr;
|
||||
spvc_resources resources = nullptr;
|
||||
|
||||
if (spvc_context_create(&context) != SPVC_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
|
||||
&compiler) != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
|
||||
const spvc_reflected_resource* list = nullptr;
|
||||
size_t count = 0;
|
||||
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, &list, &count) ==
|
||||
SPVC_SUCCESS) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const Uint32 binding =
|
||||
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
|
||||
if (binding >= m_maxBindings) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String uniformName = list[i].name ? list[i].name : "";
|
||||
Int location = program.GetUniformLocation(uniformName);
|
||||
if (location < 0) {
|
||||
const auto arraySuffix = uniformName.find("[0]");
|
||||
if (arraySuffix != String::npos) {
|
||||
uniformName = uniformName.substr(0, arraySuffix);
|
||||
location = program.GetUniformLocation(uniformName);
|
||||
}
|
||||
}
|
||||
if (location < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
layout.samplerUniformLocationByBinding[binding] = location;
|
||||
layout.samplerTextureTargetByBinding[binding] =
|
||||
UniformTypeToTextureTarget(program.GetUniformType(static_cast<Uint>(location)));
|
||||
}
|
||||
}
|
||||
|
||||
spvc_context_destroy(context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program,
|
||||
ProgramLayout& layout) const {
|
||||
layout.globalUboBinding = -1;
|
||||
|
||||
const auto& spirv = program.GetGeneratedSpirv();
|
||||
for (const auto& module : spirv) {
|
||||
if (module.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spvc_context context = nullptr;
|
||||
spvc_parsed_ir ir = nullptr;
|
||||
spvc_compiler compiler = nullptr;
|
||||
spvc_resources resources = nullptr;
|
||||
|
||||
if (spvc_context_create(&context) != SPVC_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
|
||||
&compiler) != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
|
||||
spvc_context_destroy(context);
|
||||
continue;
|
||||
}
|
||||
|
||||
const spvc_reflected_resource* list = nullptr;
|
||||
size_t count = 0;
|
||||
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count) ==
|
||||
SPVC_SUCCESS) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const char* name = list[i].name ? list[i].name : "";
|
||||
if (std::strstr(name, MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const Uint32 binding =
|
||||
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
|
||||
if (binding < m_maxBindings) {
|
||||
layout.globalUboBinding = static_cast<Int>(binding);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spvc_context_destroy(context);
|
||||
if (layout.globalUboBinding >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramLayout& layout, Uint32 binding,
|
||||
VkDescriptorImageInfo& outImageInfo) const {
|
||||
if (!m_textureSamplerManager || !MG_State::pGLContext || binding >= layout.samplerUniformLocationByBinding.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Int location = layout.samplerUniformLocationByBinding[binding];
|
||||
if (location < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||
if (unit < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||
const auto samplerOverride = textureUnit.GetSamplerObject();
|
||||
|
||||
const TextureTarget preferredTarget = layout.samplerTextureTargetByBinding[binding];
|
||||
auto texture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject();
|
||||
if (!texture) {
|
||||
auto& slots = textureUnit.GetAllBindingSlots();
|
||||
for (auto& slot : slots) {
|
||||
texture = slot.GetBoundObject();
|
||||
if (texture) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_framebufferManager &&
|
||||
m_framebufferManager->TransitionOffscreenColorTextureToShaderRead(commandBuffer, texture->GetExternalIndex())) {
|
||||
VkImageView offscreenView = VK_NULL_HANDLE;
|
||||
if (m_framebufferManager->GetOffscreenColorViewByTexture(texture->GetExternalIndex(), offscreenView) &&
|
||||
offscreenView != VK_NULL_HANDLE) {
|
||||
VkDescriptorImageInfo sampledInfo{};
|
||||
if (!m_textureSamplerManager->SyncTextureAndGetDescriptor(*texture, samplerOverride.get(), sampledInfo)) {
|
||||
return false;
|
||||
}
|
||||
sampledInfo.imageView = offscreenView;
|
||||
sampledInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
outImageInfo = sampledInfo;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return m_textureSamplerManager->SyncTextureAndGetDescriptor(*texture, samplerOverride.get(), outImageInfo);
|
||||
}
|
||||
|
||||
UniformDescriptorBinder::ProgramLayout* UniformDescriptorBinder::GetOrCreateProgramLayout(
|
||||
const MG_State::GLState::ProgramObject& program) {
|
||||
const Uint64 hash = ComputeProgramHash(program);
|
||||
auto it = m_programLayouts.find(hash);
|
||||
if (it != m_programLayouts.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
ProgramLayout layout{};
|
||||
layout.hash = hash;
|
||||
if (!ReflectBindingKinds(program, layout.bindingKinds)) {
|
||||
MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: reflection failed");
|
||||
return nullptr;
|
||||
}
|
||||
if (!ReflectSamplerBindings(program, layout)) {
|
||||
MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: sampler reflection failed");
|
||||
return nullptr;
|
||||
}
|
||||
if (!ReflectGlobalUboBinding(program, layout)) {
|
||||
MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: global UBO reflection failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Vector<VkDescriptorSetLayoutBinding> bindings;
|
||||
bindings.reserve(m_maxBindings);
|
||||
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
|
||||
const auto kind = layout.bindingKinds[binding];
|
||||
if (kind == BindingKind::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutBinding layoutBinding{};
|
||||
layoutBinding.binding = binding;
|
||||
layoutBinding.descriptorCount = 1;
|
||||
layoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
|
||||
layoutBinding.pImmutableSamplers = nullptr;
|
||||
if (kind == BindingKind::UniformBufferDynamic) {
|
||||
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
layout.dynamicBindings.push_back(binding);
|
||||
} else {
|
||||
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
}
|
||||
bindings.push_back(layoutBinding);
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
|
||||
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
setLayoutInfo.bindingCount = static_cast<Uint32>(bindings.size());
|
||||
setLayoutInfo.pBindings = bindings.data();
|
||||
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &layout.descriptorSetLayout),
|
||||
"UniformDescriptorBinder::GetOrCreateProgramLayout, vkCreateDescriptorSetLayout");
|
||||
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
||||
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
pipelineLayoutInfo.setLayoutCount = 1;
|
||||
pipelineLayoutInfo.pSetLayouts = &layout.descriptorSetLayout;
|
||||
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &layout.pipelineLayout),
|
||||
"UniformDescriptorBinder::GetOrCreateProgramLayout, vkCreatePipelineLayout");
|
||||
|
||||
auto [insertIt, _] = m_programLayouts.emplace(hash, std::move(layout));
|
||||
return &insertIt->second;
|
||||
}
|
||||
|
||||
VkPipelineLayout UniformDescriptorBinder::GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program) {
|
||||
auto* layout = GetOrCreateProgramLayout(program);
|
||||
return layout ? layout->pipelineLayout : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset) {
|
||||
const VkDeviceSize alignedOffset = AlignUp(frame.writeCursor, m_minDynamicOffsetAlignment);
|
||||
if (alignedOffset + size > m_perFrameUploadBytes) {
|
||||
return false;
|
||||
}
|
||||
outOffset = alignedOffset;
|
||||
frame.writeCursor = alignedOffset + size;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program,
|
||||
Vector<const void*>& outData,
|
||||
Vector<VkDeviceSize>& outSizes) const {
|
||||
outData.assign(m_maxBindings, nullptr);
|
||||
outSizes.assign(m_maxBindings, 0);
|
||||
|
||||
if (MG_State::pGLContext == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Uint32 activeUniformBlockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
|
||||
const Uint32 uniformBindingPointCount =
|
||||
static_cast<Uint32>(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform));
|
||||
|
||||
for (Uint32 blockIndex = 0; blockIndex < activeUniformBlockCount; ++blockIndex) {
|
||||
const Uint32 binding = program.GetUniformBlockBinding(blockIndex);
|
||||
if (binding >= m_maxBindings) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VkDeviceSize blockSize = static_cast<VkDeviceSize>(program.GetUBOSizeAt(blockIndex));
|
||||
if (blockSize == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (binding >= uniformBindingPointCount) {
|
||||
continue;
|
||||
}
|
||||
auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding);
|
||||
const auto bufferObject = bindingPoint.GetBoundObject();
|
||||
if (!bufferObject) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto bufferData = bufferObject->GetDataReadOnly();
|
||||
if (!bufferData || bufferData->empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto range = bindingPoint.GetRange();
|
||||
const VkDeviceSize bufferSize = static_cast<VkDeviceSize>(bufferObject->GetSize());
|
||||
VkDeviceSize rangeStart = static_cast<VkDeviceSize>(range.start);
|
||||
VkDeviceSize rangeEnd = static_cast<VkDeviceSize>(range.end);
|
||||
|
||||
if (rangeStart >= bufferSize) {
|
||||
continue;
|
||||
}
|
||||
if (rangeEnd <= rangeStart || rangeEnd > bufferSize) {
|
||||
rangeEnd = bufferSize;
|
||||
}
|
||||
|
||||
VkDeviceSize available = rangeEnd - rangeStart;
|
||||
if (available == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outData[binding] = bufferData->data() + static_cast<SizeT>(rangeStart);
|
||||
outSizes[binding] = std::min(blockSize, available);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const {
|
||||
outPool = VK_NULL_HANDLE;
|
||||
if (m_device == VK_NULL_HANDLE || maxSets == 0 || m_maxBindings == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Uint64 descriptorCount64 = static_cast<Uint64>(maxSets) * static_cast<Uint64>(m_maxBindings);
|
||||
if (descriptorCount64 > static_cast<Uint64>(std::numeric_limits<Uint32>::max())) {
|
||||
MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Uint32 descriptorCount = static_cast<Uint32>(descriptorCount64);
|
||||
VkDescriptorPoolSize poolSizes[2]{};
|
||||
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
poolSizes[0].descriptorCount = descriptorCount;
|
||||
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
poolSizes[1].descriptorCount = descriptorCount;
|
||||
|
||||
VkDescriptorPoolCreateInfo poolInfo{};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
poolInfo.maxSets = maxSets;
|
||||
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
|
||||
poolInfo.pPoolSizes = poolSizes;
|
||||
|
||||
const VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &outPool);
|
||||
if (result != VK_SUCCESS) {
|
||||
MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d", result);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) {
|
||||
if (frame.descriptorPools.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& currentBucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
|
||||
const Uint32 currentMaxSets = std::max<Uint32>(1, currentBucket.maxSets);
|
||||
const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits<Uint32>::max() / 2) ? (currentMaxSets * 2)
|
||||
: currentMaxSets;
|
||||
|
||||
VkDescriptorPool grownPool = VK_NULL_HANDLE;
|
||||
if (!CreateDescriptorPool(grownMaxSets, grownPool)) {
|
||||
MGLOG_E("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)",
|
||||
currentMaxSets, grownMaxSets);
|
||||
return false;
|
||||
}
|
||||
|
||||
frame.descriptorPools.push_back({grownPool, grownMaxSets, 0});
|
||||
frame.activeDescriptorPoolIndex = static_cast<Uint32>(frame.descriptorPools.size() - 1);
|
||||
MGLOG_D(
|
||||
"UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu",
|
||||
frameIndex, currentMaxSets, grownMaxSets, frame.descriptorPools.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool UniformDescriptorBinder::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
Uint32 frameIndex) {
|
||||
if (m_frames.empty()) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: binder is not initialized");
|
||||
return false;
|
||||
}
|
||||
if (frameIndex >= m_frames.size()) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: invalid frame index %u", frameIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
ProgramLayout* layout = GetOrCreateProgramLayout(program);
|
||||
if (!layout || layout->pipelineLayout == VK_NULL_HANDLE || layout->descriptorSetLayout == VK_NULL_HANDLE) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot get program layout");
|
||||
return false;
|
||||
}
|
||||
if (layout->pipelineLayout != pipelineLayout) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: pipelineLayout mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& frame = m_frames[frameIndex];
|
||||
if (frame.descriptorPools.empty()) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid");
|
||||
return false;
|
||||
}
|
||||
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) {
|
||||
frame.activeDescriptorPoolIndex = 0;
|
||||
}
|
||||
|
||||
VkDescriptorSetAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
allocInfo.descriptorSetCount = 1;
|
||||
allocInfo.pSetLayouts = &layout->descriptorSetLayout;
|
||||
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
|
||||
|
||||
auto allocateFromActivePool = [&](VkResult& outResult) {
|
||||
auto& bucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
|
||||
allocInfo.descriptorPool = bucket.handle;
|
||||
outResult = vkAllocateDescriptorSets(m_device, &allocInfo, &descriptorSet);
|
||||
if (outResult == VK_SUCCESS) {
|
||||
++bucket.allocatedSets;
|
||||
++frame.allocatedSetsThisFrame;
|
||||
frame.peakAllocatedSetsThisFrame = std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame);
|
||||
}
|
||||
};
|
||||
|
||||
VkResult allocResult = VK_SUCCESS;
|
||||
allocateFromActivePool(allocResult);
|
||||
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
|
||||
if (!GrowFrameDescriptorPool(frame, frameIndex)) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor pool growth failed");
|
||||
return false;
|
||||
}
|
||||
allocateFromActivePool(allocResult);
|
||||
}
|
||||
if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d",
|
||||
allocResult);
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector<const void*> bindingData;
|
||||
Vector<VkDeviceSize> bindingSizes;
|
||||
if (!GatherBindingPayloads(program, bindingData, bindingSizes)) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot gather UBO payloads");
|
||||
return false;
|
||||
}
|
||||
|
||||
static const Uint8 kFallbackData[16] = {};
|
||||
VkDescriptorImageInfo fallbackImageInfo{};
|
||||
const Bool hasFallbackImage = m_textureSamplerManager && m_textureSamplerManager->GetFallbackDescriptor(fallbackImageInfo);
|
||||
|
||||
Vector<VkWriteDescriptorSet> writes;
|
||||
writes.reserve(m_maxBindings);
|
||||
Vector<VkDescriptorBufferInfo> bufferInfos;
|
||||
Vector<VkDescriptorImageInfo> imageInfos;
|
||||
Vector<Uint32> dynamicOffsets;
|
||||
bufferInfos.reserve(m_maxBindings);
|
||||
imageInfos.reserve(m_maxBindings);
|
||||
dynamicOffsets.reserve(layout->dynamicBindings.size());
|
||||
|
||||
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
|
||||
const auto kind = layout->bindingKinds[binding];
|
||||
if (kind == BindingKind::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VkWriteDescriptorSet write{};
|
||||
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
write.dstSet = descriptorSet;
|
||||
write.dstBinding = binding;
|
||||
write.dstArrayElement = 0;
|
||||
write.descriptorCount = 1;
|
||||
|
||||
if (kind == BindingKind::UniformBufferDynamic) {
|
||||
const void* payload = bindingData[binding];
|
||||
VkDeviceSize payloadSize = bindingSizes[binding];
|
||||
if (payload == nullptr || payloadSize == 0) {
|
||||
if (layout->globalUboBinding == static_cast<Int>(binding)) {
|
||||
const void* globalUboData = program.GetUBOData();
|
||||
const VkDeviceSize globalUboSize = static_cast<VkDeviceSize>(program.GetUBOSize());
|
||||
if (globalUboData != nullptr && globalUboSize > 0) {
|
||||
payload = globalUboData;
|
||||
payloadSize = globalUboSize;
|
||||
}
|
||||
}
|
||||
if (payload == nullptr || payloadSize == 0) {
|
||||
payload = kFallbackData;
|
||||
payloadSize = sizeof(kFallbackData);
|
||||
}
|
||||
}
|
||||
|
||||
VkDeviceSize payloadOffset = 0;
|
||||
if (!AllocateUploadRegion(frame, payloadSize, payloadOffset)) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame upload buffer exhausted");
|
||||
return false;
|
||||
}
|
||||
if (!frame.uploadBuffer.Upload(payload, payloadSize, payloadOffset)) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
|
||||
binding);
|
||||
return false;
|
||||
}
|
||||
|
||||
VkDescriptorBufferInfo bufferInfo{};
|
||||
bufferInfo.buffer = frame.uploadBuffer.GetHandle();
|
||||
bufferInfo.offset = 0;
|
||||
bufferInfo.range = payloadSize;
|
||||
bufferInfos.push_back(bufferInfo);
|
||||
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
write.pBufferInfo = &bufferInfos.back();
|
||||
writes.push_back(write);
|
||||
dynamicOffsets.push_back(static_cast<Uint32>(payloadOffset));
|
||||
} else {
|
||||
VkDescriptorImageInfo imageInfo{};
|
||||
Bool hasImage = ResolveSamplerDescriptor(commandBuffer, program, *layout, binding, imageInfo);
|
||||
if (!hasImage) {
|
||||
if (!hasFallbackImage) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: fallback sampler/texture is unavailable");
|
||||
return false;
|
||||
}
|
||||
imageInfo = fallbackImageInfo;
|
||||
}
|
||||
if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) {
|
||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: fallback sampler/texture is unavailable");
|
||||
return false;
|
||||
}
|
||||
imageInfos.push_back(imageInfo);
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
write.pImageInfo = &imageInfos.back();
|
||||
writes.push_back(write);
|
||||
}
|
||||
}
|
||||
|
||||
if (!writes.empty()) {
|
||||
vkUpdateDescriptorSets(m_device, static_cast<Uint32>(writes.size()), writes.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet,
|
||||
static_cast<Uint32>(dynamicOffsets.size()), dynamicOffsets.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
void UniformDescriptorBinder::DestroyProgramLayouts() {
|
||||
for (auto& [_, layout] : m_programLayouts) {
|
||||
if (layout.pipelineLayout != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(m_device, layout.pipelineLayout, nullptr);
|
||||
layout.pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
if (layout.descriptorSetLayout != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorSetLayout(m_device, layout.descriptorSetLayout, nullptr);
|
||||
layout.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
m_programLayouts.clear();
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "VkBufferObject.h"
|
||||
#include "VkTextureSamplerManager.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ProgramObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VkFramebufferManager;
|
||||
|
||||
class UniformDescriptorBinder {
|
||||
public:
|
||||
enum class BindingKind : Uint8 {
|
||||
None = 0,
|
||||
UniformBufferDynamic,
|
||||
CombinedImageSampler
|
||||
};
|
||||
|
||||
Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment,
|
||||
Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
|
||||
VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024,
|
||||
VkTextureSamplerManager* textureSamplerManager = nullptr,
|
||||
VkFramebufferManager* framebufferManager = nullptr);
|
||||
void Shutdown();
|
||||
|
||||
void BeginFrame(Uint32 frameIndex);
|
||||
VkPipelineLayout GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program);
|
||||
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout,
|
||||
const MG_State::GLState::ProgramObject& program, Uint32 frameIndex);
|
||||
|
||||
private:
|
||||
struct DescriptorPoolBucket {
|
||||
VkDescriptorPool handle = VK_NULL_HANDLE;
|
||||
Uint32 maxSets = 0;
|
||||
Uint32 allocatedSets = 0;
|
||||
};
|
||||
|
||||
struct FrameResources {
|
||||
VkBufferObject uploadBuffer;
|
||||
Vector<DescriptorPoolBucket> descriptorPools;
|
||||
Uint32 activeDescriptorPoolIndex = 0;
|
||||
Uint32 allocatedSetsThisFrame = 0;
|
||||
Uint32 peakAllocatedSetsThisFrame = 0;
|
||||
VkDeviceSize writeCursor = 0;
|
||||
};
|
||||
|
||||
struct ProgramLayout {
|
||||
Uint64 hash = 0;
|
||||
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
Vector<BindingKind> bindingKinds;
|
||||
Vector<Uint32> dynamicBindings;
|
||||
Vector<Int> samplerUniformLocationByBinding;
|
||||
Vector<TextureTarget> samplerTextureTargetByBinding;
|
||||
Int globalUboBinding = -1;
|
||||
};
|
||||
|
||||
static VkDeviceSize AlignUp(VkDeviceSize value, VkDeviceSize alignment);
|
||||
static Uint64 ComputeProgramHash(const MG_State::GLState::ProgramObject& program);
|
||||
static Bool IsSamplerUniformType(GLenum glType);
|
||||
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
|
||||
Bool ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, ProgramLayout& layout) const;
|
||||
Bool ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, ProgramLayout& layout) const;
|
||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramLayout& layout, Uint32 binding,
|
||||
VkDescriptorImageInfo& outImageInfo) const;
|
||||
Bool ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, Vector<BindingKind>& outKinds) const;
|
||||
ProgramLayout* GetOrCreateProgramLayout(const MG_State::GLState::ProgramObject& program);
|
||||
Bool AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset);
|
||||
Bool GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector<const void*>& outData,
|
||||
Vector<VkDeviceSize>& outSizes) const;
|
||||
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
||||
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
||||
void DestroyProgramLayouts();
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
Vector<FrameResources> m_frames;
|
||||
UnorderedMap<Uint64, ProgramLayout> m_programLayouts;
|
||||
|
||||
VkDeviceSize m_minDynamicOffsetAlignment = 1;
|
||||
VkDeviceSize m_perFrameUploadBytes = 0;
|
||||
Uint32 m_frameCount = 0;
|
||||
Uint32 m_maxBindings = 0;
|
||||
Uint32 m_setsPerFrame = 0;
|
||||
Uint32 m_peakDescriptorSetsObserved = 0;
|
||||
VkTextureSamplerManager* m_textureSamplerManager = nullptr;
|
||||
VkFramebufferManager* m_framebufferManager = nullptr;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,182 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ProgramFactory.h"
|
||||
#include "VkBufferManager.h"
|
||||
#include "VkSamplerManager.h"
|
||||
#include "VkTextureManager.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ITextureObject;
|
||||
class ProgramObject;
|
||||
class SamplerObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class UniformManager {
|
||||
public:
|
||||
struct SamplerBindingOverride {
|
||||
Uint32 binding = 0;
|
||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
Bool Initialize(VkDevice device, VkBufferManager* bufferManager,
|
||||
ProgramFactory* programFactory,
|
||||
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
|
||||
Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
|
||||
VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr);
|
||||
void Shutdown();
|
||||
|
||||
void BeginFrame(Uint32 frameIndex);
|
||||
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<MG_State::GLState::ITextureObject*>& outTextures);
|
||||
Bool CollectStorageImageTextures(const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Vector<MG_State::GLState::ITextureObject*>& outTextures) const;
|
||||
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 frameIndex,
|
||||
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
const SamplerBindingOverride* samplerBindingOverride = nullptr);
|
||||
|
||||
// Pure format-policy helper kept public for host regression tests. Formatted storage
|
||||
// images use their shader qualifier; transformed float images use glBindImageTexture's
|
||||
// format and never silently fall back to the backing image format.
|
||||
static VkFormat ResolveStorageImageViewFormat(VkFormat reflectedFormat, GLenum bindingFormat,
|
||||
VkFormat resourceFormat, Bool useBindingFormat);
|
||||
|
||||
private:
|
||||
struct DescriptorPoolBucket {
|
||||
VkDescriptorPool handle = VK_NULL_HANDLE;
|
||||
Uint32 maxSets = 0;
|
||||
Uint32 allocatedSets = 0;
|
||||
};
|
||||
|
||||
struct DescriptorSetCacheEntry {
|
||||
Vector<VkDescriptorSet> sets;
|
||||
Uint32 cursor = 0;
|
||||
};
|
||||
|
||||
struct FrameResources {
|
||||
Vector<DescriptorPoolBucket> descriptorPools;
|
||||
UnorderedMap<VkDescriptorSetLayout, DescriptorSetCacheEntry> descriptorSetCacheByLayout;
|
||||
Vector<VkBufferView> texelBufferViews;
|
||||
Uint32 activeDescriptorPoolIndex = 0;
|
||||
Uint32 allocatedSetsThisFrame = 0;
|
||||
Uint32 peakAllocatedSetsThisFrame = 0;
|
||||
};
|
||||
|
||||
static Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||
// Raw-pointer variant for the per-draw sampled-texture walk (CollectSampledTextures):
|
||||
// the bound texture stays alive through the draw via GL binding state, so callers that
|
||||
// only need the pointer skip the SharedPtr copy's atomic refcount churn.
|
||||
static MG_State::GLState::ITextureObject* ResolveSamplerTextureRaw(
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding);
|
||||
SharedPtr<MG_State::GLState::ITextureObject> GetFallbackTexture(TextureTarget target) const;
|
||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
VkDescriptorImageInfo& outImageInfo) const;
|
||||
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
||||
VkDescriptorImageInfo& outImageInfo) const;
|
||||
Bool ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 frameIndex, VkBufferView& outBufferView);
|
||||
Bool ResolveStorageBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
VkDescriptorBufferInfo& outBufferInfo) const;
|
||||
Bool ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
VkDescriptorImageInfo& outImageInfo) const;
|
||||
// Result of resolving a UBO binding: either a zero-copy direct bind to the app's resident
|
||||
// VkBuffer (the GLES backend's approach - no per-draw copy) or the CPU payload to upload.
|
||||
struct UboBindResult {
|
||||
Bool directBindable = false;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceSize range = 0; // reflected block size; constant across draws (hashed)
|
||||
VkDeviceSize dynamicOffset = 0; // block range start; moves per draw (NOT hashed)
|
||||
const void* payload = nullptr; // fallback UploadTransient path
|
||||
VkDeviceSize payloadSize = 0;
|
||||
};
|
||||
Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
UboBindResult& out) const;
|
||||
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
||||
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
||||
VkResult AllocateDescriptorSetsFromActivePool(
|
||||
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
|
||||
VkResult AcquireDescriptorSet(Uint32 frameIndex,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
VkDescriptorSet& outDescriptorSet);
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkBufferManager* m_bufferManager = nullptr;
|
||||
ProgramFactory* m_programFactory = nullptr;
|
||||
Vector<FrameResources> m_frames;
|
||||
|
||||
VkDeviceSize m_minDynamicOffsetAlignment = 1;
|
||||
Uint32 m_frameCount = 0;
|
||||
Uint32 m_maxBindings = 0;
|
||||
Uint32 m_setsPerFrame = 0;
|
||||
Uint32 m_peakDescriptorSetsObserved = 0;
|
||||
VkTextureManager* m_textureManager = nullptr;
|
||||
VkSamplerManager* m_samplerManager = nullptr;
|
||||
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2D;
|
||||
|
||||
// Per-draw scratch buffers for BindProgramUniformBuffers: reused (clear keeps
|
||||
// capacity) so the descriptor-write path stops allocating on every draw.
|
||||
Vector<VkWriteDescriptorSet> m_writesScratch;
|
||||
Vector<VkDescriptorBufferInfo> m_bufferInfosScratch;
|
||||
Vector<VkDescriptorImageInfo> m_imageInfosScratch;
|
||||
Vector<VkBufferView> m_texelBufferViewsScratch;
|
||||
Vector<Uint32> m_dynamicOffsetsScratch;
|
||||
|
||||
// Descriptor-set reuse across consecutive draws (see BindProgramUniformBuffers).
|
||||
// When a draw's resolved descriptor content is byte-identical to the previous
|
||||
// draw's, reuse the same VkDescriptorSet and skip AcquireDescriptorSet +
|
||||
// vkUpdateDescriptorSets - only the bind-time dynamic offsets differ. Reset each
|
||||
// frame in BeginFrame because the frame's descriptor sets are recycled there.
|
||||
VkDescriptorSet m_lastBoundDescriptorSet = VK_NULL_HANDLE;
|
||||
Uint64 m_lastDescriptorSignature = 0;
|
||||
Bool m_hasLastDescriptor = false;
|
||||
|
||||
// Per-binding fast path over VkSamplerManager's content-hashed sampler cache, which
|
||||
// stays the source of truth: its key hashes all sampler+texture state, so two distinct
|
||||
// sampler objects with identical state still resolve to one VkSampler. This memo only
|
||||
// skips recomputing that hash. Across a draw batch the bound sampler set is stable, so a
|
||||
// binding whose sampler (lifetime id + version, bumped on every setter) and texture
|
||||
// (lifetime id + params version, bumped on the format/border-color setters that feed the
|
||||
// key) are unchanged recycles the VkSampler it resolved last draw; a param change bumps
|
||||
// a version and forces a re-resolve. Both objects are keyed by a never-reused monotonic
|
||||
// lifetime id, so a freed-and-reallocated sampler or texture at the same heap address
|
||||
// always gets a fresh id and misses (a raw pointer would false-hit that ABA) - so a
|
||||
// stale guess can only miss and fall through to the hash, never resolve wrong. Still
|
||||
// reset each frame alongside the descriptor-set cache. Indexed by binding.
|
||||
struct SamplerResolveMemo {
|
||||
Uint64 samplerLifetimeId = 0;
|
||||
Uint64 textureLifetimeId = 0;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
Uint16 samplerVersion = 0;
|
||||
Uint16 textureParamsVersion = 0;
|
||||
Bool forceNearestFiltering = false;
|
||||
Bool valid = false;
|
||||
};
|
||||
mutable Vector<SamplerResolveMemo> m_samplerResolveMemo;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -29,6 +29,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsBgra, sizeof(attr.IsBgra)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||
|
||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||
@@ -38,70 +39,122 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return XXH64_digest(m_hashState);
|
||||
}
|
||||
|
||||
VertexInputStateFactory::HashType VertexInputStateFactory::GetOrComputeHash(
|
||||
const MG_State::GLState::VertexArrayObject& vao) const {
|
||||
HashType hash = 0;
|
||||
if (!vao.GetBackendHashMemo(hash)) {
|
||||
hash = ComputeHash(vao);
|
||||
vao.SetBackendHashMemo(hash);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||
const MG_State::GLState::VertexArrayObject& vao) {
|
||||
const HashType hash = ComputeHash(vao);
|
||||
return GetOrCreateVertexInputState(vao, GetOrComputeHash(vao));
|
||||
}
|
||||
|
||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash) {
|
||||
auto it = m_cache.find(hash);
|
||||
if (it != m_cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
VertexInputStateBuilder builder;
|
||||
UnorderedMap<SizeT, Uint32> bindingByBufferKey;
|
||||
UnorderedMap<SizeT, Uint32> strideByBufferKey;
|
||||
UnorderedMap<SizeT, VkVertexInputRate> inputRateByBufferKey;
|
||||
Vector<SizeT> bindingBufferKeys;
|
||||
Vector<SizeT> bindingBaseOffsets;
|
||||
Vector<Uint32> bindingAttributeLocations;
|
||||
Vector<Bool> bindingUsesClientMemory;
|
||||
Vector<VertexStreamConversion> bindingConversions;
|
||||
Uint32 unsupportedAttribMask = 0;
|
||||
|
||||
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
|
||||
const auto& attr = vao.GetAttribute(location);
|
||||
if (!attr.Enabled || !attr.Buffer) {
|
||||
if (!attr.Enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto vkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger);
|
||||
if (vkFormat == VK_FORMAT_UNDEFINED) {
|
||||
MGLOG_D("Skipping unsupported vertex attribute layout (location=%u, type=%s, size=%d)",
|
||||
const VkFormat sourceVkFormat =
|
||||
ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra);
|
||||
if (sourceVkFormat == VK_FORMAT_UNDEFINED) {
|
||||
MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is "
|
||||
"enabled but cannot be mapped to a VkFormat",
|
||||
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
|
||||
unsupportedAttribMask |= (1u << location);
|
||||
continue;
|
||||
}
|
||||
|
||||
const SizeT componentSize = GetComponentSize(attr.Type);
|
||||
if (componentSize == 0) {
|
||||
MGLOG_D("Skipping vertex attribute with unknown component size (location=%u, type=%s)",
|
||||
VkFormat vkFormat = sourceVkFormat;
|
||||
VertexStreamConversion conversion = VertexStreamConversion::None;
|
||||
if (!SupportsVertexBufferFormat(vkFormat)) {
|
||||
if (IsScaledIntegerVertexFormat(vkFormat)) {
|
||||
const VkFormat fallbackFormat = ToFloat32VertexFormat(attr.Size);
|
||||
if (fallbackFormat != VK_FORMAT_UNDEFINED && SupportsVertexBufferFormat(fallbackFormat)) {
|
||||
vkFormat = fallbackFormat;
|
||||
conversion = VertexStreamConversion::ScaledIntegerToFloat32;
|
||||
MGLOG_W("Vertex attribute location=%u format=%d lacks "
|
||||
"VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT; using float32 stream format=%d "
|
||||
"(type=%s size=%d normalized=%s integer=%s)",
|
||||
location, static_cast<Int>(sourceVkFormat), static_cast<Int>(vkFormat),
|
||||
MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size,
|
||||
attr.Normalized ? "true" : "false", attr.IsInteger ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
if (conversion == VertexStreamConversion::None) {
|
||||
MGLOG_E("Unsupported Vulkan vertex format (location=%u, format=%d, type=%s, size=%d): "
|
||||
"VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT is unavailable and no semantic fallback exists",
|
||||
location, static_cast<Int>(sourceVkFormat),
|
||||
MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
|
||||
unsupportedAttribMask |= (1u << location);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const SizeT attribByteSize = GetAttributeByteSize(attr.Type, attr.Size, attr.IsBgra);
|
||||
if (attribByteSize == 0) {
|
||||
MGLOG_E("Vertex attribute with unknown component size (location=%u, type=%s): the array is "
|
||||
"enabled but cannot be sized",
|
||||
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str());
|
||||
unsupportedAttribMask |= (1u << location);
|
||||
continue;
|
||||
}
|
||||
|
||||
const Uint32 stride = attr.Stride > 0
|
||||
? static_cast<Uint32>(attr.Stride)
|
||||
: static_cast<Uint32>(componentSize * static_cast<SizeT>(attr.Size));
|
||||
const Uint32 sourceStride =
|
||||
attr.Stride > 0 ? static_cast<Uint32>(attr.Stride) : static_cast<Uint32>(attribByteSize);
|
||||
const Bool packedAttribute = attr.Type == DataType::Int2101010Rev ||
|
||||
attr.Type == DataType::Uint2101010Rev;
|
||||
const SizeT requiredAlignment = packedAttribute ? attribByteSize : GetComponentSize(attr.Type);
|
||||
if (conversion == VertexStreamConversion::None && requiredAlignment > 1 &&
|
||||
((sourceStride % requiredAlignment) != 0 || (attr.Offset % requiredAlignment) != 0)) {
|
||||
// GL accepts arbitrary byte strides and offsets. Core Vulkan vertex fetches do not
|
||||
// unless VK_EXT_legacy_vertex_attributes is available, so deinterleave this one
|
||||
// attribute into a tightly packed transient stream without changing its format.
|
||||
conversion = VertexStreamConversion::Repack;
|
||||
MGLOG_W("Vertex attribute location=%u uses Vulkan-incompatible alignment "
|
||||
"(offset=%zu stride=%u required=%zu); using a tightly packed stream",
|
||||
location, attr.Offset, sourceStride, requiredAlignment);
|
||||
}
|
||||
|
||||
Uint32 stride = sourceStride;
|
||||
if (conversion == VertexStreamConversion::Repack) {
|
||||
stride = static_cast<Uint32>(attribByteSize);
|
||||
} else if (conversion == VertexStreamConversion::ScaledIntegerToFloat32) {
|
||||
stride = static_cast<Uint32>(attr.Size * static_cast<Int>(sizeof(Float)));
|
||||
}
|
||||
const VkVertexInputRate inputRate =
|
||||
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
||||
|
||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||
Uint32 binding = 0;
|
||||
auto itBinding = bindingByBufferKey.find(bufferKey);
|
||||
if (itBinding == bindingByBufferKey.end()) {
|
||||
binding = static_cast<Uint32>(bindingByBufferKey.size());
|
||||
bindingByBufferKey.emplace(bufferKey, binding);
|
||||
strideByBufferKey.emplace(bufferKey, stride);
|
||||
inputRateByBufferKey.emplace(bufferKey, inputRate);
|
||||
bindingBufferKeys.push_back(bufferKey);
|
||||
builder.AddBinding(binding, stride, inputRate);
|
||||
} else {
|
||||
binding = itBinding->second;
|
||||
if (strideByBufferKey[bufferKey] != stride) {
|
||||
MGLOG_D("Skipping vertex attribute at location %u: stride mismatch (%u vs %u) on same buffer",
|
||||
location, stride, strideByBufferKey[bufferKey]);
|
||||
continue;
|
||||
}
|
||||
if (inputRateByBufferKey[bufferKey] != inputRate) {
|
||||
MGLOG_D("Skipping vertex attribute at location %u: input-rate mismatch on same buffer", location);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
builder.AddAttribute(location, binding, vkFormat, static_cast<Uint32>(attr.Offset));
|
||||
const Uint32 binding = static_cast<Uint32>(bindingBufferKeys.size());
|
||||
bindingBufferKeys.push_back(bufferKey);
|
||||
bindingBaseOffsets.push_back(attr.Buffer ? attr.Offset : 0);
|
||||
bindingAttributeLocations.push_back(location);
|
||||
bindingUsesClientMemory.push_back(attr.Buffer == nullptr);
|
||||
bindingConversions.push_back(conversion);
|
||||
builder.AddBinding(binding, stride, inputRate);
|
||||
builder.AddAttribute(location, binding, vkFormat, 0);
|
||||
}
|
||||
|
||||
const auto& state = builder.Build();
|
||||
@@ -111,14 +164,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
entry.bindings = builder.GetBindings();
|
||||
entry.attributes = builder.GetAttributes();
|
||||
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
||||
entry.bindingBaseOffsets = std::move(bindingBaseOffsets);
|
||||
entry.bindingAttributeLocations = std::move(bindingAttributeLocations);
|
||||
entry.bindingUsesClientMemory = std::move(bindingUsesClientMemory);
|
||||
entry.bindingConversions = std::move(bindingConversions);
|
||||
entry.unsupportedAttribMask = unsupportedAttribMask;
|
||||
entry.state = state;
|
||||
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
|
||||
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
|
||||
return entry;
|
||||
}
|
||||
|
||||
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger) {
|
||||
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger,
|
||||
Bool isBgra) {
|
||||
if (isBgra) {
|
||||
// GL_BGRA: four reversed-order components, always normalized (enforced at validation), only
|
||||
// legal with GL_UNSIGNED_BYTE or a 2_10_10_10 type. The reversed VkFormats put the
|
||||
// components back into R,G,B,A order for the shader.
|
||||
switch (type) {
|
||||
case DataType::Uint8:
|
||||
return VK_FORMAT_B8G8R8A8_UNORM;
|
||||
case DataType::Uint2101010Rev:
|
||||
return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
|
||||
case DataType::Int2101010Rev:
|
||||
return VK_FORMAT_A2R10G10B10_SNORM_PACK32;
|
||||
default:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
switch (type) {
|
||||
case DataType::Uint2101010Rev:
|
||||
// Packed 2_10_10_10 travels the float-normalizing path only; size is always 4. SNORM/UNORM
|
||||
// normalize, SSCALED/USCALED cast the packed field to float.
|
||||
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
|
||||
return normalized ? VK_FORMAT_A2B10G10R10_UNORM_PACK32 : VK_FORMAT_A2B10G10R10_USCALED_PACK32;
|
||||
case DataType::Int2101010Rev:
|
||||
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
|
||||
return normalized ? VK_FORMAT_A2B10G10R10_SNORM_PACK32 : VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
|
||||
case DataType::Float32:
|
||||
switch (size) {
|
||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||
@@ -127,6 +209,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Float16:
|
||||
// GL_HALF_FLOAT is a floating-point array type: it is never an integer attribute, and
|
||||
// GL_TRUE for `normalized` is ignored for float types rather than selecting a *NORM format.
|
||||
if (isInteger) return VK_FORMAT_UNDEFINED;
|
||||
switch (size) {
|
||||
case 1: return VK_FORMAT_R16_SFLOAT;
|
||||
case 2: return VK_FORMAT_R16G16_SFLOAT;
|
||||
case 3: return VK_FORMAT_R16G16B16_SFLOAT;
|
||||
case 4: return VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Int32:
|
||||
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
||||
switch (size) {
|
||||
@@ -148,8 +241,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
case DataType::Int16:
|
||||
switch (size) {
|
||||
case 1:
|
||||
return isInteger ? VK_FORMAT_R16_SINT
|
||||
: (normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SSCALED);
|
||||
return isInteger ? VK_FORMAT_R16_SINT : (normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SSCALED);
|
||||
case 2:
|
||||
return isInteger ? VK_FORMAT_R16G16_SINT
|
||||
: (normalized ? VK_FORMAT_R16G16_SNORM : VK_FORMAT_R16G16_SSCALED);
|
||||
@@ -164,8 +256,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
case DataType::Uint16:
|
||||
switch (size) {
|
||||
case 1:
|
||||
return isInteger ? VK_FORMAT_R16_UINT
|
||||
: (normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_USCALED);
|
||||
return isInteger ? VK_FORMAT_R16_UINT : (normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_USCALED);
|
||||
case 2:
|
||||
return isInteger ? VK_FORMAT_R16G16_UINT
|
||||
: (normalized ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16G16_USCALED);
|
||||
@@ -180,8 +271,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
case DataType::Int8:
|
||||
switch (size) {
|
||||
case 1:
|
||||
return isInteger ? VK_FORMAT_R8_SINT
|
||||
: (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED);
|
||||
return isInteger ? VK_FORMAT_R8_SINT : (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED);
|
||||
case 2:
|
||||
return isInteger ? VK_FORMAT_R8G8_SINT
|
||||
: (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED);
|
||||
@@ -196,8 +286,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
case DataType::Uint8:
|
||||
switch (size) {
|
||||
case 1:
|
||||
return isInteger ? VK_FORMAT_R8_UINT
|
||||
: (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED);
|
||||
return isInteger ? VK_FORMAT_R8_UINT : (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED);
|
||||
case 2:
|
||||
return isInteger ? VK_FORMAT_R8G8_UINT
|
||||
: (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED);
|
||||
@@ -234,4 +323,57 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SizeT VertexInputStateFactory::GetAttributeByteSize(DataType type, Int size, Bool isBgra) {
|
||||
// The packed 2_10_10_10 types are a single 32-bit word for all 4 components; GL_BGRA is always
|
||||
// 4 components (GL_UNSIGNED_BYTE x4 = 4 bytes, or a packed word = 4 bytes) -- both are 4 bytes.
|
||||
if (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev || isBgra) {
|
||||
return 4;
|
||||
}
|
||||
const SizeT componentSize = GetComponentSize(type);
|
||||
return componentSize == 0 ? 0 : componentSize * static_cast<SizeT>(size);
|
||||
}
|
||||
|
||||
Bool VertexInputStateFactory::IsScaledIntegerVertexFormat(VkFormat format) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_R8_USCALED:
|
||||
case VK_FORMAT_R8_SSCALED:
|
||||
case VK_FORMAT_R8G8_USCALED:
|
||||
case VK_FORMAT_R8G8_SSCALED:
|
||||
case VK_FORMAT_R8G8B8_USCALED:
|
||||
case VK_FORMAT_R8G8B8_SSCALED:
|
||||
case VK_FORMAT_R8G8B8A8_USCALED:
|
||||
case VK_FORMAT_R8G8B8A8_SSCALED:
|
||||
case VK_FORMAT_R16_USCALED:
|
||||
case VK_FORMAT_R16_SSCALED:
|
||||
case VK_FORMAT_R16G16_USCALED:
|
||||
case VK_FORMAT_R16G16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16_USCALED:
|
||||
case VK_FORMAT_R16G16B16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16A16_USCALED:
|
||||
case VK_FORMAT_R16G16B16A16_SSCALED:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat VertexInputStateFactory::ToFloat32VertexFormat(Int componentCount) {
|
||||
switch (componentCount) {
|
||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||
case 2: return VK_FORMAT_R32G32_SFLOAT;
|
||||
case 3: return VK_FORMAT_R32G32B32_SFLOAT;
|
||||
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
Bool VertexInputStateFactory::SupportsVertexBufferFormat(VkFormat format) const {
|
||||
if (m_physicalDevice == VK_NULL_HANDLE || format == VK_FORMAT_UNDEFINED) {
|
||||
return false;
|
||||
}
|
||||
VkFormatProperties properties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties);
|
||||
return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -19,29 +19,56 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
public:
|
||||
using HashType = Uint64;
|
||||
|
||||
enum class VertexStreamConversion : Uint8 {
|
||||
None = 0,
|
||||
Repack,
|
||||
ScaledIntegerToFloat32,
|
||||
};
|
||||
|
||||
struct BackendVertexInputState {
|
||||
HashType hash = 0;
|
||||
Vector<VkVertexInputBindingDescription> bindings;
|
||||
Vector<VkVertexInputAttributeDescription> attributes;
|
||||
Vector<SizeT> bindingBufferKeys;
|
||||
Vector<SizeT> bindingBaseOffsets;
|
||||
Vector<Uint32> bindingAttributeLocations;
|
||||
Vector<Bool> bindingUsesClientMemory;
|
||||
Vector<VertexStreamConversion> bindingConversions;
|
||||
// Locations whose array is ENABLED but whose GL format has no VkFormat mapping. They are
|
||||
// absent from `attributes`, so without this mask the draw path cannot tell them apart from
|
||||
// a genuinely disabled array and would silently feed the shader the current attribute value.
|
||||
Uint32 unsupportedAttribMask = 0;
|
||||
VkPipelineVertexInputStateCreateInfo state{
|
||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
||||
};
|
||||
};
|
||||
|
||||
explicit VertexInputStateFactory(const VulkanRendererConfig& config):
|
||||
m_config(config) {}
|
||||
VertexInputStateFactory(const VulkanRendererConfig& config, VkPhysicalDevice physicalDevice):
|
||||
m_config(config), m_physicalDevice(physicalDevice) {}
|
||||
~VertexInputStateFactory() = default;
|
||||
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
||||
|
||||
HashType ComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
||||
// Memoized ComputeHash: reuses the VAO's cached hash while its config version
|
||||
// is unchanged. Use this on per-draw paths.
|
||||
HashType GetOrComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
||||
const BackendVertexInputState& GetOrCreateVertexInputState(
|
||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
||||
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
||||
static SizeT GetComponentSize(DataType type);
|
||||
// Tightly-packed byte size of one vertex element for this attribute: componentSize * size for
|
||||
// normal types, and 4 (one packed word) for the 2_10_10_10 types and GL_BGRA. Returns 0 for
|
||||
// an unknown/unsupported type.
|
||||
static SizeT GetAttributeByteSize(DataType type, Int size, Bool isBgra);
|
||||
|
||||
private:
|
||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger);
|
||||
static SizeT GetComponentSize(DataType type);
|
||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false);
|
||||
static Bool IsScaledIntegerVertexFormat(VkFormat format);
|
||||
static VkFormat ToFloat32VertexFormat(Int componentCount);
|
||||
Bool SupportsVertexBufferFormat(VkFormat format) const;
|
||||
|
||||
const VulkanRendererConfig& m_config;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "VkBufferManager.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
constexpr VmaAllocationCreateFlags kResidentBufferAllocationFlags =
|
||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
constexpr SizeT kLiveResourcePruneThreshold = 256;
|
||||
|
||||
// A zero-copy persistent buffer is created once and never recreated (the app holds
|
||||
// its mapped pointer), and may be bound to any role, so it carries every usage.
|
||||
// TRANSFER_DST is added by CreateResidentStorage.
|
||||
constexpr VkBufferUsageFlags kPersistentBackedUsage =
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
// The app writes into the persistent map with no explicit flush, so its memory must
|
||||
// be host-coherent (Adreno host-visible memory is; requiring it keeps us portable).
|
||||
constexpr VkMemoryPropertyFlags kPersistentBackedRequiredFlags =
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
|
||||
using MG_State::GLState::BackendBufferResource;
|
||||
using MG_State::GLState::BufferBackendOps;
|
||||
using MG_State::GLState::BufferObject;
|
||||
|
||||
// The manager owned by the active VulkanRenderer; immediate ops route here.
|
||||
VkBufferManager* g_activeBufferManager = nullptr;
|
||||
|
||||
void Ops_Respecify(BufferObject& bufferObject) {
|
||||
if (g_activeBufferManager) {
|
||||
g_activeBufferManager->OnRespecify(bufferObject);
|
||||
}
|
||||
}
|
||||
|
||||
void Ops_SubData(BufferObject& bufferObject, SizeT offset, SizeT size) {
|
||||
if (g_activeBufferManager) {
|
||||
g_activeBufferManager->OnSubData(bufferObject, offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range,
|
||||
Flags<BufferMappingAccessBit> appAccess) {
|
||||
if (g_activeBufferManager) {
|
||||
g_activeBufferManager->OnFlushMappedRange(bufferObject, range, appAccess);
|
||||
}
|
||||
}
|
||||
|
||||
void* Ops_AcquirePersistentMap(BufferObject& bufferObject) {
|
||||
if (g_activeBufferManager) {
|
||||
return g_activeBufferManager->AcquirePersistentMap(bufferObject);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
|
||||
if (g_activeBufferManager) {
|
||||
g_activeBufferManager->OnResourceDestroyed(std::move(resource));
|
||||
}
|
||||
// No active manager: the device/allocator is gone or going away and
|
||||
// Shutdown() already destroyed the storage; dropping the handle here
|
||||
// must not touch Vulkan. VkBufferResource's dtor destroys via VMA only
|
||||
// when the allocation is still valid, which Shutdown() cleared.
|
||||
}
|
||||
|
||||
const BufferBackendOps g_vulkanBufferBackendOps = {
|
||||
.Respecify = Ops_Respecify,
|
||||
.SubData = Ops_SubData,
|
||||
.FlushMappedRange = Ops_FlushMappedRange,
|
||||
.OnDestroy = Ops_OnDestroy,
|
||||
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
|
||||
Shutdown();
|
||||
|
||||
MOBILEGL_ASSERT(initInfo.allocator != nullptr, "VkBufferManager::Initialize requires valid allocator");
|
||||
MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkBufferManager::Initialize requires non-zero frame count");
|
||||
|
||||
m_initInfo = initInfo;
|
||||
m_deferredBufferReleases.resize(initInfo.frameCount);
|
||||
m_deferredResourceReleases.resize(initInfo.frameCount);
|
||||
m_currentFrameIndex = 0;
|
||||
m_frameSerial = 1;
|
||||
m_completedSerialFloor = 0;
|
||||
if (!InitializeTransientArenas()) {
|
||||
return false;
|
||||
}
|
||||
g_activeBufferManager = this;
|
||||
MG_State::GLState::SetBufferBackendOps(&g_vulkanBufferBackendOps);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBufferManager::Shutdown() {
|
||||
if (g_activeBufferManager == this) {
|
||||
g_activeBufferManager = nullptr;
|
||||
if (MG_State::GLState::GetBufferBackendOps() == &g_vulkanBufferBackendOps) {
|
||||
MG_State::GLState::SetBufferBackendOps(nullptr);
|
||||
}
|
||||
}
|
||||
m_transientUploadArena.Shutdown();
|
||||
DestroyAllDeferredReleases();
|
||||
ReleaseAllLiveResources();
|
||||
m_copyProvider = nullptr;
|
||||
m_initInfo = {};
|
||||
m_currentFrameIndex = 0;
|
||||
m_frameSerial = 1;
|
||||
m_completedSerialFloor = 0;
|
||||
}
|
||||
|
||||
Bool VkBufferManager::RecreateTransientArenas(Uint32 frameCount) {
|
||||
MOBILEGL_ASSERT(m_initInfo.allocator != nullptr,
|
||||
"VkBufferManager::RecreateTransientArenas requires initialized manager");
|
||||
MOBILEGL_ASSERT(frameCount > 0, "VkBufferManager::RecreateTransientArenas requires non-zero frame count");
|
||||
|
||||
// Callers guarantee the device is idle around arena recreation.
|
||||
NotifyDeviceIdle();
|
||||
m_transientUploadArena.Shutdown();
|
||||
m_initInfo.frameCount = frameCount;
|
||||
DestroyAllDeferredReleases();
|
||||
m_deferredBufferReleases.resize(frameCount);
|
||||
m_deferredResourceReleases.resize(frameCount);
|
||||
m_currentFrameIndex = 0;
|
||||
return InitializeTransientArenas();
|
||||
}
|
||||
|
||||
void VkBufferManager::BeginFrame(Uint32 frameIndex) {
|
||||
MOBILEGL_ASSERT(frameIndex < m_deferredBufferReleases.size(),
|
||||
"VkBufferManager::BeginFrame frame index out of range");
|
||||
m_currentFrameIndex = frameIndex;
|
||||
++m_frameSerial;
|
||||
CollectDeferredReleases(frameIndex);
|
||||
m_transientUploadArena.BeginFrame(frameIndex);
|
||||
}
|
||||
|
||||
void VkBufferManager::NotifyDeviceIdle() {
|
||||
// Everything submitted so far has completed. Work recorded for the
|
||||
// current frame has not been submitted yet, so the current serial
|
||||
// remains busy.
|
||||
if (m_frameSerial > 0) {
|
||||
m_completedSerialFloor = m_frameSerial - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void VkBufferManager::NotifyFrameSerialComplete(Uint64 serial) {
|
||||
// The current serial's work is still being recorded; a completion
|
||||
// report for it (or beyond) can only come from a stale caller.
|
||||
if (serial >= m_frameSerial) {
|
||||
return;
|
||||
}
|
||||
m_completedSerialFloor = std::max(m_completedSerialFloor, serial);
|
||||
}
|
||||
|
||||
void VkBufferManager::SetCopyCommandProvider(IBufferCopyCommandProvider* provider) {
|
||||
m_copyProvider = provider;
|
||||
}
|
||||
|
||||
Uint64 VkBufferManager::GetCompletedSerial() const {
|
||||
const Uint64 frameCount = m_initInfo.frameCount > 0 ? m_initInfo.frameCount : 1;
|
||||
const Uint64 completed = m_frameSerial > frameCount ? m_frameSerial - frameCount : 0;
|
||||
return std::max(completed, m_completedSerialFloor);
|
||||
}
|
||||
|
||||
Bool VkBufferManager::IsResourceBusy(const VkBufferResource& resource) const {
|
||||
return resource.lastUseSerial > GetCompletedSerial();
|
||||
}
|
||||
|
||||
Bool VkBufferManager::UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data,
|
||||
VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) {
|
||||
(void)kind;
|
||||
return m_transientUploadArena.Upload(frameIndex, data, size, alignment, outSlice);
|
||||
}
|
||||
|
||||
Bool VkBufferManager::InitializeTransientArenas() {
|
||||
return m_transientUploadArena.Initialize({
|
||||
.allocator = m_initInfo.allocator,
|
||||
.frameCount = m_initInfo.frameCount,
|
||||
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
.memoryUsage = m_initInfo.transientMemoryUsage,
|
||||
.allocationFlags = m_initInfo.transientAllocationFlags,
|
||||
.minBufferSize = m_initInfo.minUploadBytes,
|
||||
.persistentlyMapped = m_initInfo.transientPersistentMapping,
|
||||
});
|
||||
}
|
||||
|
||||
VkBufferResource* VkBufferManager::ResourceOf(MG_State::GLState::BufferObject& bufferObject) {
|
||||
return static_cast<VkBufferResource*>(bufferObject.GetBackendResource().get());
|
||||
}
|
||||
|
||||
VkBufferResource* VkBufferManager::GetOrCreateResource(
|
||||
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
|
||||
// Return by raw pointer: the resource is owned for its whole lifetime by the BufferObject's
|
||||
// backend-resource SharedPtr (already set, or set below), so callers that only dereference
|
||||
// it avoid a static_pointer_cast + SharedPtr refcount inc/dec on every per-draw buffer bind.
|
||||
const auto& existing = bufferObject->GetBackendResource();
|
||||
if (existing) {
|
||||
return static_cast<VkBufferResource*>(existing.get());
|
||||
}
|
||||
auto resource = MakeShared<VkBufferResource>();
|
||||
VkBufferResource* raw = resource.get();
|
||||
bufferObject->SetBackendResource(resource);
|
||||
TrackLiveResource(resource);
|
||||
return raw;
|
||||
}
|
||||
|
||||
void VkBufferManager::TrackLiveResource(const SharedPtr<VkBufferResource>& resource) {
|
||||
if (m_liveResources.size() >= kLiveResourcePruneThreshold) {
|
||||
std::erase_if(m_liveResources, [](const WeakPtr<VkBufferResource>& weak) { return weak.expired(); });
|
||||
}
|
||||
m_liveResources.push_back(resource);
|
||||
}
|
||||
|
||||
void VkBufferManager::ReleaseAllLiveResources() {
|
||||
for (auto& weak : m_liveResources) {
|
||||
if (auto resource = weak.lock()) {
|
||||
resource->buffer.Destroy();
|
||||
resource->storageSize = 0;
|
||||
resource->usageFlags = 0;
|
||||
resource->lastUseSerial = 0;
|
||||
resource->pendingFullUpload = true;
|
||||
resource->transientSlice = {};
|
||||
resource->transientFrameSerial = 0;
|
||||
}
|
||||
}
|
||||
m_liveResources.clear();
|
||||
}
|
||||
|
||||
Bool VkBufferManager::CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size,
|
||||
VkBufferUsageFlags usage, VkMemoryPropertyFlags requiredFlags) {
|
||||
// Staged range copies write resident storage with vkCmdCopyBuffer.
|
||||
usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
const Bool created = resource.buffer.Create({
|
||||
.allocator = m_initInfo.allocator,
|
||||
.size = size,
|
||||
.usage = usage,
|
||||
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
|
||||
.allocationFlags = kResidentBufferAllocationFlags,
|
||||
.requiredFlags = requiredFlags,
|
||||
});
|
||||
if (!created || resource.buffer.Map() == nullptr) {
|
||||
MGLOG_E("VkBufferManager::CreateResidentStorage failed (size=%llu)",
|
||||
static_cast<unsigned long long>(size));
|
||||
resource.buffer.Destroy();
|
||||
resource.storageSize = 0;
|
||||
resource.usageFlags = 0;
|
||||
return false;
|
||||
}
|
||||
resource.storageSize = size;
|
||||
resource.usageFlags = usage;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkBufferManager::SwapStorageAndUploadAll(VkBufferResource& resource,
|
||||
MG_State::GLState::BufferObject& bufferObject) {
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject.GetSize());
|
||||
const VkBufferUsageFlags usage = resource.usageFlags;
|
||||
DeferRelease(std::move(resource.buffer));
|
||||
if (!CreateResidentStorage(resource, size, usage)) {
|
||||
resource.pendingFullUpload = true;
|
||||
return false;
|
||||
}
|
||||
if (!resource.buffer.Upload(bufferObject.MappedData(), size, 0)) {
|
||||
MGLOG_E("VkBufferManager::SwapStorageAndUploadAll: upload failed");
|
||||
resource.pendingFullUpload = true;
|
||||
return false;
|
||||
}
|
||||
resource.pendingFullUpload = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkBufferManager::StagedRangeCopy(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject,
|
||||
SizeT offset, SizeT size) {
|
||||
if (!m_copyProvider) {
|
||||
return false;
|
||||
}
|
||||
BufferSlice staging{};
|
||||
if (!m_transientUploadArena.Upload(m_currentFrameIndex, bufferObject.MappedData() + offset,
|
||||
static_cast<VkDeviceSize>(size), 16, staging)) {
|
||||
return false;
|
||||
}
|
||||
VkCommandBuffer commandBuffer = m_copyProvider->AcquireBufferCopyCommandBuffer();
|
||||
if (commandBuffer == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Order the copy after every prior read/write of this buffer, both from
|
||||
// in-flight frames (submission order) and from commands already recorded
|
||||
// in this frame's command buffer.
|
||||
VkMemoryBarrier beforeBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
beforeBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
beforeBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1,
|
||||
&beforeBarrier, 0, nullptr, 0, nullptr);
|
||||
|
||||
VkBufferCopy region{};
|
||||
region.srcOffset = staging.offset;
|
||||
region.dstOffset = static_cast<VkDeviceSize>(offset);
|
||||
region.size = static_cast<VkDeviceSize>(size);
|
||||
vkCmdCopyBuffer(commandBuffer, staging.buffer, resource.buffer.GetHandle(), 1, ®ion);
|
||||
|
||||
VkMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
afterBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
afterBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1,
|
||||
&afterBarrier, 0, nullptr, 0, nullptr);
|
||||
|
||||
resource.lastUseSerial = m_frameSerial;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBufferManager::OnRespecify(MG_State::GLState::BufferObject& bufferObject) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
if (!resource) {
|
||||
return; // lazy: AcquireResidentSlice performs a full upload on creation
|
||||
}
|
||||
// Any cached streaming slice refers to the previous contents.
|
||||
resource->transientFrameSerial = 0;
|
||||
if (!resource->buffer.IsValid()) {
|
||||
return; // streaming-only resource: shadow + serial are enough
|
||||
}
|
||||
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject.GetSize());
|
||||
if (size == 0) {
|
||||
DeferRelease(std::move(resource->buffer));
|
||||
resource->storageSize = 0;
|
||||
resource->pendingFullUpload = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (size != resource->storageSize || IsResourceBusy(*resource)) {
|
||||
// Conditional orphan: only swap the storage when the old one is
|
||||
// still referenced by the GPU (or no longer fits).
|
||||
SwapStorageAndUploadAll(*resource, bufferObject);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resource->buffer.Upload(bufferObject.MappedData(), size, 0)) {
|
||||
MGLOG_E("VkBufferManager::OnRespecify: in-place upload failed");
|
||||
resource->pendingFullUpload = true;
|
||||
}
|
||||
}
|
||||
|
||||
void VkBufferManager::OnSubData(MG_State::GLState::BufferObject& bufferObject, SizeT offset, SizeT size) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
if (!resource) {
|
||||
return;
|
||||
}
|
||||
resource->transientFrameSerial = 0;
|
||||
if (!resource->buffer.IsValid() || resource->pendingFullUpload) {
|
||||
return;
|
||||
}
|
||||
if (static_cast<VkDeviceSize>(bufferObject.GetSize()) != resource->storageSize) {
|
||||
resource->pendingFullUpload = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsResourceBusy(*resource)) {
|
||||
if (!resource->buffer.Upload(bufferObject.MappedData() + offset,
|
||||
static_cast<VkDeviceSize>(size), static_cast<VkDeviceSize>(offset))) {
|
||||
MGLOG_E("VkBufferManager::OnSubData: host upload failed");
|
||||
resource->pendingFullUpload = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Busy partial write: stage + GPU copy preserves GL ordering within the
|
||||
// frame and leaves bytes outside the range (possibly GPU-written, e.g.
|
||||
// SSBO) intact. Fall back to a storage swap if staging is unavailable.
|
||||
if (!StagedRangeCopy(*resource, bufferObject, offset, size)) {
|
||||
SwapStorageAndUploadAll(*resource, bufferObject);
|
||||
}
|
||||
}
|
||||
|
||||
void VkBufferManager::OnFlushMappedRange(MG_State::GLState::BufferObject& bufferObject, Range1D range,
|
||||
Flags<BufferMappingAccessBit> appAccess) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
if (!resource) {
|
||||
return;
|
||||
}
|
||||
resource->transientFrameSerial = 0;
|
||||
if (!resource->buffer.IsValid() || resource->pendingFullUpload) {
|
||||
return;
|
||||
}
|
||||
if (static_cast<VkDeviceSize>(bufferObject.GetSize()) != resource->storageSize) {
|
||||
resource->pendingFullUpload = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const SizeT offset = range.start;
|
||||
const SizeT size = range.end - range.start;
|
||||
// GL_MAP_UNSYNCHRONIZED_BIT: the app guarantees it does not overwrite
|
||||
// data the GPU is still reading; honour it with a direct host write.
|
||||
if ((appAccess & BufferMappingAccessBit::Unsynchronized) || !IsResourceBusy(*resource)) {
|
||||
if (!resource->buffer.Upload(bufferObject.MappedData() + offset,
|
||||
static_cast<VkDeviceSize>(size), static_cast<VkDeviceSize>(offset))) {
|
||||
MGLOG_E("VkBufferManager::OnFlushMappedRange: host upload failed");
|
||||
resource->pendingFullUpload = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!StagedRangeCopy(*resource, bufferObject, offset, size)) {
|
||||
SwapStorageAndUploadAll(*resource, bufferObject);
|
||||
}
|
||||
}
|
||||
|
||||
void VkBufferManager::OnResourceDestroyed(SharedPtr<MG_State::GLState::BackendBufferResource>&& resource) {
|
||||
if (!resource) {
|
||||
return;
|
||||
}
|
||||
auto vkResource = std::static_pointer_cast<VkBufferResource>(std::move(resource));
|
||||
if (!vkResource->buffer.IsValid()) {
|
||||
return;
|
||||
}
|
||||
if (m_deferredResourceReleases.empty()) {
|
||||
vkResource->buffer.Destroy();
|
||||
return;
|
||||
}
|
||||
MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredResourceReleases.size(),
|
||||
"VkBufferManager::OnResourceDestroyed current frame index out of range");
|
||||
// Keep the whole resource alive until this frame slot's fence has been
|
||||
// waited, then the storage is destroyed with it.
|
||||
m_deferredResourceReleases[m_currentFrameIndex].push_back(std::move(vkResource));
|
||||
}
|
||||
|
||||
void* VkBufferManager::AcquirePersistentMap(MG_State::GLState::BufferObject& bufferObject) {
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject.GetSize());
|
||||
if (size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto resource = std::static_pointer_cast<VkBufferResource>(bufferObject.GetBackendResource());
|
||||
if (!resource) {
|
||||
resource = MakeShared<VkBufferResource>();
|
||||
bufferObject.SetBackendResource(resource);
|
||||
TrackLiveResource(resource);
|
||||
}
|
||||
|
||||
// Idempotent: an already-backed buffer returns the same mapped base.
|
||||
if (resource->persistentMapped && resource->buffer.IsValid() && resource->storageSize == size) {
|
||||
return resource->buffer.GetMappedData();
|
||||
}
|
||||
|
||||
// One-time creation of HOST_VISIBLE + HOST_COHERENT, persistently mapped storage
|
||||
// carrying every usage (never recreated, so the app's pointer never dangles). Seed
|
||||
// it from the current shadow - MappedData() is still the shadow here because the
|
||||
// frontend adopts (and drops) the shadow only after this returns.
|
||||
DeferRelease(std::move(resource->buffer));
|
||||
if (!CreateResidentStorage(*resource, size, kPersistentBackedUsage, kPersistentBackedRequiredFlags)) {
|
||||
resource->persistentMapped = false;
|
||||
resource->storageSize = 0;
|
||||
resource->usageFlags = 0;
|
||||
return nullptr;
|
||||
}
|
||||
const Uint8* seed = bufferObject.MappedData();
|
||||
if (seed != nullptr) {
|
||||
resource->buffer.Upload(seed, size, 0);
|
||||
}
|
||||
resource->persistentMapped = true;
|
||||
resource->pendingFullUpload = false;
|
||||
resource->storageSize = size;
|
||||
resource->lastUseSerial = 0;
|
||||
return resource->buffer.GetMappedData();
|
||||
}
|
||||
|
||||
Bool VkBufferManager::AcquireResidentSlice(BufferKind kind,
|
||||
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
||||
BufferSlice& outSlice) {
|
||||
const VkBufferUsageFlags requiredUsage = GetVkBufferUsage(kind);
|
||||
MOBILEGL_ASSERT(requiredUsage != 0, "VkBufferManager::AcquireResidentSlice unsupported buffer kind");
|
||||
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::AcquireResidentSlice requires valid buffer object");
|
||||
|
||||
auto resource = GetOrCreateResource(bufferObject);
|
||||
bufferObject->SyncPersistentMappedRange();
|
||||
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject->GetSize());
|
||||
if (size == 0) {
|
||||
MGLOG_E("VkBufferManager::AcquireResidentSlice failed: buffer size is zero");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Zero-copy persistent buffers already hold the app's live coherent writes in
|
||||
// host-visible storage carrying every usage; bind directly, no re-upload/staging.
|
||||
if (resource->persistentMapped && resource->buffer.IsValid() && resource->storageSize == size) {
|
||||
resource->lastUseSerial = m_frameSerial;
|
||||
outSlice = resource->buffer.GetSlice(0, size);
|
||||
return outSlice.IsValid();
|
||||
}
|
||||
|
||||
const Bool needsRecreate = !resource->buffer.IsValid() || resource->storageSize != size ||
|
||||
((resource->usageFlags & requiredUsage) != requiredUsage) ||
|
||||
resource->pendingFullUpload;
|
||||
if (needsRecreate) {
|
||||
const VkBufferUsageFlags usage = resource->usageFlags | requiredUsage;
|
||||
DeferRelease(std::move(resource->buffer));
|
||||
if (!CreateResidentStorage(*resource, size, usage)) {
|
||||
return false;
|
||||
}
|
||||
if (!resource->buffer.Upload(bufferObject->MappedData(), size, 0)) {
|
||||
MGLOG_E("VkBufferManager::AcquireResidentSlice failed: initial upload failed");
|
||||
resource->buffer.Destroy();
|
||||
resource->storageSize = 0;
|
||||
resource->usageFlags = 0;
|
||||
return false;
|
||||
}
|
||||
resource->pendingFullUpload = false;
|
||||
}
|
||||
|
||||
resource->lastUseSerial = m_frameSerial;
|
||||
outSlice = resource->buffer.GetSlice(0, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkBufferManager::AcquireStreamedSlice(BufferKind kind,
|
||||
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
||||
BufferSlice& outSlice) {
|
||||
(void)kind;
|
||||
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::AcquireStreamedSlice requires valid buffer object");
|
||||
|
||||
auto resource = GetOrCreateResource(bufferObject);
|
||||
bufferObject->SyncPersistentMappedRange();
|
||||
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject->GetSize());
|
||||
if (size == 0) {
|
||||
MGLOG_E("VkBufferManager::AcquireStreamedSlice failed: buffer size is zero");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Uint64 changeSerial = bufferObject->GetChangeSerial();
|
||||
if (resource->transientFrameSerial == m_frameSerial && resource->transientChangeSerial == changeSerial &&
|
||||
resource->transientSize == size && resource->transientSlice.IsValid()) {
|
||||
outSlice = resource->transientSlice;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_transientUploadArena.Upload(m_currentFrameIndex, bufferObject->MappedData(), size, 16,
|
||||
outSlice)) {
|
||||
return false;
|
||||
}
|
||||
resource->transientSlice = outSlice;
|
||||
resource->transientFrameSerial = m_frameSerial;
|
||||
resource->transientChangeSerial = changeSerial;
|
||||
resource->transientSize = size;
|
||||
|
||||
// Streaming path is authoritative now; release resident storage so we do
|
||||
// not keep a second, stale copy alive (downgrade).
|
||||
if (resource->buffer.IsValid()) {
|
||||
DeferRelease(std::move(resource->buffer));
|
||||
resource->storageSize = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBufferManager::DeferRelease(VkBufferObject&& buffer) {
|
||||
if (!buffer.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_deferredBufferReleases.empty()) {
|
||||
buffer.Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredBufferReleases.size(),
|
||||
"VkBufferManager::DeferRelease current frame index out of range");
|
||||
m_deferredBufferReleases[m_currentFrameIndex].push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
void VkBufferManager::CollectDeferredReleases(Uint32 frameIndex) {
|
||||
MOBILEGL_ASSERT(frameIndex < m_deferredBufferReleases.size(),
|
||||
"VkBufferManager::CollectDeferredReleases frame index out of range");
|
||||
m_deferredBufferReleases[frameIndex].clear();
|
||||
m_deferredResourceReleases[frameIndex].clear();
|
||||
}
|
||||
|
||||
VkBufferUsageFlags VkBufferManager::GetVkBufferUsage(BufferKind kind) {
|
||||
switch (kind) {
|
||||
case BufferKind::Vertex:
|
||||
case BufferKind::Index:
|
||||
// A GL buffer can be rebound between ARRAY_BUFFER and ELEMENT_ARRAY_BUFFER,
|
||||
// and may even be used as both within the same draw setup. Keep resident
|
||||
// vertex/index buffers compatible with both roles from the start so we
|
||||
// never need to recreate a buffer after it has already been bound.
|
||||
return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||
case BufferKind::Uniform:
|
||||
return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
||||
case BufferKind::TextureBuffer:
|
||||
return VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
|
||||
case BufferKind::ShaderStorage:
|
||||
return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
|
||||
case BufferKind::Indirect:
|
||||
return VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void VkBufferManager::DestroyAllDeferredReleases() {
|
||||
for (auto& releases : m_deferredBufferReleases) {
|
||||
for (auto& buffer : releases) {
|
||||
buffer.Destroy();
|
||||
}
|
||||
releases.clear();
|
||||
}
|
||||
m_deferredBufferReleases.clear();
|
||||
for (auto& releases : m_deferredResourceReleases) {
|
||||
for (auto& resource : releases) {
|
||||
resource->buffer.Destroy();
|
||||
}
|
||||
releases.clear();
|
||||
}
|
||||
m_deferredResourceReleases.clear();
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,153 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BufferArena.h"
|
||||
#include "MG_State/GLState/BufferState/BufferObject.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
enum class BufferKind : Uint8 {
|
||||
Vertex,
|
||||
Index,
|
||||
Uniform,
|
||||
TextureBuffer,
|
||||
ShaderStorage,
|
||||
Indirect,
|
||||
};
|
||||
|
||||
struct VkBufferManagerInitInfo {
|
||||
VmaAllocator allocator = nullptr;
|
||||
Uint32 frameCount = 0;
|
||||
VkDeviceSize minUploadBytes = 4 * 1024 * 1024;
|
||||
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
|
||||
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
Bool transientPersistentMapping = false;
|
||||
};
|
||||
|
||||
// The DirectVulkan storage behind one frontend buffer (pipe_resource analogue).
|
||||
// Owned (refcounted) by the frontend BufferObject; the manager holds only weak
|
||||
// references (for shutdown) plus strong references on deferred-release lists.
|
||||
class VkBufferResource : public MG_State::GLState::BackendBufferResource {
|
||||
public:
|
||||
~VkBufferResource() override = default;
|
||||
|
||||
// Resident storage (may be invalid for streaming-only buffers).
|
||||
VkBufferObject buffer;
|
||||
VkDeviceSize storageSize = 0;
|
||||
VkBufferUsageFlags usageFlags = 0;
|
||||
// Frame serial of the last GPU reference; drives busy tracking.
|
||||
Uint64 lastUseSerial = 0;
|
||||
// Set when an immediate op could not be applied; forces a full re-upload
|
||||
// on the next AcquireResidentSlice.
|
||||
Bool pendingFullUpload = false;
|
||||
// Backs a zero-copy coherent persistent map (PipeResource GPU residency): the
|
||||
// buffer is HOST_VISIBLE+COHERENT, persistently mapped, carries every usage and is
|
||||
// never orphaned or recreated. Draw-time acquire binds it directly, no re-upload.
|
||||
Bool persistentMapped = false;
|
||||
|
||||
// Cached transient (streaming) slice for the current frame.
|
||||
BufferSlice transientSlice{};
|
||||
Uint64 transientFrameSerial = 0;
|
||||
Uint64 transientChangeSerial = 0;
|
||||
VkDeviceSize transientSize = 0;
|
||||
};
|
||||
|
||||
// Supplies a command buffer that is recording and outside any render pass,
|
||||
// for staged buffer-range copies. Implemented by VulkanRenderer.
|
||||
class IBufferCopyCommandProvider {
|
||||
public:
|
||||
virtual ~IBufferCopyCommandProvider() = default;
|
||||
virtual VkCommandBuffer AcquireBufferCopyCommandBuffer() = 0;
|
||||
};
|
||||
|
||||
class VkBufferManager {
|
||||
public:
|
||||
Bool Initialize(const VkBufferManagerInitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
// Recreate all per-frame transient arenas
|
||||
Bool RecreateTransientArenas(Uint32 frameCount);
|
||||
void BeginFrame(Uint32 frameIndex);
|
||||
// All previously submitted GPU work has completed (vkDeviceWaitIdle).
|
||||
void NotifyDeviceIdle();
|
||||
// A frame slot's submission fence has been waited: every serial up to
|
||||
// and including `serial` is complete. Raises the completed floor so
|
||||
// GetCompletedSerial reflects real fence progress instead of only the
|
||||
// frameSerial-minus-frameCount inference.
|
||||
void NotifyFrameSerialComplete(Uint64 serial);
|
||||
void SetCopyCommandProvider(IBufferCopyCommandProvider* provider);
|
||||
|
||||
Bool UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size,
|
||||
VkDeviceSize alignment, BufferSlice& outSlice);
|
||||
|
||||
// Draw-time acquire for resident (device-storage) buffers: ensures the
|
||||
// resource exists and is fully uploaded, marks it used this frame.
|
||||
Bool AcquireResidentSlice(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
||||
BufferSlice& outSlice);
|
||||
// Draw-time acquire for streamed buffers: uploads the whole shadow into
|
||||
// the per-frame arena (cached by change serial), releasing any resident
|
||||
// storage the buffer may still own.
|
||||
Bool AcquireStreamedSlice(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
||||
BufferSlice& outSlice);
|
||||
|
||||
// Zero-copy persistent map (PipeResource GPU residency): create (once) a
|
||||
// HOST_VISIBLE+COHERENT, persistently mapped resident buffer carrying every usage,
|
||||
// seed it from the shadow, and return its mapped base for the app to write into
|
||||
// directly. Idempotent. Returns nullptr on failure (frontend keeps its shadow).
|
||||
void* AcquirePersistentMap(MG_State::GLState::BufferObject& bufferObject);
|
||||
|
||||
// Immediate ops, dispatched from the frontend BufferBackendOps table.
|
||||
void OnRespecify(MG_State::GLState::BufferObject& bufferObject);
|
||||
void OnSubData(MG_State::GLState::BufferObject& bufferObject, SizeT offset, SizeT size);
|
||||
void OnFlushMappedRange(MG_State::GLState::BufferObject& bufferObject, Range1D range,
|
||||
Flags<BufferMappingAccessBit> appAccess);
|
||||
void OnResourceDestroyed(SharedPtr<MG_State::GLState::BackendBufferResource>&& resource);
|
||||
|
||||
Uint64 GetFrameSerial() const { return m_frameSerial; }
|
||||
// Highest frame serial whose GPU work is known complete; serials at or
|
||||
// below it may be considered signaled. Drives IsResourceBusy and the
|
||||
// backend GL fence objects.
|
||||
Uint64 GetCompletedSerial() const;
|
||||
// Busy = potentially referenced by GPU work that has not been fenced yet
|
||||
// (including commands recorded for the current, unsubmitted frame).
|
||||
Bool IsResourceBusy(const VkBufferResource& resource) const;
|
||||
|
||||
private:
|
||||
Bool InitializeTransientArenas();
|
||||
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
|
||||
VkBufferResource* GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||
static VkBufferResource* ResourceOf(MG_State::GLState::BufferObject& bufferObject);
|
||||
Bool CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
VkMemoryPropertyFlags requiredFlags = 0);
|
||||
// Swap storage (conditional orphan) and refill it from the shadow copy.
|
||||
Bool SwapStorageAndUploadAll(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject);
|
||||
// Record a staging-slice copy into the resident storage, ordered against
|
||||
// in-flight and already-recorded GPU work.
|
||||
Bool StagedRangeCopy(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject,
|
||||
SizeT offset, SizeT size);
|
||||
void DeferRelease(VkBufferObject&& buffer);
|
||||
void CollectDeferredReleases(Uint32 frameIndex);
|
||||
void DestroyAllDeferredReleases();
|
||||
void TrackLiveResource(const SharedPtr<VkBufferResource>& resource);
|
||||
void ReleaseAllLiveResources();
|
||||
|
||||
VkBufferManagerInitInfo m_initInfo{};
|
||||
BufferArena m_transientUploadArena;
|
||||
IBufferCopyCommandProvider* m_copyProvider = nullptr;
|
||||
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
|
||||
Vector<WeakPtr<VkBufferResource>> m_liveResources;
|
||||
Uint32 m_currentFrameIndex = 0;
|
||||
Uint64 m_frameSerial = 1;
|
||||
Uint64 m_completedSerialFloor = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -13,11 +13,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_allocator = other.m_allocator;
|
||||
m_buffer = other.m_buffer;
|
||||
m_allocation = other.m_allocation;
|
||||
m_mappedData = other.m_mappedData;
|
||||
m_size = other.m_size;
|
||||
|
||||
other.m_allocator = nullptr;
|
||||
other.m_buffer = VK_NULL_HANDLE;
|
||||
other.m_allocation = nullptr;
|
||||
other.m_mappedData = nullptr;
|
||||
other.m_size = 0;
|
||||
}
|
||||
|
||||
@@ -31,11 +33,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_allocator = other.m_allocator;
|
||||
m_buffer = other.m_buffer;
|
||||
m_allocation = other.m_allocation;
|
||||
m_mappedData = other.m_mappedData;
|
||||
m_size = other.m_size;
|
||||
|
||||
other.m_allocator = nullptr;
|
||||
other.m_buffer = VK_NULL_HANDLE;
|
||||
other.m_allocation = nullptr;
|
||||
other.m_mappedData = nullptr;
|
||||
other.m_size = 0;
|
||||
return *this;
|
||||
}
|
||||
@@ -44,8 +48,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
Bool VkBufferObject::Create(const VkBufferObjectDesc& desc) {
|
||||
return Create(desc.allocator, desc.size, desc.usage, desc.memoryUsage, desc.allocationFlags,
|
||||
desc.requiredFlags);
|
||||
}
|
||||
|
||||
Bool VkBufferObject::Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags) {
|
||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags,
|
||||
VkMemoryPropertyFlags requiredFlags) {
|
||||
MOBILEGL_ASSERT(allocator != nullptr, "VkBufferObject::Create requires valid VMA allocator");
|
||||
MOBILEGL_ASSERT(size > 0, "VkBufferObject::Create requires non-zero size");
|
||||
|
||||
@@ -61,6 +71,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VmaAllocationCreateInfo allocationInfo{};
|
||||
allocationInfo.usage = memoryUsage;
|
||||
allocationInfo.flags = allocationFlags;
|
||||
allocationInfo.requiredFlags = requiredFlags;
|
||||
|
||||
const VkResult result =
|
||||
vmaCreateBuffer(m_allocator, &bufferInfo, &allocationInfo, &m_buffer, &m_allocation, nullptr);
|
||||
@@ -78,6 +89,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
void VkBufferObject::Destroy() {
|
||||
Unmap();
|
||||
if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) {
|
||||
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
|
||||
}
|
||||
@@ -87,6 +99,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
void* VkBufferObject::Map() {
|
||||
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Map called on invalid buffer");
|
||||
|
||||
if (m_mappedData != nullptr) {
|
||||
return m_mappedData;
|
||||
}
|
||||
|
||||
const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &m_mappedData);
|
||||
if (mapResult != VK_SUCCESS || m_mappedData == nullptr) {
|
||||
MGLOG_E("VkBufferObject::Map failed: vmaMapMemory returned %d", mapResult);
|
||||
m_mappedData = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_mappedData;
|
||||
}
|
||||
|
||||
void VkBufferObject::Unmap() {
|
||||
if (!IsValid() || m_mappedData == nullptr) {
|
||||
m_mappedData = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
vmaUnmapMemory(m_allocator, m_allocation);
|
||||
m_mappedData = nullptr;
|
||||
}
|
||||
|
||||
Bool VkBufferObject::Upload(const void* data, VkDeviceSize size, VkDeviceSize offset) {
|
||||
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Upload called on invalid buffer");
|
||||
MOBILEGL_ASSERT(data != nullptr || size == 0, "VkBufferObject::Upload data pointer is null");
|
||||
@@ -96,15 +135,57 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
void* mapped = nullptr;
|
||||
const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &mapped);
|
||||
if (mapResult != VK_SUCCESS || mapped == nullptr) {
|
||||
MGLOG_E("VkBufferObject::Upload failed: vmaMapMemory returned %d", mapResult);
|
||||
const Bool wasMapped = IsMapped();
|
||||
void* mapped = wasMapped ? m_mappedData : Map();
|
||||
if (mapped == nullptr) {
|
||||
MGLOG_E("VkBufferObject::Upload failed: unable to map buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size));
|
||||
vmaUnmapMemory(m_allocator, m_allocation);
|
||||
const VkResult flushResult = vmaFlushAllocation(m_allocator, m_allocation, offset, size);
|
||||
if (flushResult != VK_SUCCESS) {
|
||||
MGLOG_E("VkBufferObject::Upload failed: vmaFlushAllocation returned %d", flushResult);
|
||||
if (!wasMapped) {
|
||||
Unmap();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!wasMapped) {
|
||||
Unmap();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkBufferObject::Invalidate(VkDeviceSize size, VkDeviceSize offset) {
|
||||
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Invalidate called on invalid buffer");
|
||||
MOBILEGL_ASSERT(IsMapped(), "VkBufferObject::Invalidate requires mapped memory");
|
||||
MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::Invalidate offset out of range");
|
||||
|
||||
const VkDeviceSize resolvedSize = size == VK_WHOLE_SIZE ? m_size - offset : size;
|
||||
MOBILEGL_ASSERT(offset + resolvedSize <= m_size, "VkBufferObject::Invalidate range out of bounds");
|
||||
if (resolvedSize == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const VkResult result = vmaInvalidateAllocation(m_allocator, m_allocation, offset, resolvedSize);
|
||||
if (result != VK_SUCCESS) {
|
||||
MGLOG_E("VkBufferObject::Invalidate failed: vmaInvalidateAllocation returned %d", result);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BufferSlice VkBufferObject::GetSlice(VkDeviceSize offset, VkDeviceSize size) const {
|
||||
MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::GetSlice offset out of range");
|
||||
const VkDeviceSize resolvedSize = (size == VK_WHOLE_SIZE) ? (m_size - offset) : size;
|
||||
MOBILEGL_ASSERT(offset + resolvedSize <= m_size, "VkBufferObject::GetSlice range out of bounds");
|
||||
|
||||
BufferSlice slice{};
|
||||
slice.buffer = m_buffer;
|
||||
slice.offset = offset;
|
||||
slice.size = resolvedSize;
|
||||
slice.mapped = (m_mappedData != nullptr) ? static_cast<Uint8*>(m_mappedData) + offset : nullptr;
|
||||
return slice;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -8,11 +8,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BufferSlice.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct VkBufferObjectDesc {
|
||||
VmaAllocator allocator = nullptr;
|
||||
VkDeviceSize size = 0;
|
||||
VkBufferUsageFlags usage = 0;
|
||||
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO;
|
||||
VmaAllocationCreateFlags allocationFlags = 0;
|
||||
// Memory property bits the allocation MUST satisfy (e.g. HOST_VISIBLE|HOST_COHERENT
|
||||
// for a persistently-mapped buffer the app writes into without explicit flushes).
|
||||
VkMemoryPropertyFlags requiredFlags = 0;
|
||||
};
|
||||
|
||||
class VkBufferObject {
|
||||
public:
|
||||
VkBufferObject() = default;
|
||||
@@ -23,20 +35,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkBufferObject(VkBufferObject&& other) noexcept;
|
||||
VkBufferObject& operator=(VkBufferObject&& other) noexcept;
|
||||
|
||||
Bool Create(const VkBufferObjectDesc& desc);
|
||||
Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0);
|
||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0,
|
||||
VkMemoryPropertyFlags requiredFlags = 0);
|
||||
void Destroy();
|
||||
|
||||
void* Map();
|
||||
void Unmap();
|
||||
Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0);
|
||||
Bool Invalidate(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
|
||||
|
||||
VkBuffer GetHandle() const { return m_buffer; }
|
||||
VkDeviceSize GetSize() const { return m_size; }
|
||||
BufferSlice GetSlice(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) const;
|
||||
void* GetMappedData() const { return m_mappedData; }
|
||||
Bool IsMapped() const { return m_mappedData != nullptr; }
|
||||
Bool IsValid() const { return m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr; }
|
||||
|
||||
private:
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
VkBuffer m_buffer = VK_NULL_HANDLE;
|
||||
VmaAllocation m_allocation = nullptr;
|
||||
void* m_mappedData = nullptr;
|
||||
VkDeviceSize m_size = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "VkClearManager.h"
|
||||
|
||||
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
|
||||
return target >= TextureUploadTarget::CubeMapPositiveX &&
|
||||
target <= TextureUploadTarget::CubeMapNegativeZ;
|
||||
}
|
||||
|
||||
static Bool PendingClearMatchesTextureIdentity(const PendingClearKey& key, const TextureIdentity& identity) {
|
||||
return key.texture == identity.texture && key.textureLifetimeId == identity.lifetimeId;
|
||||
}
|
||||
|
||||
static Uint32 ResolveAttachmentBaseArrayLayer(TextureUploadTarget target) {
|
||||
if (!IsCubeMapFaceUploadTarget(target)) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<Uint32>(target) - static_cast<Uint32>(TextureUploadTarget::CubeMapPositiveX);
|
||||
}
|
||||
|
||||
static Uint32 ResolveAttachmentBaseArrayLayer(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (attachment.IsLayered()) {
|
||||
return 0;
|
||||
}
|
||||
const TextureUploadTarget uploadTarget = attachment.GetTextureUploadTarget();
|
||||
if (!IsCubeMapFaceUploadTarget(uploadTarget)) {
|
||||
return static_cast<Uint32>(std::max(attachment.GetTextureLayer(), 0));
|
||||
}
|
||||
return ResolveAttachmentBaseArrayLayer(uploadTarget);
|
||||
}
|
||||
|
||||
static Uint32 ResolveAttachmentLayerCount(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (attachment.IsLayered()) {
|
||||
return static_cast<Uint32>(std::max(attachment.GetSize().z(), 1));
|
||||
}
|
||||
return 1u;
|
||||
}
|
||||
|
||||
static const MG_State::GLState::FramebufferAttachmentObject* GetClearableAttachment(
|
||||
const MG_State::GLState::FramebufferObject& drawFbo, FramebufferAttachmentType attachmentType) {
|
||||
if (attachmentType == FramebufferAttachmentType::None) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& attachment = drawFbo.GetAttachment(attachmentType);
|
||||
if (!attachment.IsTexture() || attachment.IsRenderbuffer()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &attachment;
|
||||
}
|
||||
|
||||
PendingClearKey VkClearManager::MakePendingClearKey(MG_State::GLState::ITextureObject* texture, Uint32 mipLevel,
|
||||
Uint32 baseArrayLayer, Uint32 layerCount) {
|
||||
return PendingClearKey {
|
||||
.texture = texture,
|
||||
.textureLifetimeId = texture ? texture->GetLifetimeId() : 0,
|
||||
.mipLevel = mipLevel,
|
||||
.baseArrayLayer = baseArrayLayer,
|
||||
.layerCount = layerCount,
|
||||
};
|
||||
}
|
||||
|
||||
PendingClearKey VkClearManager::MakePendingClearKey(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
MOBILEGL_ASSERT(attachment.IsTexture() && !attachment.IsRenderbuffer(),
|
||||
"MakePendingClearKey requires a texture framebuffer attachment");
|
||||
auto* texture = attachment.GetTexture().get();
|
||||
MOBILEGL_ASSERT(texture != nullptr, "MakePendingClearKey: texture attachment resolved to null");
|
||||
const Uint32 mipLevel = static_cast<Uint32>(std::max(attachment.GetTextureLevel(), 0));
|
||||
const Uint32 baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||
const Uint32 layerCount = ResolveAttachmentLayerCount(attachment);
|
||||
return MakePendingClearKey(texture, mipLevel, baseArrayLayer, layerCount);
|
||||
}
|
||||
|
||||
Bool VkClearManager::Initialize() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkClearManager::Shutdown() {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_pendingClears.clear();
|
||||
m_aliveObjects.clear();
|
||||
}
|
||||
|
||||
TextureIdentity VkClearManager::MakeTextureIdentity(MG_State::GLState::ITextureObject* texture) {
|
||||
return TextureIdentity {
|
||||
.texture = texture,
|
||||
.lifetimeId = texture ? texture->GetLifetimeId() : 0,
|
||||
};
|
||||
}
|
||||
|
||||
void VkClearManager::MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src) {
|
||||
dst.mask |= src.mask;
|
||||
if ((src.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
dst.color = src.color;
|
||||
}
|
||||
if ((src.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
dst.depth = src.depth;
|
||||
}
|
||||
if ((src.mask & GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
dst.stencil = src.stencil;
|
||||
}
|
||||
}
|
||||
|
||||
void VkClearManager::ErasePendingClearsForTextureLocked(const TextureIdentity& identity) {
|
||||
Vector<PendingClearKey> keysToErase;
|
||||
keysToErase.reserve(m_pendingClears.size());
|
||||
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||
if (PendingClearMatchesTextureIdentity(it->first, identity)) {
|
||||
keysToErase.emplace_back(it->first);
|
||||
}
|
||||
}
|
||||
for (const auto& key : keysToErase) {
|
||||
m_pendingClears.erase(key);
|
||||
}
|
||||
m_aliveObjects.erase(identity);
|
||||
}
|
||||
|
||||
Bool VkClearManager::LockTextureIdentityLocked(const TextureIdentity& identity,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||
outTexture.reset();
|
||||
if (identity.texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto aliveIt = m_aliveObjects.find(identity);
|
||||
if (aliveIt == m_aliveObjects.end()) {
|
||||
ErasePendingClearsForTextureLocked(identity);
|
||||
return false;
|
||||
}
|
||||
|
||||
outTexture = aliveIt->second.lock();
|
||||
if (!outTexture || outTexture.get() != identity.texture || outTexture->GetLifetimeId() != identity.lifetimeId) {
|
||||
ErasePendingClearsForTextureLocked(identity);
|
||||
outTexture.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkClearManager::LockTextureLocked(const PendingClearKey& key,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||
return LockTextureIdentityLocked(TextureIdentity{
|
||||
.texture = key.texture,
|
||||
.lifetimeId = key.textureLifetimeId,
|
||||
}, outTexture);
|
||||
}
|
||||
|
||||
void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferObject& drawFbo) {
|
||||
if (mask & GL_COLOR_BUFFER_BIT) {
|
||||
auto& drawbufs = drawFbo.GetDrawBuffers();
|
||||
// This should automatically work on default & offscreen FBO
|
||||
for (auto drawbuf: drawbufs) {
|
||||
const auto* attachment = GetClearableAttachment(drawFbo, drawbuf);
|
||||
if (!attachment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QueueClear({
|
||||
.mask = GL_COLOR_BUFFER_BIT,
|
||||
.color = clearPayload.color
|
||||
}, *attachment);
|
||||
|
||||
MGLOG_D("%s: %s (texture %d) - color = (%.2f, %.2f, %.2f, %.2f)", __func__,
|
||||
MG_Util::ConvertFramebufferAttachmentTypeToString(drawbuf).c_str(),
|
||||
attachment->GetTexture()->GetExternalIndex(),
|
||||
clearPayload.color[0], clearPayload.color[1], clearPayload.color[2], clearPayload.color[3]);
|
||||
}
|
||||
}
|
||||
|
||||
if (mask & GL_DEPTH_BUFFER_BIT) {
|
||||
const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Depth);
|
||||
if (attachment) {
|
||||
QueueClear({
|
||||
.mask = GL_DEPTH_BUFFER_BIT,
|
||||
.depth = clearPayload.depth,
|
||||
}, *attachment);
|
||||
|
||||
MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__,
|
||||
attachment->GetTexture()->GetExternalIndex(), clearPayload.depth);
|
||||
}
|
||||
}
|
||||
|
||||
if (mask & GL_STENCIL_BUFFER_BIT) {
|
||||
const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Stencil);
|
||||
if (attachment) {
|
||||
QueueClear({
|
||||
.mask = GL_STENCIL_BUFFER_BIT,
|
||||
.stencil = clearPayload.stencil,
|
||||
}, *attachment);
|
||||
|
||||
MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__,
|
||||
attachment->GetTexture()->GetExternalIndex(), clearPayload.stencil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||
if (clearPayload.mask == 0 || !texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PendingClearKey key = MakePendingClearKey(texture.get());
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
|
||||
auto& pending = m_pendingClears[key];
|
||||
MergeClearPayload(pending, clearPayload);
|
||||
}
|
||||
|
||||
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (clearPayload.mask == 0 || !attachment.IsTexture() || attachment.IsRenderbuffer()) {
|
||||
return;
|
||||
}
|
||||
const auto texture = attachment.GetTexture();
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PendingClearKey key = MakePendingClearKey(attachment);
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
|
||||
auto& pending = m_pendingClears[key];
|
||||
MergeClearPayload(pending, clearPayload);
|
||||
}
|
||||
|
||||
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
|
||||
if (texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Uint64 lifetimeId = texture->GetLifetimeId();
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||
if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) {
|
||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||
return LockTextureLocked(it->first, liveTexture);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool VkClearManager::HasPendingClear(const PendingClearKey& key) {
|
||||
if (key.texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_pendingClears.find(key) == m_pendingClears.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||
return LockTextureLocked(key, liveTexture);
|
||||
}
|
||||
|
||||
Bool VkClearManager::HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) {
|
||||
return false;
|
||||
}
|
||||
return HasPendingClear(MakePendingClearKey(attachment));
|
||||
}
|
||||
|
||||
Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload) {
|
||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||
return GetPendingClear(key, outPayload, liveTexture);
|
||||
}
|
||||
|
||||
Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||
if (key.texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!LockTextureLocked(key, outTexture)) {
|
||||
return false;
|
||||
}
|
||||
auto it = m_pendingClears.find(key);
|
||||
if (it == m_pendingClears.end()) {
|
||||
outTexture.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
outPayload = it->second;
|
||||
MGLOG_D("%s: Got pending clear for texture@%p lifetime=%llu, mip=%u layer=%u count=%u mask=0x%x clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__,
|
||||
static_cast<void*>(key.texture),
|
||||
static_cast<unsigned long long>(key.textureLifetimeId),
|
||||
key.mipLevel, key.baseArrayLayer, key.layerCount,
|
||||
static_cast<Uint32>(outPayload.mask),
|
||||
outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3],
|
||||
outPayload.depth,
|
||||
outPayload.stencil);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkClearManager::GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
||||
ClearAttachmentPayload& outPayload) {
|
||||
if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) {
|
||||
MGLOG_D("%s: Failed getting pending clear for non-texture framebuffer attachment", __func__);
|
||||
return false;
|
||||
}
|
||||
return GetPendingClear(MakePendingClearKey(attachment), outPayload);
|
||||
}
|
||||
|
||||
Bool VkClearManager::GetPendingClears(MG_State::GLState::ITextureObject* texture,
|
||||
Vector<PendingClearEntry>& outEntries) {
|
||||
outEntries.clear();
|
||||
if (texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Uint64 lifetimeId = texture->GetLifetimeId();
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||
if (!LockTextureIdentityLocked(MakeTextureIdentity(texture), liveTexture)) {
|
||||
return false;
|
||||
}
|
||||
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||
if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) {
|
||||
outEntries.emplace_back(PendingClearEntry{.key = it->first, .payload = it->second});
|
||||
}
|
||||
}
|
||||
return !outEntries.empty();
|
||||
}
|
||||
|
||||
void VkClearManager::PopPendingClear(MG_State::GLState::ITextureObject* texture) {
|
||||
if (texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const TextureIdentity identity = MakeTextureIdentity(texture);
|
||||
MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex());
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
ErasePendingClearsForTextureLocked(identity);
|
||||
}
|
||||
|
||||
void VkClearManager::PopPendingClear(const PendingClearKey& key) {
|
||||
if (key.texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
auto it = m_pendingClears.find(key);
|
||||
if (it != m_pendingClears.end()) {
|
||||
m_pendingClears.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
MGLOG_D("%s: Pop pending clear for texture@%p lifetime=%llu mip=%u layer=%u count=%u", __func__,
|
||||
static_cast<void*>(key.texture), static_cast<unsigned long long>(key.textureLifetimeId),
|
||||
key.mipLevel, key.baseArrayLayer, key.layerCount);
|
||||
}
|
||||
|
||||
void VkClearManager::PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) {
|
||||
return;
|
||||
}
|
||||
PopPendingClear(MakePendingClearKey(attachment));
|
||||
}
|
||||
|
||||
SizeT VkClearManager::CollectGarbage() {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_gcCounter++;
|
||||
if (m_gcCounter != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vector<TextureIdentity> expiredTextures;
|
||||
expiredTextures.reserve(m_aliveObjects.size());
|
||||
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
|
||||
if (it->second.expired()) {
|
||||
expiredTextures.emplace_back(it->first);
|
||||
}
|
||||
}
|
||||
if (expiredTextures.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& identity : expiredTextures) {
|
||||
ErasePendingClearsForTextureLocked(identity);
|
||||
}
|
||||
return expiredTextures.size();
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,127 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
#include "../VulkanRendererConfig.h"
|
||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||
#include "MG_Util/Math/VectorTypes.h"
|
||||
|
||||
#include <Includes.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct ClearFramebufferPayload {
|
||||
FloatVec4 color;
|
||||
Float depth{};
|
||||
Uint32 stencil{};
|
||||
};
|
||||
|
||||
struct ClearAttachmentPayload {
|
||||
GLbitfield mask = 0;
|
||||
FloatVec4 color = FloatVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
Float depth = 1.0f;
|
||||
Uint32 stencil = 0;
|
||||
};
|
||||
|
||||
struct PendingClearKey {
|
||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||
Uint64 textureLifetimeId = 0;
|
||||
Uint32 mipLevel = 0;
|
||||
Uint32 baseArrayLayer = 0;
|
||||
Uint32 layerCount = 1;
|
||||
|
||||
Bool operator==(const PendingClearKey& other) const {
|
||||
return texture == other.texture && textureLifetimeId == other.textureLifetimeId &&
|
||||
mipLevel == other.mipLevel &&
|
||||
baseArrayLayer == other.baseArrayLayer && layerCount == other.layerCount;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureIdentity {
|
||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||
Uint64 lifetimeId = 0;
|
||||
|
||||
Bool operator==(const TextureIdentity& other) const {
|
||||
return texture == other.texture && lifetimeId == other.lifetimeId;
|
||||
}
|
||||
};
|
||||
|
||||
struct PendingClearEntry {
|
||||
PendingClearKey key{};
|
||||
ClearAttachmentPayload payload{};
|
||||
};
|
||||
|
||||
struct PendingClearKeyHash {
|
||||
SizeT operator()(const PendingClearKey& key) const {
|
||||
const SizeT textureHash = std::hash<MG_State::GLState::ITextureObject*>{}(key.texture);
|
||||
const SizeT textureLifetimeHash = std::hash<Uint64>{}(key.textureLifetimeId);
|
||||
const SizeT mipHash = std::hash<Uint32>{}(key.mipLevel);
|
||||
const SizeT layerHash = std::hash<Uint32>{}(key.baseArrayLayer);
|
||||
const SizeT layerCountHash = std::hash<Uint32>{}(key.layerCount);
|
||||
SizeT hash = textureHash;
|
||||
hash ^= textureLifetimeHash + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= mipHash + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= layerHash + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= layerCountHash + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureIdentityHash {
|
||||
SizeT operator()(const TextureIdentity& key) const {
|
||||
SizeT hash = std::hash<MG_State::GLState::ITextureObject*>{}(key.texture);
|
||||
hash ^= std::hash<Uint64>{}(key.lifetimeId) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
class VkClearManager {
|
||||
public:
|
||||
static PendingClearKey MakePendingClearKey(const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
static PendingClearKey MakePendingClearKey(MG_State::GLState::ITextureObject* texture, Uint32 mipLevel = 0,
|
||||
Uint32 baseArrayLayer = 0, Uint32 layerCount = 1);
|
||||
|
||||
Bool Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, const MG_State::GLState::FramebufferObject& drawFbo);
|
||||
void QueueClear(
|
||||
const ClearAttachmentPayload& clearPayload,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture);
|
||||
void QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
Bool HasPendingClear(MG_State::GLState::ITextureObject* texture);
|
||||
Bool HasPendingClear(const PendingClearKey& key);
|
||||
Bool HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload);
|
||||
Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||
Bool GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
||||
ClearAttachmentPayload& outPayload);
|
||||
Bool GetPendingClears(MG_State::GLState::ITextureObject* texture, Vector<PendingClearEntry>& outEntries);
|
||||
void PopPendingClear(MG_State::GLState::ITextureObject* texture);
|
||||
void PopPendingClear(const PendingClearKey& key);
|
||||
void PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
SizeT CollectGarbage();
|
||||
private:
|
||||
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
||||
static void MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src);
|
||||
void ErasePendingClearsForTextureLocked(const TextureIdentity& identity);
|
||||
Bool LockTextureIdentityLocked(const TextureIdentity& identity,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||
Bool LockTextureLocked(const PendingClearKey& key,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||
|
||||
Uint8 m_gcCounter = 0;
|
||||
mutable std::mutex m_mutex;
|
||||
std::unordered_map<PendingClearKey, ClearAttachmentPayload, PendingClearKeyHash> m_pendingClears;
|
||||
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -1,547 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "VkFramebufferManager.h"
|
||||
|
||||
#include <MG_State/GLState/RenderbufferState/RenderbufferObject.h>
|
||||
#include <MG_State/GLState/TextureState/TextureEnum.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool VkFramebufferManager::Initialize(const InitInfo& initInfo) {
|
||||
m_device = initInfo.device;
|
||||
m_physicalDevice = initInfo.physicalDevice;
|
||||
return m_device != VK_NULL_HANDLE && m_physicalDevice != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void VkFramebufferManager::Shutdown() {
|
||||
for (auto& [_, target] : m_offscreenColorTargets) {
|
||||
DestroyOffscreenColorTarget(target);
|
||||
}
|
||||
m_offscreenColorTargets.clear();
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_physicalDevice = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::EnsureOffscreenColorTarget(Uint glFboExternalIndex,
|
||||
const MG_State::GLState::FramebufferObject& glFbo) {
|
||||
const auto& colorAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Color0);
|
||||
if (!colorAttachment.IsValid() || colorAttachment.IsEmpty()) {
|
||||
MGLOG_W("VkFramebufferManager: FBO %u has no valid COLOR0 attachment", glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto objectVersion = glFbo.GetObjectVersion();
|
||||
auto& target = m_offscreenColorTargets[glFboExternalIndex];
|
||||
if (target.image != VK_NULL_HANDLE && target.glObjectVersion == objectVersion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return RecreateOffscreenColorTarget(target, glFbo, colorAttachment, objectVersion);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorToAttachment(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToAttachment skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
if (!TransitionImageLayout(commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
|
||||
return TransitionImageLayout(
|
||||
commandBuffer, target.depthStencilImage, target.depthStencilLayout,
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, 0,
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, aspectMask);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToTransferSrc skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
return TransitionImageLayout(commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorToTransferDst(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToTransferDst skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
return TransitionImageLayout(commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorToGeneral(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToGeneral skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
return TransitionImageLayout(commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenDepthStencilToTransferSrc(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenDepthStencilToTransferSrc skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
if (target.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
return TransitionImageLayout(commandBuffer, target.depthStencilImage, target.depthStencilLayout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_READ_BIT, aspectMask);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenDepthStencilToTransferDst(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenDepthStencilToTransferDst skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
if (target.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
return TransitionImageLayout(commandBuffer, target.depthStencilImage, target.depthStencilLayout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT, aspectMask);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenDepthStencilToGeneral(VkCommandBuffer commandBuffer,
|
||||
Uint glFboExternalIndex) {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end()) {
|
||||
MGLOG_W("VkFramebufferManager::TransitionOffscreenDepthStencilToGeneral skipped: FBO %u not found",
|
||||
glFboExternalIndex);
|
||||
return false;
|
||||
}
|
||||
auto& target = it->second;
|
||||
if (target.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (target.depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
target.depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
return TransitionImageLayout(commandBuffer, target.depthStencilImage, target.depthStencilLayout,
|
||||
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, aspectMask);
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionOffscreenColorTextureToShaderRead(VkCommandBuffer commandBuffer,
|
||||
Uint textureExternalIndex) {
|
||||
for (auto& [_, target] : m_offscreenColorTargets) {
|
||||
if (target.colorTextureExternalIndex != textureExternalIndex || target.image == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
const Bool fromUndefined = (target.layout == VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
return TransitionImageLayout(
|
||||
commandBuffer, target.image, target.layout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
fromUndefined ? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
|
||||
: (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT),
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
fromUndefined ? 0 : (VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT),
|
||||
VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage,
|
||||
VkExtent2D& outExtent) const {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end() || it->second.image == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outImage = it->second.image;
|
||||
outExtent = it->second.extent;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::GetOffscreenDepthStencilImage(Uint glFboExternalIndex, VkImage& outImage,
|
||||
VkExtent2D& outExtent, VkFormat& outFormat) const {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end() || it->second.depthStencilImage == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outImage = it->second.depthStencilImage;
|
||||
outExtent = it->second.extent;
|
||||
outFormat = it->second.depthStencilFormat;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::GetOffscreenColorViewByTexture(Uint textureExternalIndex,
|
||||
VkImageView& outImageView) const {
|
||||
for (const auto& [_, target] : m_offscreenColorTargets) {
|
||||
if (target.colorTextureExternalIndex != textureExternalIndex || target.imageView == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
outImageView = target.imageView;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::GetOffscreenRenderSurface(Uint glFboExternalIndex, VkImageView& outColorView,
|
||||
VkFormat& outColorFormat, VkImageView& outDepthStencilView,
|
||||
VkFormat& outDepthStencilFormat, VkExtent2D& outExtent) const {
|
||||
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
|
||||
if (it == m_offscreenColorTargets.end() || it->second.imageView == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outColorView = it->second.imageView;
|
||||
outColorFormat = it->second.format;
|
||||
outDepthStencilView = it->second.depthStencilImageView;
|
||||
outExtent = it->second.extent;
|
||||
outDepthStencilFormat = it->second.depthStencilFormat;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::RecreateOffscreenColorTarget(
|
||||
OffscreenColorTarget& target, const MG_State::GLState::FramebufferObject& glFbo,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment, Uint16 glObjectVersion) {
|
||||
DestroyOffscreenColorTarget(target);
|
||||
|
||||
const auto size = colorAttachment.GetSize();
|
||||
if (size.x() <= 0 || size.y() <= 0) {
|
||||
MGLOG_W("VkFramebufferManager: COLOR0 attachment size is invalid (%d, %d)", size.x(), size.y());
|
||||
return false;
|
||||
}
|
||||
|
||||
const VkFormat format = ResolveColorFormat(colorAttachment);
|
||||
if (format == VK_FORMAT_UNDEFINED) {
|
||||
MGLOG_W("VkFramebufferManager: COLOR0 attachment format is unsupported for Vulkan clear");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent.width = static_cast<Uint32>(size.x());
|
||||
imageInfo.extent.height = static_cast<Uint32>(size.y());
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.format = format;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &target.image), "vkCreateImage(offscreen color)");
|
||||
|
||||
VkMemoryRequirements memoryRequirements{};
|
||||
vkGetImageMemoryRequirements(m_device, target.image, &memoryRequirements);
|
||||
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = memoryRequirements.size;
|
||||
allocInfo.memoryTypeIndex =
|
||||
FindMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &target.memory), "vkAllocateMemory(offscreen color)");
|
||||
VK_VERIFY(vkBindImageMemory(m_device, target.image, target.memory, 0), "vkBindImageMemory(offscreen color)");
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = target.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = format;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &target.imageView),
|
||||
"vkCreateImageView(offscreen color)");
|
||||
|
||||
const auto& depthAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Depth);
|
||||
const auto& stencilAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Stencil);
|
||||
const Bool requestedDepthStencil = (depthAttachment.IsValid() && !depthAttachment.IsEmpty()) ||
|
||||
(stencilAttachment.IsValid() && !stencilAttachment.IsEmpty());
|
||||
VkFormat depthStencilFormat = ResolveDepthStencilFormat(depthAttachment, stencilAttachment);
|
||||
if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT) {
|
||||
depthStencilFormat =
|
||||
FindSupportedDepthStencilFormat({VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT});
|
||||
} else if (depthStencilFormat == VK_FORMAT_D32_SFLOAT) {
|
||||
depthStencilFormat = FindSupportedDepthStencilFormat({VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM});
|
||||
}
|
||||
const Bool hasDepthStencil = (depthStencilFormat != VK_FORMAT_UNDEFINED);
|
||||
if (requestedDepthStencil && !hasDepthStencil) {
|
||||
MGLOG_W("VkFramebufferManager: FBO %u depth/stencil attachment exists but format is unsupported",
|
||||
glFbo.GetExternalIndex());
|
||||
}
|
||||
if (hasDepthStencil) {
|
||||
VkImageCreateInfo depthImageInfo{};
|
||||
depthImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
depthImageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
depthImageInfo.extent.width = static_cast<Uint32>(size.x());
|
||||
depthImageInfo.extent.height = static_cast<Uint32>(size.y());
|
||||
depthImageInfo.extent.depth = 1;
|
||||
depthImageInfo.mipLevels = 1;
|
||||
depthImageInfo.arrayLayers = 1;
|
||||
depthImageInfo.format = depthStencilFormat;
|
||||
depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(m_device, &depthImageInfo, nullptr, &target.depthStencilImage),
|
||||
"vkCreateImage(offscreen depth/stencil)");
|
||||
|
||||
VkMemoryRequirements depthMemoryRequirements{};
|
||||
vkGetImageMemoryRequirements(m_device, target.depthStencilImage, &depthMemoryRequirements);
|
||||
VkMemoryAllocateInfo depthAllocInfo{};
|
||||
depthAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
depthAllocInfo.allocationSize = depthMemoryRequirements.size;
|
||||
depthAllocInfo.memoryTypeIndex =
|
||||
FindMemoryType(depthMemoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &depthAllocInfo, nullptr, &target.depthStencilMemory),
|
||||
"vkAllocateMemory(offscreen depth/stencil)");
|
||||
VK_VERIFY(vkBindImageMemory(m_device, target.depthStencilImage, target.depthStencilMemory, 0),
|
||||
"vkBindImageMemory(offscreen depth/stencil)");
|
||||
|
||||
VkImageAspectFlags depthAspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
depthAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
VkImageViewCreateInfo depthViewInfo{};
|
||||
depthViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
depthViewInfo.image = target.depthStencilImage;
|
||||
depthViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
depthViewInfo.format = depthStencilFormat;
|
||||
depthViewInfo.subresourceRange.aspectMask = depthAspectMask;
|
||||
depthViewInfo.subresourceRange.baseMipLevel = 0;
|
||||
depthViewInfo.subresourceRange.levelCount = 1;
|
||||
depthViewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
depthViewInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &depthViewInfo, nullptr, &target.depthStencilImageView),
|
||||
"vkCreateImageView(offscreen depth/stencil)");
|
||||
}
|
||||
|
||||
target.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
target.extent = {static_cast<Uint32>(size.x()), static_cast<Uint32>(size.y())};
|
||||
target.format = format;
|
||||
target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
target.depthStencilFormat = depthStencilFormat;
|
||||
target.glObjectVersion = glObjectVersion;
|
||||
target.colorTextureExternalIndex = (colorAttachment.IsTexture() && colorAttachment.GetTexture())
|
||||
? colorAttachment.GetTexture()->GetExternalIndex()
|
||||
: 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkFramebufferManager::DestroyOffscreenColorTarget(OffscreenColorTarget& target) {
|
||||
if (target.imageView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(m_device, target.imageView, nullptr);
|
||||
target.imageView = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.depthStencilImageView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(m_device, target.depthStencilImageView, nullptr);
|
||||
target.depthStencilImageView = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.image != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(m_device, target.image, nullptr);
|
||||
target.image = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.depthStencilImage != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(m_device, target.depthStencilImage, nullptr);
|
||||
target.depthStencilImage = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(m_device, target.memory, nullptr);
|
||||
target.memory = VK_NULL_HANDLE;
|
||||
}
|
||||
if (target.depthStencilMemory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(m_device, target.depthStencilMemory, nullptr);
|
||||
target.depthStencilMemory = VK_NULL_HANDLE;
|
||||
}
|
||||
target.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
target.extent = {0, 0};
|
||||
target.format = VK_FORMAT_UNDEFINED;
|
||||
target.depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
target.glObjectVersion = 0;
|
||||
target.colorTextureExternalIndex = 0;
|
||||
}
|
||||
|
||||
Bool VkFramebufferManager::TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image,
|
||||
VkImageLayout& trackedLayout, VkImageLayout newLayout,
|
||||
VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
|
||||
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask) {
|
||||
if (image == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
if (trackedLayout == newLayout) {
|
||||
return true;
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.srcAccessMask = srcAccessMask;
|
||||
barrier.dstAccessMask = dstAccessMask;
|
||||
barrier.oldLayout = trackedLayout;
|
||||
barrier.newLayout = newLayout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange.aspectMask = aspectMask;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
|
||||
trackedLayout = newLayout;
|
||||
return true;
|
||||
}
|
||||
|
||||
Uint32 VkFramebufferManager::FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const {
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
|
||||
for (Uint32 i = 0; i < memoryProperties.memoryTypeCount; ++i) {
|
||||
if ((typeFilter & (1U << i)) &&
|
||||
(memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
MOBILEGL_ASSERT(false, "VkFramebufferManager::FindMemoryType failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
VkFormat VkFramebufferManager::ResolveColorFormat(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment) {
|
||||
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||
if (colorAttachment.IsTexture()) {
|
||||
const auto texture = colorAttachment.GetTexture();
|
||||
internalFormat = texture ? texture->GetFormat() : TextureInternalFormat::Unknown;
|
||||
} else if (colorAttachment.IsRenderbuffer()) {
|
||||
const auto renderbuffer = colorAttachment.GetRenderbuffer();
|
||||
internalFormat = renderbuffer ? renderbuffer->GetInternalFormat() : TextureInternalFormat::Unknown;
|
||||
}
|
||||
|
||||
switch (internalFormat) {
|
||||
case TextureInternalFormat::RGBA:
|
||||
case TextureInternalFormat::RGBA8:
|
||||
case TextureInternalFormat::SRGB8Alpha8:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
default:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat VkFramebufferManager::ResolveDepthStencilFormat(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& depthAttachment,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& stencilAttachment) {
|
||||
const auto resolveAttachmentFormat = [](const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||
if (attachment.IsTexture()) {
|
||||
const auto texture = attachment.GetTexture();
|
||||
internalFormat = texture ? texture->GetFormat() : TextureInternalFormat::Unknown;
|
||||
} else if (attachment.IsRenderbuffer()) {
|
||||
const auto renderbuffer = attachment.GetRenderbuffer();
|
||||
internalFormat = renderbuffer ? renderbuffer->GetInternalFormat() : TextureInternalFormat::Unknown;
|
||||
}
|
||||
return internalFormat;
|
||||
};
|
||||
|
||||
const auto depthFormat = resolveAttachmentFormat(depthAttachment);
|
||||
const auto stencilFormat = resolveAttachmentFormat(stencilAttachment);
|
||||
|
||||
switch (depthFormat) {
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
return VK_FORMAT_D24_UNORM_S8_UINT;
|
||||
case TextureInternalFormat::DepthComponent16:
|
||||
return VK_FORMAT_D16_UNORM;
|
||||
case TextureInternalFormat::DepthComponent24:
|
||||
case TextureInternalFormat::DepthComponent32:
|
||||
case TextureInternalFormat::DepthComponent32F:
|
||||
case TextureInternalFormat::DepthComponent:
|
||||
return VK_FORMAT_D32_SFLOAT;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (stencilFormat) {
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
return VK_FORMAT_D24_UNORM_S8_UINT;
|
||||
default:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat VkFramebufferManager::FindSupportedDepthStencilFormat(const Vector<VkFormat>& candidates) const {
|
||||
for (auto format : candidates) {
|
||||
VkFormatProperties properties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties);
|
||||
if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -1,83 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VkFramebufferManager {
|
||||
public:
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
VkFramebufferManager() = default;
|
||||
~VkFramebufferManager() = default;
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
Bool EnsureOffscreenColorTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo);
|
||||
Bool TransitionOffscreenColorToAttachment(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenColorToTransferDst(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenColorToGeneral(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenDepthStencilToTransferSrc(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenDepthStencilToTransferDst(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenDepthStencilToGeneral(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
|
||||
Bool TransitionOffscreenColorTextureToShaderRead(VkCommandBuffer commandBuffer, Uint textureExternalIndex);
|
||||
Bool GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent) const;
|
||||
Bool GetOffscreenDepthStencilImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent,
|
||||
VkFormat& outFormat) const;
|
||||
Bool GetOffscreenColorViewByTexture(Uint textureExternalIndex, VkImageView& outImageView) const;
|
||||
Bool GetOffscreenRenderSurface(Uint glFboExternalIndex, VkImageView& outColorView, VkFormat& outColorFormat,
|
||||
VkImageView& outDepthStencilView, VkFormat& outDepthStencilFormat,
|
||||
VkExtent2D& outExtent) const;
|
||||
|
||||
private:
|
||||
struct OffscreenColorTarget {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkImage depthStencilImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory depthStencilMemory = VK_NULL_HANDLE;
|
||||
VkImageView depthStencilImageView = VK_NULL_HANDLE;
|
||||
VkImageLayout depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
Uint16 glObjectVersion = 0;
|
||||
Uint colorTextureExternalIndex = 0;
|
||||
};
|
||||
|
||||
Bool RecreateOffscreenColorTarget(OffscreenColorTarget& target,
|
||||
const MG_State::GLState::FramebufferObject& glFbo,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment,
|
||||
Uint16 glObjectVersion);
|
||||
void DestroyOffscreenColorTarget(OffscreenColorTarget& target);
|
||||
Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout,
|
||||
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
|
||||
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask);
|
||||
Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const;
|
||||
static VkFormat ResolveColorFormat(const MG_State::GLState::FramebufferAttachmentObject& colorAttachment);
|
||||
static VkFormat ResolveDepthStencilFormat(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& depthAttachment,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& stencilAttachment);
|
||||
VkFormat FindSupportedDepthStencilFormat(const Vector<VkFormat>& candidates) const;
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
UnorderedMap<Uint, OffscreenColorTarget> m_offscreenColorTargets;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,79 +8,247 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SwapchainObject.h"
|
||||
#include "VkClearManager.h"
|
||||
#include "VkTextureManager.h"
|
||||
#include "../VkIncludes.h"
|
||||
#include "../VulkanRendererConfig.h"
|
||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
enum class TrackedAttachmentTarget : Uint8 {
|
||||
Texture,
|
||||
Renderbuffer,
|
||||
SwapchainColor,
|
||||
SwapchainDepthStencil
|
||||
};
|
||||
|
||||
struct PendingClearAttachmentInfo {
|
||||
// Index into the render pass attachment descriptions (VkRenderPassBeginInfo::pClearValues space).
|
||||
Uint32 attachmentIndex = 0;
|
||||
// Index into the subpass pColorAttachments (VkClearAttachment::colorAttachment space) — the GL
|
||||
// draw-buffer slot. Differs from attachmentIndex when earlier slots are GL_NONE/incomplete.
|
||||
// Only meaningful for color clears.
|
||||
Uint32 colorAttachmentSlot = 0;
|
||||
PendingClearKey key{};
|
||||
MG_State::GLState::RenderbufferObject* renderbuffer = nullptr;
|
||||
Bool hasInlinePayload = false;
|
||||
ClearAttachmentPayload inlinePayload{};
|
||||
};
|
||||
|
||||
struct TrackedAttachmentLayoutInfo {
|
||||
TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture;
|
||||
WeakPtr<MG_State::GLState::ITextureObject> texture;
|
||||
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||
Uint32 textureMipLevel = 0;
|
||||
Uint32 swapchainImageIndex = 0;
|
||||
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
};
|
||||
|
||||
struct DepthStencilAttachmentLoadInfo {
|
||||
VkAttachmentLoadOp depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
VkAttachmentLoadOp stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
};
|
||||
|
||||
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
|
||||
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil);
|
||||
IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent,
|
||||
VkExtent2D swapchainExtent);
|
||||
|
||||
struct RenderPassEntry {
|
||||
static inline VkDevice s_device;
|
||||
static inline Vector<VkTextureManager::TextureResource*> s_textureResourcesScratch;
|
||||
Uint64 hash = 0;
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
Uint64 compatibilityHash = 0;
|
||||
Vector<PendingClearAttachmentInfo> pendingClearAttachments;
|
||||
Vector<TrackedAttachmentLayoutInfo> trackedAttachmentLayouts;
|
||||
Uint32 attachmentCount = 0;
|
||||
Uint32 colorAttachmentCount = 0;
|
||||
Bool hasDepthStencilAttachment = false;
|
||||
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
IntVec2 extent = {0, 0};
|
||||
// VkFramebufferCreateInfo::layers of the entry's framebuffer (>1 for layered GL attachments).
|
||||
Uint32 layers = 1;
|
||||
// Frame counter value of the last GetOrCreateRenderPass hit; drives cache eviction.
|
||||
Uint64 lastUsedFrame = 0;
|
||||
|
||||
RenderPassEntry() = default;
|
||||
RenderPassEntry(const RenderPassEntry&) = delete;
|
||||
RenderPassEntry(RenderPassEntry&& that) noexcept {
|
||||
std::swap(hash, that.hash);
|
||||
std::swap(renderPass, that.renderPass);
|
||||
std::swap(framebuffer, that.framebuffer);
|
||||
std::swap(compatibilityHash, that.compatibilityHash);
|
||||
std::swap(pendingClearAttachments, that.pendingClearAttachments);
|
||||
std::swap(trackedAttachmentLayouts, that.trackedAttachmentLayouts);
|
||||
std::swap(attachmentCount, that.attachmentCount);
|
||||
std::swap(colorAttachmentCount, that.colorAttachmentCount);
|
||||
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
|
||||
std::swap(sampleCount, that.sampleCount);
|
||||
std::swap(extent, that.extent);
|
||||
std::swap(layers, that.layers);
|
||||
std::swap(lastUsedFrame, that.lastUsedFrame);
|
||||
}
|
||||
RenderPassEntry(
|
||||
Uint64 hash,
|
||||
VkRenderPass renderpass,
|
||||
VkFramebuffer framebuffer,
|
||||
Uint64 compatibilityHash,
|
||||
const Vector<PendingClearAttachmentInfo>& pendingClearAttachments,
|
||||
const Vector<TrackedAttachmentLayoutInfo>& trackedAttachmentLayouts,
|
||||
Uint32 attachmentCount,
|
||||
Uint32 colorAttachmentCount,
|
||||
Bool hasDepthStencilAttachment,
|
||||
VkSampleCountFlagBits sampleCount,
|
||||
IntVec2 extent, Uint32 layers):
|
||||
hash(hash),
|
||||
renderPass(renderpass),
|
||||
framebuffer(framebuffer),
|
||||
compatibilityHash(compatibilityHash),
|
||||
pendingClearAttachments(Move(pendingClearAttachments)),
|
||||
trackedAttachmentLayouts(Move(trackedAttachmentLayouts)),
|
||||
attachmentCount(attachmentCount),
|
||||
colorAttachmentCount(colorAttachmentCount),
|
||||
hasDepthStencilAttachment(hasDepthStencilAttachment),
|
||||
sampleCount(sampleCount),
|
||||
extent(extent),
|
||||
layers(layers)
|
||||
{}
|
||||
|
||||
~RenderPassEntry() {
|
||||
if (renderPass != VK_NULL_HANDLE) {
|
||||
vkDestroyRenderPass(s_device, renderPass, nullptr);
|
||||
}
|
||||
if (framebuffer != VK_NULL_HANDLE) {
|
||||
vkDestroyFramebuffer(s_device, framebuffer, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Bool CompatibleWith(const RenderPassEntry& that) const {
|
||||
return this->compatibilityHash == that.compatibilityHash;
|
||||
}
|
||||
|
||||
Bool CompatibleWith(Uint64 compatibilityHash) const {
|
||||
return this->compatibilityHash == compatibilityHash;
|
||||
}
|
||||
};
|
||||
|
||||
struct ActiveRenderPassInfo {
|
||||
Uint64 hash = 0;
|
||||
Uint64 compatibilityHash = 0;
|
||||
Vector<TrackedAttachmentLayoutInfo> trackedAttachmentLayouts;
|
||||
IntVec2 extent = {0, 0};
|
||||
|
||||
Bool CompatibleWith(const RenderPassEntry& that) const {
|
||||
return compatibilityHash == that.compatibilityHash;
|
||||
}
|
||||
|
||||
Bool CompatibleWith(Uint64 thatCompatibilityHash) const {
|
||||
return compatibilityHash == thatCompatibilityHash;
|
||||
}
|
||||
};
|
||||
|
||||
class VkRenderPassManager {
|
||||
public:
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
};
|
||||
using HashType = Uint64;
|
||||
VkRenderPassManager(VkDevice device,
|
||||
VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config,
|
||||
VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject);
|
||||
~VkRenderPassManager();
|
||||
|
||||
struct OffscreenRenderTargetInfo {
|
||||
Uint targetExternalIndex = 0;
|
||||
Uint16 targetVersion = 0;
|
||||
VkImageView colorView = VK_NULL_HANDLE;
|
||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkImageView depthStencilView = VK_NULL_HANDLE;
|
||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
};
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
Bool Initialize();
|
||||
void Shutdown();
|
||||
|
||||
Bool RecreateDefaultFramebuffers(const Vector<VkImageView>& colorViews,
|
||||
const Vector<VkImageView>& depthStencilViews, VkExtent2D extent);
|
||||
Bool GetDefaultRenderTarget(Uint32 imageIndex, VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const;
|
||||
|
||||
Bool EnsureOffscreenRenderTarget(const OffscreenRenderTargetInfo& targetInfo);
|
||||
Bool GetOffscreenRenderTarget(Uint targetExternalIndex, VkRenderPass& outRenderPass,
|
||||
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
|
||||
VkFormat& outDepthStencilFormat) const;
|
||||
void RemoveOffscreenRenderTarget(Uint targetExternalIndex);
|
||||
|
||||
void BeginRenderPass(VkCommandBuffer commandBuffer, VkRenderPass renderPass, VkFramebuffer framebuffer,
|
||||
VkExtent2D extent) const;
|
||||
void EndRenderPass(VkCommandBuffer commandBuffer) const;
|
||||
void RecordColorClear(VkCommandBuffer commandBuffer, VkExtent2D extent,
|
||||
const VkClearColorValue& clearColor) const;
|
||||
void RecordDepthStencilClear(VkCommandBuffer commandBuffer, VkExtent2D extent, GLbitfield mask, Float depth,
|
||||
Uint32 stencil, VkFormat depthStencilFormat) const;
|
||||
|
||||
VkRenderPass GetLoadRenderPass() const;
|
||||
VkRenderPass GetClearRenderPass() const;
|
||||
|
||||
HashType ComputeHash(
|
||||
const MG_State::GLState::FramebufferObject& fbo,
|
||||
Uint32 swapchainImageIndex,
|
||||
Bool includePendingClear = true);
|
||||
RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex);
|
||||
void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferObject& drawFbo);
|
||||
void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer);
|
||||
// Frame boundary hook: ages the render-pass cache and evicts long-unused
|
||||
// entries (their command buffers retired many frames ago).
|
||||
void OnPresent();
|
||||
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
|
||||
static Bool EndRenderPass(VkCommandBuffer commandBuffer);
|
||||
static ActiveRenderPassInfo* GetActiveRenderPass();
|
||||
private:
|
||||
struct OffscreenRenderTarget {
|
||||
Uint16 targetVersion = 0;
|
||||
VkImageView colorView = VK_NULL_HANDLE;
|
||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkImageView depthStencilView = VK_NULL_HANDLE;
|
||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
const VulkanRendererConfig& m_config;
|
||||
VkClearManager& m_clearManager;
|
||||
VkTextureManager& m_textureManager;
|
||||
SwapchainObject& m_swapchainObject;
|
||||
UnorderedMap<Uint64, RenderPassEntry> m_renderPasses;
|
||||
// Monotonic frame counter (bumped in OnPresent) for render-pass cache aging.
|
||||
Uint64 m_frameCounter = 0;
|
||||
|
||||
// Bumped whenever a renderbuffer VkImage is (re)created; together with the texture
|
||||
// manager's image epoch this invalidates the render-pass fast path on any attachment
|
||||
// image recreation.
|
||||
Uint64 m_renderbufferImageEpoch = 1;
|
||||
|
||||
// Per-draw fast-path memo for GetOrCreateRenderPass (dirty-flag state tracking): when the
|
||||
// framebuffer state is provably unchanged since the last resolution, the active render pass
|
||||
// is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch /
|
||||
// version change, swapchain rotation, any attachment image recreation (the two epochs),
|
||||
// or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed).
|
||||
Bool m_rpFastValid = false;
|
||||
const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr;
|
||||
Uint16 m_rpFastFboVersion = 0;
|
||||
Uint32 m_rpFastSwapchainIndex = 0;
|
||||
Uint64 m_rpFastTexEpoch = 0;
|
||||
Uint64 m_rpFastRbEpoch = 0;
|
||||
Uint64 m_rpFastRenderPassHash = 0;
|
||||
|
||||
struct RenderbufferResource {
|
||||
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VmaAllocation allocation = nullptr;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
||||
VkExtent2D extent = {0, 0};
|
||||
VkRenderPass renderPassLoad = VK_NULL_HANDLE;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||
Int samples = 0;
|
||||
|
||||
void Destroy(VkDevice device, VmaAllocator allocator);
|
||||
};
|
||||
|
||||
VkRenderPass CreateDefaultRenderPass(VkAttachmentLoadOp colorLoadOp) const;
|
||||
VkRenderPass CreateRenderPass(VkFormat colorFormat, VkFormat depthStencilFormat, VkAttachmentLoadOp colorLoadOp,
|
||||
VkImageLayout colorFinalLayout) const;
|
||||
void DestroyDefaultFramebuffers();
|
||||
void DestroyOffscreenRenderTarget(OffscreenRenderTarget& target);
|
||||
static Bool HasStencilComponent(VkFormat format);
|
||||
struct PendingRenderbufferClear {
|
||||
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||
ClearAttachmentPayload payload{};
|
||||
};
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkFormat m_colorFormat = VK_FORMAT_UNDEFINED;
|
||||
VkFormat m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
VkRenderPass m_renderPassLoad = VK_NULL_HANDLE;
|
||||
VkRenderPass m_renderPassClear = VK_NULL_HANDLE;
|
||||
Vector<VkFramebuffer> m_defaultFramebuffers;
|
||||
VkExtent2D m_defaultExtent = {0, 0};
|
||||
UnorderedMap<Uint, OffscreenRenderTarget> m_offscreenRenderTargets;
|
||||
UnorderedMap<MG_State::GLState::RenderbufferObject*, RenderbufferResource> m_renderbufferResources;
|
||||
UnorderedMap<MG_State::GLState::RenderbufferObject*, PendingRenderbufferClear> m_pendingRenderbufferClears;
|
||||
|
||||
RenderbufferResource* GetOrCreateRenderbufferResource(
|
||||
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||
Bool GetPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer,
|
||||
ClearAttachmentPayload& outPayload) const;
|
||||
Bool HasPendingRenderbufferClear(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) const;
|
||||
void CollectRenderbufferGarbage();
|
||||
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
static inline ActiveRenderPassInfo s_activeRenderPass{};
|
||||
static inline Bool s_hasActiveRenderPass = false;
|
||||
static inline VkClearManager* s_clearManager = nullptr;
|
||||
static inline VkTextureManager* s_textureManager = nullptr;
|
||||
static inline SwapchainObject* s_swapchainObject = nullptr;
|
||||
static inline VkRenderPassManager* s_renderPassManager = nullptr;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "VkSamplerManager.h"
|
||||
|
||||
#include "MG_State/GLState/Core.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
Bool UsesBorderColor(const MG_State::GLState::SamplerObject& sampler) {
|
||||
return sampler.GetWrapS() == SamplerWrapMode::ClampToBorder ||
|
||||
sampler.GetWrapT() == SamplerWrapMode::ClampToBorder ||
|
||||
sampler.GetWrapR() == SamplerWrapMode::ClampToBorder;
|
||||
}
|
||||
|
||||
Bool IsDepthTextureFormat(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::DepthComponent:
|
||||
case TextureInternalFormat::DepthComponent16:
|
||||
case TextureInternalFormat::DepthComponent24:
|
||||
case TextureInternalFormat::DepthComponent32:
|
||||
case TextureInternalFormat::DepthComponent32F:
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Bool NearlyEqual(Float lhs, Float rhs) {
|
||||
return std::fabs(lhs - rhs) <= 1e-6f;
|
||||
}
|
||||
|
||||
Float ResolveEffectiveMaxLod(const MG_State::GLState::SamplerObject& sampler) {
|
||||
if (sampler.GetMipmapMode() == SamplerMipmapMode::None) {
|
||||
return 0.0f;
|
||||
}
|
||||
return sampler.GetMaxLod();
|
||||
}
|
||||
|
||||
Float ResolveEffectiveMinLod(const MG_State::GLState::SamplerObject& sampler, Float effectiveMaxLod) {
|
||||
return std::min(sampler.GetMinLod(), effectiveMaxLod);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool VkSamplerManager::Initialize(const InitInfo& initInfo) {
|
||||
Shutdown();
|
||||
|
||||
m_device = initInfo.device;
|
||||
m_config = initInfo.config;
|
||||
m_samplerAnisotropySupported = initInfo.samplerAnisotropySupported;
|
||||
m_maxSamplerAnisotropy = std::max(initInfo.maxSamplerAnisotropy, 1.0f);
|
||||
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_config != nullptr,
|
||||
"VkSamplerManager::Initialize failed: invalid initialization info");
|
||||
return true;
|
||||
}
|
||||
|
||||
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
|
||||
Bool forceNearestFiltering) const {
|
||||
if (!m_samplerAnisotropySupported) return 1.0f;
|
||||
if (forceNearestFiltering) return 1.0f;
|
||||
// VUID-VkSamplerCreateInfo-anisotropyEnable-01071/01072: anisotropy requires both filters to
|
||||
// be LINEAR and the value to sit within [1, limits.maxSamplerAnisotropy].
|
||||
if (sampler.GetMinFilter() != SamplerFilterMode::Linear ||
|
||||
sampler.GetMagFilter() != SamplerFilterMode::Linear) {
|
||||
return 1.0f;
|
||||
}
|
||||
return std::clamp(sampler.GetMaxAnisotropy(), 1.0f, m_maxSamplerAnisotropy);
|
||||
}
|
||||
|
||||
void VkSamplerManager::Shutdown() {
|
||||
for (auto& [_, sampler] : m_samplers) {
|
||||
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(m_device, sampler.handle, nullptr);
|
||||
}
|
||||
sampler.handle = VK_NULL_HANDLE;
|
||||
}
|
||||
m_samplers.clear();
|
||||
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_config = nullptr;
|
||||
}
|
||||
|
||||
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering) const {
|
||||
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config->CacheVersion));
|
||||
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &forceNearestFiltering, sizeof(forceNearestFiltering)));
|
||||
|
||||
const auto minFilter = sampler.GetMinFilter();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minFilter, sizeof(minFilter)));
|
||||
const auto magFilter = sampler.GetMagFilter();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &magFilter, sizeof(magFilter)));
|
||||
const auto mipmapMode = sampler.GetMipmapMode();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &mipmapMode, sizeof(mipmapMode)));
|
||||
const auto wrapS = sampler.GetWrapS();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapS, sizeof(wrapS)));
|
||||
const auto wrapT = sampler.GetWrapT();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapT, sizeof(wrapT)));
|
||||
const auto wrapR = sampler.GetWrapR();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapR, sizeof(wrapR)));
|
||||
const auto maxLod = ResolveEffectiveMaxLod(sampler);
|
||||
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
||||
const auto lodBias = sampler.GetLodBias();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
|
||||
// The RESOLVED value, not the GL request: samplers that only differ in an anisotropy Vulkan
|
||||
// will not apply (NEAREST filtering, or requests past the device limit) must still share one
|
||||
// VkSampler, while two samplers that really do differ must not collide onto the first one's.
|
||||
const auto maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy)));
|
||||
const auto compareMode = sampler.GetCompareMode();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
||||
const auto compareFunc = ResolveCompareFunc(sampler, texture);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
||||
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
||||
return XXH64_digest(m_hashState);
|
||||
}
|
||||
|
||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering) {
|
||||
const Uint64 key = BuildSamplerKey(sampler, texture, forceNearestFiltering);
|
||||
auto it = m_samplers.find(key);
|
||||
if (it != m_samplers.end()) {
|
||||
return it->second.handle;
|
||||
}
|
||||
|
||||
VkSamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerInfo.magFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMagFilter());
|
||||
samplerInfo.minFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMinFilter());
|
||||
samplerInfo.mipmapMode = forceNearestFiltering ? VK_SAMPLER_MIPMAP_MODE_NEAREST
|
||||
: ToVkMipmapMode(sampler.GetMipmapMode());
|
||||
samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS());
|
||||
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
||||
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
||||
samplerInfo.mipLodBias = sampler.GetLodBias();
|
||||
// Must use the same resolver as BuildSamplerKey - a divergence would either collide two
|
||||
// different samplers or silently create duplicates.
|
||||
const Float maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering);
|
||||
samplerInfo.anisotropyEnable = maxAnisotropy > 1.0f ? VK_TRUE : VK_FALSE;
|
||||
samplerInfo.maxAnisotropy = maxAnisotropy;
|
||||
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
||||
samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
|
||||
samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler);
|
||||
samplerInfo.minLod = ResolveEffectiveMinLod(sampler, samplerInfo.maxLod);
|
||||
samplerInfo.borderColor = ResolveVkBorderColor(sampler, texture);
|
||||
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||
|
||||
VkSampler vkSampler = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &vkSampler), "vkCreateSampler(texture)");
|
||||
|
||||
SamplerCacheEntry entry{};
|
||||
entry.handle = vkSampler;
|
||||
entry.externalIndex = sampler.GetExternalIndex();
|
||||
entry.version = sampler.GetVersion();
|
||||
m_samplers[key] = entry;
|
||||
return vkSampler;
|
||||
}
|
||||
|
||||
VkFilter VkSamplerManager::ToVkFilter(SamplerFilterMode mode) {
|
||||
return mode == SamplerFilterMode::Nearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||
}
|
||||
|
||||
VkSamplerMipmapMode VkSamplerManager::ToVkMipmapMode(SamplerMipmapMode mode) {
|
||||
switch (mode) {
|
||||
case SamplerMipmapMode::Nearest:
|
||||
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
case SamplerMipmapMode::Linear:
|
||||
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
case SamplerMipmapMode::None:
|
||||
default:
|
||||
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
}
|
||||
}
|
||||
|
||||
VkSamplerAddressMode VkSamplerManager::ToVkAddressMode(SamplerWrapMode mode) {
|
||||
switch (mode) {
|
||||
case SamplerWrapMode::ClampToEdge:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
case SamplerWrapMode::MirroredRepeat:
|
||||
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
||||
case SamplerWrapMode::Repeat:
|
||||
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
case SamplerWrapMode::ClampToBorder:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||
case SamplerWrapMode::MirrorClampToEdge:
|
||||
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
|
||||
default:
|
||||
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
}
|
||||
}
|
||||
|
||||
VkCompareOp VkSamplerManager::ToVkCompareOp(SamplerCompareFunc func) {
|
||||
switch (func) {
|
||||
case SamplerCompareFunc::Never:
|
||||
return VK_COMPARE_OP_NEVER;
|
||||
case SamplerCompareFunc::Less:
|
||||
return VK_COMPARE_OP_LESS;
|
||||
case SamplerCompareFunc::Equal:
|
||||
return VK_COMPARE_OP_EQUAL;
|
||||
case SamplerCompareFunc::LessEqual:
|
||||
return VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
case SamplerCompareFunc::Greater:
|
||||
return VK_COMPARE_OP_GREATER;
|
||||
case SamplerCompareFunc::NotEqual:
|
||||
return VK_COMPARE_OP_NOT_EQUAL;
|
||||
case SamplerCompareFunc::GreaterEqual:
|
||||
return VK_COMPARE_OP_GREATER_OR_EQUAL;
|
||||
case SamplerCompareFunc::Always:
|
||||
default:
|
||||
return VK_COMPARE_OP_ALWAYS;
|
||||
}
|
||||
}
|
||||
|
||||
SamplerCompareFunc VkSamplerManager::ResolveCompareFunc(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture) {
|
||||
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
||||
if (sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture &&
|
||||
IsDepthTextureFormat(texture.GetFormat()) && compareFunc == SamplerCompareFunc::Always) {
|
||||
return SamplerCompareFunc::LessEqual;
|
||||
}
|
||||
|
||||
return compareFunc;
|
||||
}
|
||||
|
||||
VkBorderColor VkSamplerManager::ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture) {
|
||||
if (!UsesBorderColor(sampler)) {
|
||||
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
}
|
||||
|
||||
const auto& borderColor = texture.GetBorderColor();
|
||||
const Bool isDepthTexture = IsDepthTextureFormat(texture.GetFormat());
|
||||
|
||||
if (isDepthTexture) {
|
||||
if (NearlyEqual(borderColor.x(), 1.0f)) {
|
||||
return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
}
|
||||
if (NearlyEqual(borderColor.x(), 0.0f)) {
|
||||
return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
const Bool rgbZero = NearlyEqual(borderColor.x(), 0.0f) && NearlyEqual(borderColor.y(), 0.0f) &&
|
||||
NearlyEqual(borderColor.z(), 0.0f);
|
||||
if (rgbZero && NearlyEqual(borderColor.w(), 0.0f)) {
|
||||
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
}
|
||||
if (rgbZero && NearlyEqual(borderColor.w(), 1.0f)) {
|
||||
return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
|
||||
}
|
||||
if (NearlyEqual(borderColor.x(), 1.0f) && NearlyEqual(borderColor.y(), 1.0f) &&
|
||||
NearlyEqual(borderColor.z(), 1.0f) && NearlyEqual(borderColor.w(), 1.0f)) {
|
||||
return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
}
|
||||
|
||||
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,72 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
#include "../VulkanRendererConfig.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class SamplerObject;
|
||||
class ITextureObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VkSamplerManager {
|
||||
public:
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
const VulkanRendererConfig* config = nullptr;
|
||||
// The samplerAnisotropy device feature was requested and granted at vkCreateDevice.
|
||||
Bool samplerAnisotropySupported = false;
|
||||
// VkPhysicalDeviceLimits::maxSamplerAnisotropy.
|
||||
Float maxSamplerAnisotropy = 1.0f;
|
||||
};
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering = false);
|
||||
|
||||
private:
|
||||
struct SamplerCacheEntry {
|
||||
VkSampler handle = VK_NULL_HANDLE;
|
||||
Uint externalIndex = 0;
|
||||
Uint16 version = 0;
|
||||
};
|
||||
|
||||
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering) const;
|
||||
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
||||
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
||||
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
||||
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
|
||||
static SamplerCompareFunc ResolveCompareFunc(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture);
|
||||
static VkBorderColor ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture);
|
||||
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
|
||||
// the sampler filters linearly both ways, otherwise the GL request clamped to the device limit.
|
||||
// GL happily carries GL_TEXTURE_MAX_ANISOTROPY on a NEAREST sampler (Blaze3D's blocks do exactly
|
||||
// that) while Vulkan forbids anisotropyEnable there, so the GL value must never be forwarded raw.
|
||||
Float ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
|
||||
Bool forceNearestFiltering) const;
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
const VulkanRendererConfig* m_config = nullptr;
|
||||
Bool m_samplerAnisotropySupported = false;
|
||||
Float m_maxSamplerAnisotropy = 1.0f;
|
||||
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,387 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ITextureObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
enum class SamplerNumericDomain : Uint8;
|
||||
|
||||
class VkTextureManager {
|
||||
public:
|
||||
// Monotonic epoch bumped whenever a texture VkImage is (re)created. The render-pass
|
||||
// manager keys its per-draw fast path on this so an attachment's image recreation
|
||||
// invalidates the cached render pass (dirty-flag tracking; portable to Vulkan 1.1).
|
||||
Uint64 GetTextureImageEpoch() const { return m_textureImageEpoch; }
|
||||
|
||||
struct TextureIdentity {
|
||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||
Uint64 lifetimeId = 0;
|
||||
|
||||
Bool operator==(const TextureIdentity& other) const {
|
||||
return texture == other.texture && lifetimeId == other.lifetimeId;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureIdentityHash {
|
||||
SizeT operator()(const TextureIdentity& key) const {
|
||||
SizeT hash = std::hash<MG_State::GLState::ITextureObject*>{}(key.texture);
|
||||
hash ^= std::hash<Uint64>{}(key.lifetimeId) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||
VmaAllocator allocator = nullptr;
|
||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
||||
Uint32 frameCount = 0;
|
||||
};
|
||||
|
||||
struct TextureResource {
|
||||
struct AttachmentViewKey {
|
||||
Uint32 mipLevel = 0;
|
||||
Uint32 baseArrayLayer = 0;
|
||||
Uint32 layerCount = 1;
|
||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
|
||||
Bool operator==(const AttachmentViewKey& other) const {
|
||||
return mipLevel == other.mipLevel &&
|
||||
baseArrayLayer == other.baseArrayLayer &&
|
||||
layerCount == other.layerCount &&
|
||||
viewType == other.viewType;
|
||||
}
|
||||
};
|
||||
|
||||
struct AttachmentViewKeyHash {
|
||||
SizeT operator()(const AttachmentViewKey& key) const {
|
||||
SizeT hash = std::hash<Uint32>{}(key.mipLevel);
|
||||
hash ^= std::hash<Uint32>{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Uint32>{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
|
||||
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct StorageImageViewKey {
|
||||
Uint32 mipLevel = 0;
|
||||
Uint32 baseArrayLayer = 0;
|
||||
Uint32 layerCount = 1;
|
||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
|
||||
Bool operator==(const StorageImageViewKey& other) const {
|
||||
return mipLevel == other.mipLevel &&
|
||||
baseArrayLayer == other.baseArrayLayer &&
|
||||
layerCount == other.layerCount &&
|
||||
viewType == other.viewType &&
|
||||
format == other.format;
|
||||
}
|
||||
};
|
||||
|
||||
struct SampledImageViewKey {
|
||||
Uint32 baseMipLevel = 0;
|
||||
Uint32 levelCount = 1;
|
||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
|
||||
Bool operator==(const SampledImageViewKey& other) const {
|
||||
return baseMipLevel == other.baseMipLevel &&
|
||||
levelCount == other.levelCount &&
|
||||
viewType == other.viewType &&
|
||||
format == other.format;
|
||||
}
|
||||
};
|
||||
|
||||
struct SampledImageViewKeyHash {
|
||||
SizeT operator()(const SampledImageViewKey& key) const {
|
||||
SizeT hash = std::hash<Uint32>{}(key.baseMipLevel);
|
||||
hash ^= std::hash<Uint32>{}(key.levelCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
|
||||
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.format)) +
|
||||
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct StorageImageViewKeyHash {
|
||||
SizeT operator()(const StorageImageViewKey& key) const {
|
||||
SizeT hash = std::hash<Uint32>{}(key.mipLevel);
|
||||
hash ^= std::hash<Uint32>{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Uint32>{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
|
||||
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.format)) +
|
||||
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VmaAllocation allocation = nullptr;
|
||||
VkImageView fullView = VK_NULL_HANDLE;
|
||||
VkImageView sampledView = VK_NULL_HANDLE;
|
||||
Vector<VkImageView> perMipViews;
|
||||
Vector<VkImageView> perMipSampledViews;
|
||||
UnorderedMap<AttachmentViewKey, VkImageView, AttachmentViewKeyHash> attachmentViews;
|
||||
UnorderedMap<SampledImageViewKey, VkImageView, SampledImageViewKeyHash> alternateSampledViews;
|
||||
UnorderedMap<StorageImageViewKey, VkImageView, StorageImageViewKeyHash> storageImageViews;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
Uint32 depth = 1;
|
||||
Uint32 arrayLayers = 1;
|
||||
Uint32 mipLevels = 1;
|
||||
Uint32 sampledBaseMipLevel = 0;
|
||||
Uint32 sampledLevelCount = 1;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkImageCreateFlags imageCreateFlags = 0;
|
||||
Uint16 syncedTextureParamsVersion = 0;
|
||||
// Snapshot of ITextureObject::GetContentVersion() at the last successful sync;
|
||||
// lets SyncTexture skip the whole re-check/re-upload when content is unchanged.
|
||||
Uint64 syncedContentVersion = 0;
|
||||
// Snapshot of the defined mip-level count at the last sync. Folded into the early-out key
|
||||
// as defense-in-depth: any path that grows the level set (which resizes the sampled view)
|
||||
// busts the skip even if it failed to bump the content version.
|
||||
Uint32 syncedMipLevelCount = 0;
|
||||
|
||||
TextureResource() = default;
|
||||
TextureResource(const TextureResource&) = delete;
|
||||
TextureResource(TextureResource&& that) noexcept {
|
||||
std::swap(this->image, that.image);
|
||||
std::swap(this->allocation, that.allocation);
|
||||
std::swap(this->fullView, that.fullView);
|
||||
std::swap(this->sampledView, that.sampledView);
|
||||
std::swap(this->perMipViews, that.perMipViews);
|
||||
std::swap(this->perMipSampledViews, that.perMipSampledViews);
|
||||
std::swap(this->attachmentViews, that.attachmentViews);
|
||||
std::swap(this->alternateSampledViews, that.alternateSampledViews);
|
||||
std::swap(this->storageImageViews, that.storageImageViews);
|
||||
std::swap(this->layout, that.layout);
|
||||
std::swap(this->extent, that.extent);
|
||||
std::swap(this->depth, that.depth);
|
||||
std::swap(this->arrayLayers, that.arrayLayers);
|
||||
std::swap(this->mipLevels, that.mipLevels);
|
||||
std::swap(this->sampledBaseMipLevel, that.sampledBaseMipLevel);
|
||||
std::swap(this->sampledLevelCount, that.sampledLevelCount);
|
||||
std::swap(this->format, that.format);
|
||||
std::swap(this->aspect, that.aspect);
|
||||
std::swap(this->viewType, that.viewType);
|
||||
std::swap(this->sampleCount, that.sampleCount);
|
||||
std::swap(this->imageCreateFlags, that.imageCreateFlags);
|
||||
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
||||
std::swap(this->syncedContentVersion, that.syncedContentVersion);
|
||||
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
if (fullView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(s_device, fullView, nullptr);
|
||||
}
|
||||
if (sampledView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(s_device, sampledView, nullptr);
|
||||
}
|
||||
for (const auto attachmentView : perMipViews) {
|
||||
if (attachmentView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(s_device, attachmentView, nullptr);
|
||||
}
|
||||
}
|
||||
for (const auto sampledView : perMipSampledViews) {
|
||||
if (sampledView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(s_device, sampledView, nullptr);
|
||||
}
|
||||
}
|
||||
for (const auto& [_, attachmentView] : attachmentViews) {
|
||||
if (attachmentView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(s_device, attachmentView, nullptr);
|
||||
}
|
||||
}
|
||||
for (const auto& [_, sampledView] : alternateSampledViews) {
|
||||
if (sampledView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(s_device, sampledView, nullptr);
|
||||
}
|
||||
}
|
||||
for (const auto& [_, storageImageView] : storageImageViews) {
|
||||
if (storageImageView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(s_device, storageImageView, nullptr);
|
||||
}
|
||||
}
|
||||
if (image != VK_NULL_HANDLE && allocation != nullptr) {
|
||||
vmaDestroyImage(s_allocator, image, allocation);
|
||||
}
|
||||
fullView = VK_NULL_HANDLE;
|
||||
sampledView = VK_NULL_HANDLE;
|
||||
perMipViews.clear();
|
||||
perMipSampledViews.clear();
|
||||
attachmentViews.clear();
|
||||
alternateSampledViews.clear();
|
||||
storageImageViews.clear();
|
||||
image = VK_NULL_HANDLE;
|
||||
allocation = nullptr;
|
||||
layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
extent = {0, 0};
|
||||
depth = 1;
|
||||
arrayLayers = 1;
|
||||
mipLevels = 1;
|
||||
sampledBaseMipLevel = 0;
|
||||
sampledLevelCount = 1;
|
||||
format = VK_FORMAT_UNDEFINED;
|
||||
aspect = VK_IMAGE_ASPECT_NONE;
|
||||
viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageCreateFlags = 0;
|
||||
syncedTextureParamsVersion = 0;
|
||||
syncedContentVersion = 0;
|
||||
syncedMipLevelCount = 0;
|
||||
}
|
||||
|
||||
~TextureResource() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
static inline VkDevice s_device = VK_NULL_HANDLE;
|
||||
static inline VmaAllocator s_allocator = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
void Shutdown();
|
||||
void BeginFrame(Uint32 frameIndex);
|
||||
|
||||
TextureResource* SyncTextureAndGetDescriptor(
|
||||
MG_State::GLState::ITextureObject& texture);
|
||||
VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
|
||||
VkImageView GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
Uint32 baseArrayLayer, Uint32 layerCount,
|
||||
VkImageViewType viewType);
|
||||
VkImageView GetOrCreateSampledViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
|
||||
VkImageView GetOrCreateSampledImageView(MG_State::GLState::ITextureObject& texture, VkFormat format);
|
||||
VkImageView GetOrCreateStorageImageView(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
VkFormat format, Bool layered, Int32 layer);
|
||||
void UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout);
|
||||
void UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject* texture,
|
||||
Uint32 writtenMipLevel,
|
||||
VkImageLayout newLayout);
|
||||
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||
|
||||
static VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect);
|
||||
static VkFormat ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain);
|
||||
static Bool AreSampledImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat);
|
||||
static Bool AreStorageImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat);
|
||||
|
||||
static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout,
|
||||
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
|
||||
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask,
|
||||
Uint32 baseMipLevel = 0, Uint32 levelCount = 1,
|
||||
Uint32 layerCount = 1);
|
||||
|
||||
SizeT CollectGarbage();
|
||||
|
||||
// Per-draw sync memo. Within a single SetupDraw the same sampled texture is
|
||||
// resolved ~3x (SetupDraw's layout-probe loop, its post-transition loop, and
|
||||
// again inside ResolveSamplerDescriptor). No GL texture mutation can happen
|
||||
// mid-SetupDraw, and layout is tracked on the TextureResource independently of
|
||||
// SyncTexture, so after the first successful sync of a texture in a draw the
|
||||
// heavy SyncTexture work (mip-completeness/resource/view resync + dirty scan)
|
||||
// is pure redundancy. BeginDrawSyncScope opens a window in which repeat
|
||||
// SyncTextureAndGetDescriptor calls short-circuit to the already-synced
|
||||
// resource; EndDrawSyncScope closes it. Use the RAII DrawSyncScope guard.
|
||||
void BeginDrawSyncScope();
|
||||
void EndDrawSyncScope();
|
||||
|
||||
// RAII guard that opens/closes a per-draw sync memo window (see above).
|
||||
class DrawSyncScope {
|
||||
public:
|
||||
explicit DrawSyncScope(VkTextureManager& manager) : m_manager(manager) { m_manager.BeginDrawSyncScope(); }
|
||||
~DrawSyncScope() { m_manager.EndDrawSyncScope(); }
|
||||
DrawSyncScope(const DrawSyncScope&) = delete;
|
||||
DrawSyncScope& operator=(const DrawSyncScope&) = delete;
|
||||
private:
|
||||
VkTextureManager& m_manager;
|
||||
};
|
||||
|
||||
private:
|
||||
// Bumped in SyncTextureResource right after vmaCreateImage(texture). See GetTextureImageEpoch().
|
||||
Uint64 m_textureImageEpoch = 1;
|
||||
|
||||
Bool SyncTexture(MG_State::GLState::ITextureObject &texture,
|
||||
TextureResource &outResource);
|
||||
Bool SyncTextureResource(const MG_State::GLState::ITextureObject &texture,
|
||||
TextureUploadTarget uploadTarget,
|
||||
const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels,
|
||||
TextureResource &resource);
|
||||
Bool SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource);
|
||||
VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect,
|
||||
VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount,
|
||||
Uint32 baseArrayLayer,
|
||||
Uint32 layerCount,
|
||||
const VkComponentMapping* components = nullptr,
|
||||
VkImageUsageFlags viewUsage = 0) const;
|
||||
Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture,
|
||||
TextureUploadTarget uploadTarget,
|
||||
TextureResource &outResource);
|
||||
static Bool CheckMipmapCompleteness(const MG_State::GLState::ITextureObject& texture,
|
||||
TextureUploadTarget& outTarget,
|
||||
IntVec3& outTexelSize,
|
||||
SizeT& outByteSize,
|
||||
Uint32& outMipLevelCount);
|
||||
static Uint32 GetUploadMipLevelCount(const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget target);
|
||||
static void ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels,
|
||||
Uint32& outBaseMipLevel, Uint32& outLevelCount);
|
||||
static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format);
|
||||
void DeferResourceRelease(TextureResource&& resource);
|
||||
void DeferViewRelease(VkImageView view);
|
||||
void CollectDeferredReleases(Uint32 frameIndex);
|
||||
void DestroyDeferredReleases();
|
||||
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
||||
void EraseTrackedTexture(const TextureIdentity& identity);
|
||||
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||
Uint32 m_currentFrameIndex = 0;
|
||||
|
||||
Uint8 m_gcCounter = 0;
|
||||
// Active only between BeginDrawSyncScope/EndDrawSyncScope; identities of
|
||||
// textures already fully synced in the current draw (small N -> flat scan).
|
||||
Bool m_drawSyncScopeActive = false;
|
||||
// Per-draw sync memo: the identity plus the resolved resource pointer. The pointer is stable
|
||||
// across rehash in the node-based m_textureResources and stays valid for the draw (a texture
|
||||
// synced this draw is alive and is not erased mid-draw), so a repeat sync of the same texture
|
||||
// returns the resource without re-hashing the identity into m_textureResources.
|
||||
struct DrawSyncedTexture {
|
||||
TextureIdentity identity;
|
||||
TextureResource* resource = nullptr;
|
||||
};
|
||||
Vector<DrawSyncedTexture> m_drawSyncedThisDraw;
|
||||
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
||||
Vector<Vector<TextureResource>> m_deferredReleases;
|
||||
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -1,644 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "VkTextureSamplerManager.h"
|
||||
|
||||
#include "MG_State/GLState/Core.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
constexpr Uint64 BuildSamplerKey(Uint externalIndex, Uint16 version) {
|
||||
return (static_cast<Uint64>(externalIndex) << 16) | static_cast<Uint64>(version);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool VkTextureSamplerManager::Initialize(const InitInfo& initInfo) {
|
||||
Shutdown();
|
||||
|
||||
m_device = initInfo.device;
|
||||
m_physicalDevice = initInfo.physicalDevice;
|
||||
m_commandPool = initInfo.commandPool;
|
||||
m_graphicsQueue = initInfo.graphicsQueue;
|
||||
|
||||
if (m_device == VK_NULL_HANDLE || m_physicalDevice == VK_NULL_HANDLE || m_commandPool == VK_NULL_HANDLE ||
|
||||
m_graphicsQueue == VK_NULL_HANDLE) {
|
||||
MGLOG_E("VkTextureSamplerManager::Initialize failed: invalid Vulkan handles");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!UploadFallbackTexture()) {
|
||||
MGLOG_E("VkTextureSamplerManager::Initialize failed: fallback texture creation failed");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkTextureSamplerManager::Shutdown() {
|
||||
for (auto& [_, resource] : m_textureResources) {
|
||||
DestroyTextureResource(resource);
|
||||
}
|
||||
m_textureResources.clear();
|
||||
|
||||
for (auto& [_, sampler] : m_samplers) {
|
||||
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(m_device, sampler.handle, nullptr);
|
||||
}
|
||||
sampler.handle = VK_NULL_HANDLE;
|
||||
}
|
||||
m_samplers.clear();
|
||||
|
||||
if (m_device != VK_NULL_HANDLE && m_fallbackSampler != VK_NULL_HANDLE) {
|
||||
vkDestroySampler(m_device, m_fallbackSampler, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && m_fallbackImageView != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(m_device, m_fallbackImageView, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && m_fallbackImage != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(m_device, m_fallbackImage, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && m_fallbackImageMemory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(m_device, m_fallbackImageMemory, nullptr);
|
||||
}
|
||||
m_fallbackSampler = VK_NULL_HANDLE;
|
||||
m_fallbackImageView = VK_NULL_HANDLE;
|
||||
m_fallbackImage = VK_NULL_HANDLE;
|
||||
m_fallbackImageMemory = VK_NULL_HANDLE;
|
||||
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_physicalDevice = VK_NULL_HANDLE;
|
||||
m_commandPool = VK_NULL_HANDLE;
|
||||
m_graphicsQueue = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::GetFallbackDescriptor(VkDescriptorImageInfo& outImageInfo) const {
|
||||
if (m_fallbackSampler == VK_NULL_HANDLE || m_fallbackImageView == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
outImageInfo.sampler = m_fallbackSampler;
|
||||
outImageInfo.imageView = m_fallbackImageView;
|
||||
outImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::SyncTextureAndGetDescriptor(const MG_State::GLState::ITextureObject& texture,
|
||||
const MG_State::GLState::SamplerObject* samplerOverride,
|
||||
VkDescriptorImageInfo& outImageInfo) {
|
||||
if (m_device == VK_NULL_HANDLE) {
|
||||
return GetFallbackDescriptor(outImageInfo);
|
||||
}
|
||||
|
||||
auto it = m_textureResources.find(texture.GetExternalIndex());
|
||||
if (it == m_textureResources.end()) {
|
||||
TextureResource initial{};
|
||||
initial.textureExternalIndex = texture.GetExternalIndex();
|
||||
auto [insertIt, _] = m_textureResources.emplace(texture.GetExternalIndex(), initial);
|
||||
it = insertIt;
|
||||
}
|
||||
|
||||
if (!EnsureTextureSynced(it->second, texture)) {
|
||||
return GetFallbackDescriptor(outImageInfo);
|
||||
}
|
||||
|
||||
const MG_State::GLState::SamplerObject* samplerToUse = samplerOverride;
|
||||
if (!samplerToUse) {
|
||||
auto textureSampler = texture.GetSamplerObject();
|
||||
if (textureSampler) {
|
||||
samplerToUse = textureSampler.get();
|
||||
}
|
||||
}
|
||||
|
||||
VkSampler sampler = m_fallbackSampler;
|
||||
if (samplerToUse) {
|
||||
sampler = GetOrCreateSampler(*samplerToUse);
|
||||
}
|
||||
if (sampler == VK_NULL_HANDLE) {
|
||||
sampler = m_fallbackSampler;
|
||||
}
|
||||
|
||||
if (it->second.view == VK_NULL_HANDLE || sampler == VK_NULL_HANDLE) {
|
||||
return GetFallbackDescriptor(outImageInfo);
|
||||
}
|
||||
|
||||
outImageInfo.sampler = sampler;
|
||||
outImageInfo.imageView = it->second.view;
|
||||
outImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::EnsureTextureSynced(TextureResource& resource,
|
||||
const MG_State::GLState::ITextureObject& texture) {
|
||||
TextureUploadTarget level0Target = TextureUploadTarget::Unknown;
|
||||
IntVec3 texelSize{0, 0, 0};
|
||||
SizeT byteSize = 0;
|
||||
if (!ResolveLevel0(texture, level0Target, texelSize, byteSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureTextureResource(resource, texture, level0Target, texelSize, byteSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture);
|
||||
if (!mipTexture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mipTexture->IsStorageDirty(level0Target, 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!UploadLevel0(resource, *mipTexture, level0Target, byteSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& mutableTexture = const_cast<MG_State::GLState::TextureObjectMipmap&>(*mipTexture);
|
||||
mutableTexture.MarkStorageDirty(level0Target, 0, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::EnsureTextureResource(TextureResource& resource,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
TextureUploadTarget level0Target, const IntVec3& texelSize,
|
||||
SizeT byteSize) {
|
||||
const VkFormat format = ResolveTextureFormat(texture.GetFormat());
|
||||
if (format == VK_FORMAT_UNDEFINED) {
|
||||
return false;
|
||||
}
|
||||
if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) {
|
||||
return false;
|
||||
}
|
||||
if (level0Target != TextureUploadTarget::Texture2D) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Bool compatible = resource.image != VK_NULL_HANDLE && resource.format == format &&
|
||||
resource.extent.width == static_cast<Uint32>(texelSize.x()) &&
|
||||
resource.extent.height == static_cast<Uint32>(texelSize.y());
|
||||
if (compatible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DestroyTextureResource(resource);
|
||||
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent.width = static_cast<Uint32>(texelSize.x());
|
||||
imageInfo.extent.height = static_cast<Uint32>(texelSize.y());
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.format = format;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &resource.image), "vkCreateImage(texture)");
|
||||
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetImageMemoryRequirements(m_device, resource.image, &requirements);
|
||||
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = requirements.size;
|
||||
allocInfo.memoryTypeIndex = FindMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &resource.memory), "vkAllocateMemory(texture)");
|
||||
VK_VERIFY(vkBindImageMemory(m_device, resource.image, resource.memory, 0), "vkBindImageMemory(texture)");
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = resource.image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = format;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.view), "vkCreateImageView(texture)");
|
||||
|
||||
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
resource.extent = {static_cast<Uint32>(texelSize.x()), static_cast<Uint32>(texelSize.y())};
|
||||
resource.format = format;
|
||||
resource.textureExternalIndex = texture.GetExternalIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::UploadLevel0(TextureResource& resource,
|
||||
const MG_State::GLState::TextureObjectMipmap& mipmapTexture,
|
||||
TextureUploadTarget level0Target, SizeT byteSize) {
|
||||
auto& mutableTexture = const_cast<MG_State::GLState::TextureObjectMipmap&>(mipmapTexture);
|
||||
const void* source = mutableTexture.MapMipmapData(level0Target, 0);
|
||||
if (source == nullptr || byteSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
|
||||
|
||||
VkBufferCreateInfo bufferInfo{};
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = byteSize;
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateBuffer(m_device, &bufferInfo, nullptr, &stagingBuffer), "vkCreateBuffer(staging texture)");
|
||||
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetBufferMemoryRequirements(m_device, stagingBuffer, &requirements);
|
||||
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = requirements.size;
|
||||
allocInfo.memoryTypeIndex =
|
||||
FindMemoryType(requirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &stagingMemory), "vkAllocateMemory(staging texture)");
|
||||
VK_VERIFY(vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0), "vkBindBufferMemory(staging texture)");
|
||||
|
||||
void* mapped = nullptr;
|
||||
VK_VERIFY(vkMapMemory(m_device, stagingMemory, 0, byteSize, 0, &mapped), "vkMapMemory(staging texture)");
|
||||
std::memcpy(mapped, source, byteSize);
|
||||
vkUnmapMemory(m_device, stagingMemory);
|
||||
|
||||
const Bool ok = ExecuteImmediate([&](VkCommandBuffer commandBuffer) {
|
||||
VkImageMemoryBarrier toTransferDst{};
|
||||
toTransferDst.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
toTransferDst.srcAccessMask = 0;
|
||||
toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toTransferDst.oldLayout = resource.layout;
|
||||
toTransferDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransferDst.image = resource.image;
|
||||
toTransferDst.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
toTransferDst.subresourceRange.baseMipLevel = 0;
|
||||
toTransferDst.subresourceRange.levelCount = 1;
|
||||
toTransferDst.subresourceRange.baseArrayLayer = 0;
|
||||
toTransferDst.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &toTransferDst);
|
||||
|
||||
VkBufferImageCopy copy{};
|
||||
copy.bufferOffset = 0;
|
||||
copy.bufferRowLength = 0;
|
||||
copy.bufferImageHeight = 0;
|
||||
copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copy.imageSubresource.mipLevel = 0;
|
||||
copy.imageSubresource.baseArrayLayer = 0;
|
||||
copy.imageSubresource.layerCount = 1;
|
||||
copy.imageOffset = {0, 0, 0};
|
||||
copy.imageExtent = {resource.extent.width, resource.extent.height, 1};
|
||||
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, resource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
||||
©);
|
||||
|
||||
VkImageMemoryBarrier toSampled{};
|
||||
toSampled.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
toSampled.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toSampled.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
toSampled.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toSampled.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
toSampled.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toSampled.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toSampled.image = resource.image;
|
||||
toSampled.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
toSampled.subresourceRange.baseMipLevel = 0;
|
||||
toSampled.subresourceRange.levelCount = 1;
|
||||
toSampled.subresourceRange.baseArrayLayer = 0;
|
||||
toSampled.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
|
||||
0, nullptr, 0, nullptr, 1, &toSampled);
|
||||
});
|
||||
|
||||
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
|
||||
vkFreeMemory(m_device, stagingMemory, nullptr);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
resource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::ExecuteImmediate(const std::function<void(VkCommandBuffer)>& recorder) const {
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = m_commandPool;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer), "vkAllocateCommandBuffers(texture)");
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer(texture)");
|
||||
|
||||
recorder(commandBuffer);
|
||||
|
||||
VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture)");
|
||||
|
||||
VkSubmitInfo submitInfo{};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE), "vkQueueSubmit(texture)");
|
||||
VK_VERIFY(vkQueueWaitIdle(m_graphicsQueue), "vkQueueWaitIdle(texture)");
|
||||
|
||||
vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkTextureSamplerManager::DestroyTextureResource(TextureResource& resource) const {
|
||||
if (m_device != VK_NULL_HANDLE && resource.view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(m_device, resource.view, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && resource.image != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(m_device, resource.image, nullptr);
|
||||
}
|
||||
if (m_device != VK_NULL_HANDLE && resource.memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(m_device, resource.memory, nullptr);
|
||||
}
|
||||
resource.view = VK_NULL_HANDLE;
|
||||
resource.image = VK_NULL_HANDLE;
|
||||
resource.memory = VK_NULL_HANDLE;
|
||||
resource.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
resource.extent = {0, 0};
|
||||
resource.format = VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::ResolveLevel0(const MG_State::GLState::ITextureObject& texture,
|
||||
TextureUploadTarget& outTarget, IntVec3& outTexelSize,
|
||||
SizeT& outByteSize) {
|
||||
const auto* mipTexture = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(&texture);
|
||||
if (!mipTexture) {
|
||||
return false;
|
||||
}
|
||||
const auto& targets = texture.GetUploadTargets();
|
||||
if (targets.empty()) {
|
||||
return false;
|
||||
}
|
||||
outTarget = targets.front();
|
||||
outTexelSize = mipTexture->GetMipmapTexelSize(outTarget, 0);
|
||||
outByteSize = mipTexture->GetMipmapByteSize(outTarget, 0);
|
||||
return outTexelSize.x() > 0 && outTexelSize.y() > 0 && outByteSize > 0;
|
||||
}
|
||||
|
||||
VkFormat VkTextureSamplerManager::ResolveTextureFormat(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::RGBA:
|
||||
case TextureInternalFormat::RGBA8:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
case TextureInternalFormat::SRGB8Alpha8:
|
||||
return VK_FORMAT_R8G8B8A8_SRGB;
|
||||
default:
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 VkTextureSamplerManager::FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const {
|
||||
VkPhysicalDeviceMemoryProperties memProperties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties);
|
||||
for (Uint32 i = 0; i < memProperties.memoryTypeCount; ++i) {
|
||||
if ((typeFilter & (1u << i)) != 0 &&
|
||||
(memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
MOBILEGL_ASSERT(false, "VkTextureSamplerManager::FindMemoryType failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
VkSampler VkTextureSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler) {
|
||||
const Uint64 key = BuildSamplerKey(sampler.GetExternalIndex(), sampler.GetVersion());
|
||||
auto it = m_samplers.find(key);
|
||||
if (it != m_samplers.end()) {
|
||||
return it->second.handle;
|
||||
}
|
||||
|
||||
VkSamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerInfo.magFilter = ToVkFilter(sampler.GetMagFilter());
|
||||
samplerInfo.minFilter = ToVkFilter(sampler.GetMinFilter());
|
||||
samplerInfo.mipmapMode = ToVkMipmapMode(sampler.GetMipmapMode());
|
||||
samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS());
|
||||
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
||||
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
||||
samplerInfo.mipLodBias = sampler.GetLodBias();
|
||||
samplerInfo.anisotropyEnable = VK_FALSE;
|
||||
samplerInfo.maxAnisotropy = 1.0f;
|
||||
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
||||
samplerInfo.compareOp = ToVkCompareOp(sampler.GetSamplerCompareFunc());
|
||||
samplerInfo.minLod = sampler.GetMinLod();
|
||||
samplerInfo.maxLod = sampler.GetMaxLod();
|
||||
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||
|
||||
VkSampler vkSampler = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &vkSampler), "vkCreateSampler(texture)");
|
||||
|
||||
SamplerCacheEntry entry{};
|
||||
entry.handle = vkSampler;
|
||||
entry.externalIndex = sampler.GetExternalIndex();
|
||||
entry.version = sampler.GetVersion();
|
||||
m_samplers[key] = entry;
|
||||
return vkSampler;
|
||||
}
|
||||
|
||||
VkFilter VkTextureSamplerManager::ToVkFilter(SamplerFilterMode mode) {
|
||||
return mode == SamplerFilterMode::Nearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||
}
|
||||
|
||||
VkSamplerMipmapMode VkTextureSamplerManager::ToVkMipmapMode(SamplerMipmapMode mode) {
|
||||
switch (mode) {
|
||||
case SamplerMipmapMode::Nearest:
|
||||
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
case SamplerMipmapMode::Linear:
|
||||
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
case SamplerMipmapMode::None:
|
||||
default:
|
||||
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
}
|
||||
}
|
||||
|
||||
VkSamplerAddressMode VkTextureSamplerManager::ToVkAddressMode(SamplerWrapMode mode) {
|
||||
switch (mode) {
|
||||
case SamplerWrapMode::ClampToEdge:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
case SamplerWrapMode::MirroredRepeat:
|
||||
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
||||
case SamplerWrapMode::Repeat:
|
||||
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
case SamplerWrapMode::ClampToBorder:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||
case SamplerWrapMode::MirrorClampToEdge:
|
||||
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
|
||||
default:
|
||||
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
}
|
||||
}
|
||||
|
||||
VkCompareOp VkTextureSamplerManager::ToVkCompareOp(SamplerCompareFunc func) {
|
||||
switch (func) {
|
||||
case SamplerCompareFunc::Never:
|
||||
return VK_COMPARE_OP_NEVER;
|
||||
case SamplerCompareFunc::Less:
|
||||
return VK_COMPARE_OP_LESS;
|
||||
case SamplerCompareFunc::Equal:
|
||||
return VK_COMPARE_OP_EQUAL;
|
||||
case SamplerCompareFunc::LessEqual:
|
||||
return VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
case SamplerCompareFunc::Greater:
|
||||
return VK_COMPARE_OP_GREATER;
|
||||
case SamplerCompareFunc::NotEqual:
|
||||
return VK_COMPARE_OP_NOT_EQUAL;
|
||||
case SamplerCompareFunc::GreaterEqual:
|
||||
return VK_COMPARE_OP_GREATER_OR_EQUAL;
|
||||
case SamplerCompareFunc::Always:
|
||||
default:
|
||||
return VK_COMPARE_OP_ALWAYS;
|
||||
}
|
||||
}
|
||||
|
||||
Bool VkTextureSamplerManager::UploadFallbackTexture() {
|
||||
const Uint32 rgba = 0xFFFFFFFFu;
|
||||
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent = {1, 1, 1};
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &m_fallbackImage), "vkCreateImage(fallback)");
|
||||
|
||||
VkMemoryRequirements imageMemReq{};
|
||||
vkGetImageMemoryRequirements(m_device, m_fallbackImage, &imageMemReq);
|
||||
|
||||
VkMemoryAllocateInfo imageAllocInfo{};
|
||||
imageAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
imageAllocInfo.allocationSize = imageMemReq.size;
|
||||
imageAllocInfo.memoryTypeIndex = FindMemoryType(imageMemReq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &imageAllocInfo, nullptr, &m_fallbackImageMemory),
|
||||
"vkAllocateMemory(fallback)");
|
||||
VK_VERIFY(vkBindImageMemory(m_device, m_fallbackImage, m_fallbackImageMemory, 0), "vkBindImageMemory(fallback)");
|
||||
|
||||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
|
||||
|
||||
VkBufferCreateInfo bufferInfo{};
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = sizeof(rgba);
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_VERIFY(vkCreateBuffer(m_device, &bufferInfo, nullptr, &stagingBuffer), "vkCreateBuffer(fallback)");
|
||||
|
||||
VkMemoryRequirements stagingMemReq{};
|
||||
vkGetBufferMemoryRequirements(m_device, stagingBuffer, &stagingMemReq);
|
||||
|
||||
VkMemoryAllocateInfo stagingAllocInfo{};
|
||||
stagingAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
stagingAllocInfo.allocationSize = stagingMemReq.size;
|
||||
stagingAllocInfo.memoryTypeIndex =
|
||||
FindMemoryType(stagingMemReq.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
VK_VERIFY(vkAllocateMemory(m_device, &stagingAllocInfo, nullptr, &stagingMemory), "vkAllocateMemory(fallback)");
|
||||
VK_VERIFY(vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0), "vkBindBufferMemory(fallback)");
|
||||
|
||||
void* mapped = nullptr;
|
||||
VK_VERIFY(vkMapMemory(m_device, stagingMemory, 0, sizeof(rgba), 0, &mapped), "vkMapMemory(fallback)");
|
||||
std::memcpy(mapped, &rgba, sizeof(rgba));
|
||||
vkUnmapMemory(m_device, stagingMemory);
|
||||
|
||||
const Bool uploadOk = ExecuteImmediate([&](VkCommandBuffer commandBuffer) {
|
||||
VkImageMemoryBarrier toTransferDst{};
|
||||
toTransferDst.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
toTransferDst.srcAccessMask = 0;
|
||||
toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toTransferDst.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
toTransferDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransferDst.image = m_fallbackImage;
|
||||
toTransferDst.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
toTransferDst.subresourceRange.baseMipLevel = 0;
|
||||
toTransferDst.subresourceRange.levelCount = 1;
|
||||
toTransferDst.subresourceRange.baseArrayLayer = 0;
|
||||
toTransferDst.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &toTransferDst);
|
||||
|
||||
VkBufferImageCopy copy{};
|
||||
copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
copy.imageSubresource.mipLevel = 0;
|
||||
copy.imageSubresource.baseArrayLayer = 0;
|
||||
copy.imageSubresource.layerCount = 1;
|
||||
copy.imageExtent = {1, 1, 1};
|
||||
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, m_fallbackImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
||||
©);
|
||||
|
||||
VkImageMemoryBarrier toSampled{};
|
||||
toSampled.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
toSampled.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toSampled.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
toSampled.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toSampled.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
toSampled.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toSampled.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toSampled.image = m_fallbackImage;
|
||||
toSampled.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
toSampled.subresourceRange.baseMipLevel = 0;
|
||||
toSampled.subresourceRange.levelCount = 1;
|
||||
toSampled.subresourceRange.baseArrayLayer = 0;
|
||||
toSampled.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
|
||||
0, nullptr, 0, nullptr, 1, &toSampled);
|
||||
});
|
||||
|
||||
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
|
||||
vkFreeMemory(m_device, stagingMemory, nullptr);
|
||||
if (!uploadOk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = m_fallbackImage;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &m_fallbackImageView), "vkCreateImageView(fallback)");
|
||||
|
||||
VkSamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerInfo.magFilter = VK_FILTER_NEAREST;
|
||||
samplerInfo.minFilter = VK_FILTER_NEAREST;
|
||||
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.compareEnable = VK_FALSE;
|
||||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = 0.0f;
|
||||
samplerInfo.maxAnisotropy = 1.0f;
|
||||
VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &m_fallbackSampler), "vkCreateSampler(fallback)");
|
||||
return true;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ITextureObject;
|
||||
class SamplerObject;
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class VkTextureSamplerManager {
|
||||
public:
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
Bool GetFallbackDescriptor(VkDescriptorImageInfo& outImageInfo) const;
|
||||
Bool SyncTextureAndGetDescriptor(const MG_State::GLState::ITextureObject& texture,
|
||||
const MG_State::GLState::SamplerObject* samplerOverride,
|
||||
VkDescriptorImageInfo& outImageInfo);
|
||||
|
||||
private:
|
||||
struct TextureResource {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkExtent2D extent = {0, 0};
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
Uint textureExternalIndex = 0;
|
||||
};
|
||||
|
||||
struct SamplerCacheEntry {
|
||||
VkSampler handle = VK_NULL_HANDLE;
|
||||
Uint externalIndex = 0;
|
||||
Uint16 version = 0;
|
||||
};
|
||||
|
||||
Bool EnsureTextureSynced(TextureResource& resource, const MG_State::GLState::ITextureObject& texture);
|
||||
Bool EnsureTextureResource(TextureResource& resource, const MG_State::GLState::ITextureObject& texture,
|
||||
TextureUploadTarget level0Target, const IntVec3& texelSize, SizeT byteSize);
|
||||
Bool UploadLevel0(TextureResource& resource, const MG_State::GLState::TextureObjectMipmap& mipmapTexture,
|
||||
TextureUploadTarget level0Target, SizeT byteSize);
|
||||
Bool ExecuteImmediate(const std::function<void(VkCommandBuffer)>& recorder) const;
|
||||
void DestroyTextureResource(TextureResource& resource) const;
|
||||
static Bool ResolveLevel0(const MG_State::GLState::ITextureObject& texture, TextureUploadTarget& outTarget,
|
||||
IntVec3& outTexelSize, SizeT& outByteSize);
|
||||
static VkFormat ResolveTextureFormat(TextureInternalFormat format);
|
||||
Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const;
|
||||
|
||||
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler);
|
||||
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
||||
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
||||
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
||||
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
|
||||
Bool UploadFallbackTexture();
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||
|
||||
UnorderedMap<Uint, TextureResource> m_textureResources;
|
||||
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
||||
|
||||
VkImage m_fallbackImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory m_fallbackImageMemory = VK_NULL_HANDLE;
|
||||
VkImageView m_fallbackImageView = VK_NULL_HANDLE;
|
||||
VkSampler m_fallbackSampler = VK_NULL_HANDLE;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,179 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "VkTimerQueryManager.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool VkTimerQueryManager::Initialize(const InitInfo& initInfo) {
|
||||
Shutdown();
|
||||
|
||||
MOBILEGL_ASSERT(initInfo.device != VK_NULL_HANDLE, "VkTimerQueryManager::Initialize requires valid VkDevice");
|
||||
MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkTimerQueryManager::Initialize requires non-zero frame count");
|
||||
if (initInfo.timestampValidBits == 0 || initInfo.timestampPeriodNs <= 0.0f || initInfo.slotsPerPool == 0) {
|
||||
MGLOG_W("VkTimerQueryManager: timestamps unsupported (validBits=%u, period=%f, slots=%u)",
|
||||
initInfo.timestampValidBits, initInfo.timestampPeriodNs, initInfo.slotsPerPool);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_device = initInfo.device;
|
||||
m_timestampPeriodNs = initInfo.timestampPeriodNs;
|
||||
m_validBitsMask = initInfo.timestampValidBits >= 64
|
||||
? ~0ull
|
||||
: ((1ull << initInfo.timestampValidBits) - 1ull);
|
||||
m_slotsPerPool = initInfo.slotsPerPool;
|
||||
m_pools.resize(initInfo.frameCount);
|
||||
|
||||
VkQueryPoolCreateInfo poolInfo{};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
|
||||
poolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
|
||||
poolInfo.queryCount = m_slotsPerPool;
|
||||
for (auto& poolState : m_pools) {
|
||||
const VkResult result = vkCreateQueryPool(m_device, &poolInfo, nullptr, &poolState.pool);
|
||||
if (result != VK_SUCCESS) {
|
||||
MGLOG_E("VkTimerQueryManager: vkCreateQueryPool failed with %s", VkResultToString(result));
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkTimerQueryManager::Shutdown() {
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
for (auto& poolState : m_pools) {
|
||||
if (poolState.pool != VK_NULL_HANDLE) {
|
||||
vkDestroyQueryPool(m_device, poolState.pool, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Records the frontend still holds simply stay unharvested; their
|
||||
// results read back as 0.
|
||||
m_pools.clear();
|
||||
m_device = VK_NULL_HANDLE;
|
||||
m_timestampPeriodNs = 0.0f;
|
||||
m_validBitsMask = 0;
|
||||
m_slotsPerPool = 0;
|
||||
}
|
||||
|
||||
void VkTimerQueryManager::OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer, Uint32 frameIndex,
|
||||
Uint64 frameSerial) {
|
||||
MOBILEGL_ASSERT(frameIndex < m_pools.size(), "VkTimerQueryManager frame index out of range");
|
||||
auto& poolState = m_pools[frameIndex];
|
||||
if (poolState.preparedFrameSerial == frameSerial) {
|
||||
// Recording re-began within the same frame (mid-frame readback
|
||||
// submit or the Present layout transition); the pool was already
|
||||
// harvested and reset for this cycle, and resetting again would
|
||||
// clobber timestamps written earlier in the frame.
|
||||
return;
|
||||
}
|
||||
|
||||
// Harvest what the pool's previous cycle left behind. The frame slot's
|
||||
// fence was waited before re-recording, so every executed query is
|
||||
// already available and the reads return immediately.
|
||||
DrainPoolPending(poolState);
|
||||
|
||||
vkCmdResetQueryPool(commandBuffer, poolState.pool, 0, m_slotsPerPool);
|
||||
poolState.cursor = 0;
|
||||
poolState.exhaustionWarned = false;
|
||||
poolState.preparedFrameSerial = frameSerial;
|
||||
}
|
||||
|
||||
SharedPtr<VkTimerQueryManager::TimestampRecord> VkTimerQueryManager::WriteTimestamp(VkCommandBuffer commandBuffer,
|
||||
Uint32 frameIndex,
|
||||
Uint64 frameSerial) {
|
||||
MOBILEGL_ASSERT(frameIndex < m_pools.size(), "VkTimerQueryManager frame index out of range");
|
||||
auto& poolState = m_pools[frameIndex];
|
||||
if (poolState.cursor >= m_slotsPerPool) {
|
||||
if (!poolState.exhaustionWarned) {
|
||||
MGLOG_W("VkTimerQueryManager: frame %u timestamp pool exhausted (%u slots); further timer queries "
|
||||
"this frame fall back to the frontend path",
|
||||
frameIndex, m_slotsPerPool);
|
||||
poolState.exhaustionWarned = true;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto record = MakeShared<TimestampRecord>();
|
||||
record->poolIndex = frameIndex;
|
||||
record->slot = poolState.cursor++;
|
||||
record->frameSerial = frameSerial;
|
||||
vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, poolState.pool, record->slot);
|
||||
poolState.pendingRecords.push_back(record);
|
||||
return record;
|
||||
}
|
||||
|
||||
Bool VkTimerQueryManager::TryHarvest(TimestampRecord& record) {
|
||||
if (record.harvested) {
|
||||
return true;
|
||||
}
|
||||
if (m_device == VK_NULL_HANDLE || record.poolIndex >= m_pools.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint64 resultWithAvailability[2] = {0, 0};
|
||||
const VkResult result = vkGetQueryPoolResults(
|
||||
m_device, m_pools[record.poolIndex].pool, record.slot, 1, sizeof(resultWithAvailability),
|
||||
resultWithAvailability, sizeof(Uint64), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
|
||||
if (result != VK_SUCCESS && result != VK_NOT_READY) {
|
||||
MGLOG_E("VkTimerQueryManager: vkGetQueryPoolResults failed with %s", VkResultToString(result));
|
||||
return false;
|
||||
}
|
||||
if (resultWithAvailability[1] == 0) {
|
||||
return false;
|
||||
}
|
||||
record.rawTicks = resultWithAvailability[0];
|
||||
record.harvested = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkTimerQueryManager::InvalidatePendingRecords() {
|
||||
for (auto& poolState : m_pools) {
|
||||
DrainPoolPending(poolState);
|
||||
// Force a harvest-free reset cycle the next time this pool's frame
|
||||
// begins recording.
|
||||
poolState.preparedFrameSerial = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void VkTimerQueryManager::DrainPoolPending(PoolState& poolState) {
|
||||
for (auto& record : poolState.pendingRecords) {
|
||||
if (record->harvested) {
|
||||
continue;
|
||||
}
|
||||
if (!TryHarvest(*record)) {
|
||||
// The commands carrying this timestamp never executed (they
|
||||
// were dropped, e.g. by a swapchain recreation mid-frame).
|
||||
// Mark the record resolved-as-invalid so waits on it cannot
|
||||
// hang; its result reads back as 0.
|
||||
record->harvested = true;
|
||||
record->valid = false;
|
||||
}
|
||||
}
|
||||
poolState.pendingRecords.clear();
|
||||
}
|
||||
|
||||
Uint64 VkTimerQueryManager::MaskToValidBits(Uint64 ticks) const {
|
||||
return ticks & m_validBitsMask;
|
||||
}
|
||||
|
||||
Uint64 VkTimerQueryManager::ElapsedNs(const TimestampRecord& begin, const TimestampRecord& end) const {
|
||||
if (!begin.valid || !end.valid) {
|
||||
return 0;
|
||||
}
|
||||
const Uint64 deltaTicks = MaskToValidBits(end.rawTicks - begin.rawTicks);
|
||||
return static_cast<Uint64>(static_cast<double>(deltaTicks) * static_cast<double>(m_timestampPeriodNs));
|
||||
}
|
||||
|
||||
Uint64 VkTimerQueryManager::TimestampNs(const TimestampRecord& record) const {
|
||||
if (!record.valid) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<Uint64>(static_cast<double>(MaskToValidBits(record.rawTicks)) *
|
||||
static_cast<double>(m_timestampPeriodNs));
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,111 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// GPU timestamp storage backing the GL timer-query frontend (GL_TIME_ELAPSED
|
||||
// spans and GL_TIMESTAMP one-shots): one VkQueryPool of timestamp slots per
|
||||
// frame in flight.
|
||||
//
|
||||
// Per-frame lifecycle: right after a frame slot's command buffer begins
|
||||
// recording (and before any render pass, since vkCmdResetQueryPool must be
|
||||
// recorded outside one), OnFrameCommandRecordingBegan harvests every
|
||||
// not-yet-read slot of the pool about to be reused (the slot's frame fence
|
||||
// was waited before re-recording, so the results are already available),
|
||||
// records a reset of the whole pool, and rewinds the allocation cursor.
|
||||
class VkTimerQueryManager {
|
||||
public:
|
||||
// One vkCmdWriteTimestamp landing spot. Shared (via SharedPtr) between
|
||||
// the frontend-held query object and the owning pool's pending list, so
|
||||
// deleting a query while its result is still in flight never leaves the
|
||||
// pool with a dangling record.
|
||||
struct TimestampRecord {
|
||||
Uint32 poolIndex = 0;
|
||||
Uint32 slot = 0;
|
||||
// VkBufferManager frame serial current when the timestamp was
|
||||
// recorded; result availability is bounded by its completion.
|
||||
Uint64 frameSerial = 0;
|
||||
Bool harvested = false;
|
||||
// Cleared when the recorded commands were dropped before they could
|
||||
// execute (swapchain recreation abandons the in-progress command
|
||||
// buffer); the result then reads back as 0.
|
||||
Bool valid = true;
|
||||
Uint64 rawTicks = 0;
|
||||
};
|
||||
|
||||
struct InitInfo {
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
Uint32 frameCount = 0;
|
||||
Uint32 timestampValidBits = 0;
|
||||
Float timestampPeriodNs = 0.0f; // nanoseconds per timestamp tick
|
||||
Uint32 slotsPerPool = 128;
|
||||
};
|
||||
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
// The caller guarantees the device is idle (same contract as the other
|
||||
// DirectVulkan managers' Shutdown paths).
|
||||
void Shutdown();
|
||||
|
||||
// The per-frame hook described in the class comment. Re-begins within
|
||||
// the same frame serial (mid-frame readback submits, the Present layout
|
||||
// transition) are skipped so already-written slots survive.
|
||||
void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer, Uint32 frameIndex, Uint64 frameSerial);
|
||||
|
||||
// Allocates a slot from the frame's pool and records a bottom-of-pipe
|
||||
// vkCmdWriteTimestamp (valid both inside and outside a render pass).
|
||||
// Returns null on pool exhaustion, with one warning per pool cycle; the
|
||||
// frontend falls back gracefully on a null handle.
|
||||
SharedPtr<TimestampRecord> WriteTimestamp(VkCommandBuffer commandBuffer, Uint32 frameIndex,
|
||||
Uint64 frameSerial);
|
||||
|
||||
// Non-blocking single-slot read (WITH_AVAILABILITY, no WAIT). Returns
|
||||
// true once the record holds its raw ticks. Callers gate this on the
|
||||
// record's frame serial being complete.
|
||||
Bool TryHarvest(TimestampRecord& record);
|
||||
|
||||
// Reads every pending result that is available (the caller guarantees
|
||||
// the device is idle) and marks the rest invalid. Called when recorded
|
||||
// but unsubmitted commands are dropped (swapchain recreation), which
|
||||
// would otherwise leave slots that never become available. Each pool is
|
||||
// reset lazily on its next OnFrameCommandRecordingBegan.
|
||||
void InvalidatePendingRecords();
|
||||
|
||||
// end - begin using unsigned wrap arithmetic masked to the queue's
|
||||
// timestampValidBits, converted to nanoseconds. 0 if either record was
|
||||
// invalidated.
|
||||
Uint64 ElapsedNs(const TimestampRecord& begin, const TimestampRecord& end) const;
|
||||
// Raw GPU timestamp converted to nanoseconds. 0 if invalidated.
|
||||
Uint64 TimestampNs(const TimestampRecord& record) const;
|
||||
|
||||
private:
|
||||
struct PoolState {
|
||||
VkQueryPool pool = VK_NULL_HANDLE;
|
||||
Uint32 cursor = 0;
|
||||
// Frame serial the pool was last harvested + reset for; guards
|
||||
// against double resets when recording re-begins mid-frame.
|
||||
Uint64 preparedFrameSerial = 0;
|
||||
Bool exhaustionWarned = false;
|
||||
Vector<SharedPtr<TimestampRecord>> pendingRecords;
|
||||
};
|
||||
|
||||
Uint64 MaskToValidBits(Uint64 ticks) const;
|
||||
// Harvest (or invalidate, when the result never became available)
|
||||
// every pending record of a pool and clear its pending list.
|
||||
void DrainPoolPending(PoolState& pool);
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
Float m_timestampPeriodNs = 0.0f;
|
||||
Uint64 m_validBitsMask = 0;
|
||||
Uint32 m_slotsPerPool = 0;
|
||||
Vector<PoolState> m_pools;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,12 +12,15 @@
|
||||
#include "PipelineFactory.h"
|
||||
#include "ProgramFactory.h"
|
||||
#include "SwapchainObject.h"
|
||||
#include "UniformDescriptorBinder.h"
|
||||
#include "UniformManager.h"
|
||||
#include "VertexInputStateFactory.h"
|
||||
#include "VkBufferObject.h"
|
||||
#include "VkFramebufferManager.h"
|
||||
#include "VkBufferManager.h"
|
||||
#include "VkClearManager.h"
|
||||
#include "VkRenderPassManager.h"
|
||||
#include "VkTextureSamplerManager.h"
|
||||
#include "VkSamplerManager.h"
|
||||
#include "VkTextureManager.h"
|
||||
#include "VkTimerQueryManager.h"
|
||||
#include "MG_Util/Math/VectorTypes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
@@ -27,23 +30,67 @@
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class FramebufferObject;
|
||||
class ProgramObject;
|
||||
class SamplerObject;
|
||||
class VertexArrayObject;
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct DrawArrayPayload {
|
||||
GLenum mode = GL_TRIANGLES;
|
||||
GLint first = 0;
|
||||
GLsizei count = 0;
|
||||
const MG_State::GLState::ProgramObject* program = nullptr;
|
||||
const MG_State::GLState::VertexArrayObject* vertexArray = nullptr;
|
||||
enum class DrawSetupAspect: Uint8 {
|
||||
FramebufferObject = 1 << 0,
|
||||
VertexArrayObject = 1 << 1,
|
||||
UniformBuffer = 1 << 2,
|
||||
VertexBuffer = 1 << 3,
|
||||
IndexBuffer = 1 << 4,
|
||||
IndirectDrawBuffer = 1 << 5,
|
||||
Viewport = 1 << 6,
|
||||
Scissor = 1 << 7,
|
||||
};
|
||||
|
||||
struct DrawElementPayload {
|
||||
DrawArrayPayload drawArray;
|
||||
struct DrawCmdParam {
|
||||
Uint32 vertexCount = 0;
|
||||
Uint32 instanceCount = 1;
|
||||
Uint32 firstVertex = 0;
|
||||
Uint32 firstInstance = 0;
|
||||
};
|
||||
|
||||
struct DrawIndexedCmdParam {
|
||||
Uint32 indexCount = 0;
|
||||
Uint32 instanceCount = 1;
|
||||
Uint32 firstIndex = 0;
|
||||
Int32 vertexOffset = 0;
|
||||
Int32 firstInstance = 0;
|
||||
};
|
||||
|
||||
struct DrawCmd {
|
||||
GLenum mode = GL_TRIANGLES;
|
||||
DrawCmdParam params;
|
||||
};
|
||||
|
||||
struct IndexBufferView {
|
||||
GLenum indexType = GL_UNSIGNED_SHORT;
|
||||
SizeT indexByteOffset = 0;
|
||||
GLint baseVertex = 0;
|
||||
SizeT indexByteSize = 0;
|
||||
};
|
||||
|
||||
struct DrawIndexedCmd {
|
||||
GLenum mode = GL_TRIANGLES;
|
||||
IndexBufferView indexBufferView;
|
||||
|
||||
DrawIndexedCmdParam params;
|
||||
};
|
||||
|
||||
struct MultiDrawIndexedCmd {
|
||||
GLenum mode = GL_TRIANGLES;
|
||||
IndexBufferView indexBufferView;
|
||||
|
||||
Uint32 drawCount = 0;
|
||||
DrawIndexedCmdParam* pParams = nullptr;
|
||||
};
|
||||
|
||||
struct MultiDrawCmd {
|
||||
GLenum mode = GL_TRIANGLES;
|
||||
Uint32 drawCount = 0;
|
||||
DrawCmdParam* pParams = nullptr;
|
||||
};
|
||||
|
||||
struct QueueFamilyIndices {
|
||||
@@ -61,7 +108,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
};
|
||||
|
||||
class VulkanRenderer {
|
||||
class VulkanRenderer : public IBufferCopyCommandProvider, public FrameContext::IRecordingObserver {
|
||||
public:
|
||||
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
||||
~VulkanRenderer();
|
||||
@@ -69,36 +116,239 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil,
|
||||
Uint drawFboExternalIndex, Bool isDefaultFramebufferTarget);
|
||||
Bool ConsumePendingColorClear(VkClearColorValue& outClearColor);
|
||||
void EnsureFrameRecordingStarted();
|
||||
void DrawArrays(const DrawArrayPayload& payload);
|
||||
void DrawElements(const DrawElementPayload& payload);
|
||||
void MultiDrawElements(const Vector<DrawElementPayload>& payloads);
|
||||
Bool BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter, Uint readFboExternalIndex,
|
||||
Uint drawFboExternalIndex, Bool readIsDefaultFramebuffer, Bool drawIsDefaultFramebuffer);
|
||||
void Render();
|
||||
// IBufferCopyCommandProvider: recording command buffer, outside any
|
||||
// render pass, for immediate staged buffer copies.
|
||||
VkCommandBuffer AcquireBufferCopyCommandBuffer() override;
|
||||
|
||||
// FrameContext::IRecordingObserver: prepares the frame's timer-query
|
||||
// pool (harvest + reset) right after the frame command buffer begins
|
||||
// recording, before any render pass.
|
||||
void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) override;
|
||||
|
||||
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||
const DrawCmdParam& drawParams,
|
||||
const IndexBufferView* pIndexBufferView = nullptr);
|
||||
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
|
||||
const RenderPassEntry& compatibleRenderPassEntry);
|
||||
|
||||
enum class ScissoredClearPrep {
|
||||
NotNeeded, // scissor covers the whole target — take the deferred whole-surface path instead
|
||||
NoOp, // nothing to clear (degenerate target or empty scissor rect)
|
||||
Ready, // a render pass is active; record vkCmdClearAttachments with the returned rect
|
||||
};
|
||||
ScissoredClearPrep PrepareScissoredClear(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
VkClearRect& outClearRect);
|
||||
|
||||
void Clear(GLbitfield mask);
|
||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFbo,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFbo,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||
void GenerateMipmap(GLenum target);
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
|
||||
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
||||
GLsizei width, GLsizei height, GLenum destinationFormat,
|
||||
GLenum destinationType, SizeT destinationRowStride,
|
||||
Uint8* destinationPixels);
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
||||
GLsizei bufSize, GLvoid* pixels);
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void DispatchComputeIndirect(GLintptr indirect);
|
||||
void MemoryBarrier(GLbitfield barriers);
|
||||
static VkMemoryBarrier BuildMemoryBarrierForGlBarriers(GLbitfield barriers);
|
||||
void DrawArrays(const DrawCmd& payload);
|
||||
void DrawElements(const DrawIndexedCmd& payload);
|
||||
void MultiDrawArrays(const MultiDrawCmd& payload);
|
||||
void MultiDrawElements(const MultiDrawIndexedCmd& payloads);
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
||||
GLsizei stride);
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void Present();
|
||||
|
||||
const PhysicalDevice& GetPhysicalDevice() const;
|
||||
VkInstance GetInstance() const;
|
||||
Bool IsDrawIndirectCountExtensionEnabled() const;
|
||||
|
||||
// GL fence support, expressed in queue-submission indices backed by
|
||||
// real VkFences. A GL fence captures GetSyncPointSubmitIndex() at
|
||||
// creation: the index of the submission that will carry the commands
|
||||
// recorded so far (m_submitCounter + 1 while work is pending, or
|
||||
// m_submitCounter when nothing has been recorded since the last
|
||||
// submit). It is signaled once that submission's fence is observed
|
||||
// signaled - unlike the frame-serial heuristic, this makes fences
|
||||
// signal as soon as the GPU actually finishes, which MC 1.21.5's
|
||||
// fence-paced ring buffers rely on to recycle their space.
|
||||
Uint64 GetSyncPointSubmitIndex() const;
|
||||
// Non-blocking: polls outstanding submission fences and reports
|
||||
// whether every submission up to `submitIndex` has completed.
|
||||
Bool IsSubmitIndexComplete(Uint64 submitIndex);
|
||||
// Submits the commands recorded so far without waiting (GL flush).
|
||||
// Recording restarts lazily on a fresh command buffer; the submitted
|
||||
// one is retired until the frame slot's fence is next waited. Returns
|
||||
// true when a submission was made.
|
||||
Bool FlushPendingCommands();
|
||||
// Flush gated on usefulness: only flushes when `submitIndex` is still
|
||||
// unsubmitted, so poll loops on already-submitted fences do not split
|
||||
// the frame's render pass (a full tile load/store on TBDR GPUs).
|
||||
Bool FlushForSyncPoint(Uint64 submitIndex);
|
||||
// Blocking wait for a submission index with a nanosecond timeout.
|
||||
// When the index is still unsubmitted and flushIfPending is set, the
|
||||
// pending commands are flushed first so the wait can make progress.
|
||||
Bool WaitForSubmitIndex(Uint64 submitIndex, Uint64 timeoutNs, Bool flushIfPending);
|
||||
|
||||
// Frame-serial completion, still used by the timer-query paths (their
|
||||
// records are bucketed per frame slot).
|
||||
Bool IsFrameSerialComplete(Uint64 serial) const;
|
||||
// Blocking wait for a submitted serial. Returns false when the serial
|
||||
// cannot complete without further submissions (it belongs to the
|
||||
// current, not-yet-presented frame) or when the wait failed.
|
||||
Bool WaitForFrameSerial(Uint64 serial, Uint64 timeoutNs);
|
||||
|
||||
// GPU timer queries, backing the GL_TIME_ELAPSED / GL_TIMESTAMP
|
||||
// frontend. Timestamp support (queue timestampValidBits > 0 and a
|
||||
// non-zero timestampPeriod) is cached at device creation.
|
||||
Bool IsTimerQuerySupported() const;
|
||||
// The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is
|
||||
// honored rather than accepted-and-ignored.
|
||||
Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; }
|
||||
// Ensures the frame command buffer is recording (same lazy pattern as
|
||||
// SetupDraw) and writes a bottom-of-pipe timestamp into the current
|
||||
// frame's pool. Null when unsupported or the pool is exhausted.
|
||||
SharedPtr<VkTimerQueryManager::TimestampRecord> WriteTimerQueryTimestamp();
|
||||
// Non-blocking: true once the record's raw ticks are on the CPU
|
||||
// (harvests the slot once its frame serial has completed).
|
||||
Bool IsTimerQueryResultReady(VkTimerQueryManager::TimestampRecord& record);
|
||||
// Blocking wait, mirroring ClientWaitSync's caveat: a record written
|
||||
// this frame cannot complete until Present submits the commands, so
|
||||
// this returns false (result reads as 0) instead of deadlocking.
|
||||
Bool WaitForTimerQueryResult(VkTimerQueryManager::TimestampRecord& record);
|
||||
Uint64 GetTimerQueryElapsedNs(const VkTimerQueryManager::TimestampRecord& begin,
|
||||
const VkTimerQueryManager::TimestampRecord& end) const;
|
||||
Uint64 GetTimerQueryTimestampNs(const VkTimerQueryManager::TimestampRecord& record) const;
|
||||
|
||||
void RequestSwapchainResize(Uint32 width, Uint32 height);
|
||||
void RecreateSwapchain();
|
||||
|
||||
private:
|
||||
struct PendingClearState {
|
||||
GLbitfield mask = 0;
|
||||
VkClearColorValue color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
||||
Float depth = 1.0f;
|
||||
Uint32 stencil = 0;
|
||||
Uint drawFboExternalIndex = 0;
|
||||
Bool targetsDefaultFramebuffer = true;
|
||||
struct BlitUniformData {
|
||||
float srcRect[4] = {0.f, 0.f, 1.f, 1.f};
|
||||
float dstRect[4] = {0.f, 0.f, 1.f, 1.f};
|
||||
Int surfaceTransform = 0;
|
||||
Int padding[3] = {0, 0, 0};
|
||||
};
|
||||
|
||||
struct BlitResources {
|
||||
SharedPtr<MG_State::GLState::ProgramObject> program;
|
||||
SharedPtr<MG_State::GLState::SamplerObject> nearestSampler;
|
||||
SharedPtr<MG_State::GLState::SamplerObject> linearSampler;
|
||||
Int srcRectLocation = -1;
|
||||
Int dstRectLocation = -1;
|
||||
Int surfaceTransformLocation = -1;
|
||||
Uint32 samplerBinding = 0;
|
||||
};
|
||||
|
||||
struct DepthMipmapResources {
|
||||
SharedPtr<MG_State::GLState::ProgramObject> program;
|
||||
Int srcRectLocation = -1;
|
||||
Int dstRectLocation = -1;
|
||||
Int surfaceTransformLocation = -1;
|
||||
Int srcTexelSizeLocation = -1;
|
||||
Uint32 samplerBinding = 0;
|
||||
};
|
||||
|
||||
struct DeferredDepthMipmapCleanup {
|
||||
Vector<VkImageView> imageViews;
|
||||
Vector<VkFramebuffer> framebuffers;
|
||||
Vector<VkRenderPass> renderPasses;
|
||||
Vector<VkPipeline> pipelines;
|
||||
};
|
||||
|
||||
void QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload);
|
||||
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload);
|
||||
void RecordScissoredClearBuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload,
|
||||
const VkClearRect& clearRect);
|
||||
|
||||
// ---- Submission fence tracking (GL sync objects) ----
|
||||
// One record per vkQueueSubmit still in flight, in ascending submit
|
||||
// order. Present/readback submissions reference the frame slot's
|
||||
// fence (not pool-owned); mid-frame flushes use pooled fences that are
|
||||
// recycled once their submission is observed complete.
|
||||
// Not thread-safe: like the rest of the renderer, the tracker relies
|
||||
// on GL calls being serialized (launchers migrate the context across
|
||||
// threads, but calls never run concurrently), so sync-object polls
|
||||
// may mutate it without locking.
|
||||
struct SubmitRecord {
|
||||
Uint64 submitIndex = 0;
|
||||
// Buffer-manager frame serial the submission was made under; its
|
||||
// completion raises the completed-serial floor (timer queries and
|
||||
// buffer busy-tracking live in frame-serial space).
|
||||
Uint64 frameSerial = 0;
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
Bool pooledFence = false;
|
||||
};
|
||||
// Registers a submission that vkQueueSubmit just made with `fence`.
|
||||
// Invariant: every graphics-queue submission that outlives its call
|
||||
// site must be registered so GL fences observe it. Exempt are the
|
||||
// texture-upload/preserve submits in VkTextureManager, which
|
||||
// vkWaitForFences inline before returning.
|
||||
void RegisterSubmit(VkFence fence, Bool pooledFence);
|
||||
// Builds the submit packet for the frame's pending command buffer
|
||||
// (consuming the acquire semaphore on the slot's first submission),
|
||||
// submits it with `fence`, and registers the submission. On failure
|
||||
// the frame state is left untouched. Shared by the mid-frame flush
|
||||
// and the readback path so the semaphore-consumption invariant lives
|
||||
// in one place.
|
||||
Bool SubmitPendingCommandBuffer(FrameContext::FrameData& frame, VkFence fence, Bool pooledFence);
|
||||
// Polls in-flight submission fences (prefix order) and advances the
|
||||
// completed counter past every fence observed signaled.
|
||||
void RefreshCompletedSubmits();
|
||||
// All submissions up to `submitIndex` are known complete (their fence
|
||||
// was waited or the device was idled); drops their records and
|
||||
// recycles pooled fences.
|
||||
void OnSubmitsCompletedUpTo(Uint64 submitIndex);
|
||||
VkFence AcquirePooledSubmitFence();
|
||||
void DestroySubmitFencePool();
|
||||
Bool HasPendingRecordedWork() const;
|
||||
|
||||
Vector<SubmitRecord> m_inFlightSubmits;
|
||||
Vector<VkFence> m_freeSubmitFences;
|
||||
Uint64 m_submitCounter = 0;
|
||||
Uint64 m_completedSubmitCounter = 0;
|
||||
|
||||
NativeWindowType m_window = 0;
|
||||
void* m_platformDisplay = nullptr;
|
||||
void* m_platformLibrary = nullptr;
|
||||
void* m_platformCloseDisplay = nullptr;
|
||||
VulkanRendererConfig m_config;
|
||||
Bool m_swapchainResizeRequested = false;
|
||||
|
||||
// Vulkan objects
|
||||
Bool m_validationLayersEnabled = false;
|
||||
@@ -106,56 +356,140 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkInstance m_instance = VK_NULL_HANDLE;
|
||||
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
||||
PhysicalDevice m_physicalDevice;
|
||||
// VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
VmaAllocator m_allocator = nullptr;
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
SwapchainObject m_swapchainObject;
|
||||
|
||||
// Vector<VkQueueFamilyProperties> m_queueFamilies;
|
||||
// QueueFamilyIndices m_queueFamilyIndices;
|
||||
|
||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||
VkQueue m_presentQueue = VK_NULL_HANDLE;
|
||||
Bool m_drawIndirectCountExtensionEnabled = false;
|
||||
Bool m_indexTypeUint8ExtensionEnabled = false;
|
||||
Bool m_logicOpFeatureEnabled = false;
|
||||
Bool m_multiDrawIndirectFeatureEnabled = false;
|
||||
Bool m_samplerAnisotropyFeatureEnabled = false;
|
||||
Bool m_shaderDrawParametersExtensionEnabled = false;
|
||||
Bool m_shaderDrawParametersFeatureEnabled = false;
|
||||
Bool m_unformattedFloatStorageImagesEnabled = false;
|
||||
// fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates
|
||||
// per-draw-buffer color write masks (glColorMaski). Both are cached at device creation and
|
||||
// drive a runtime fallback when the device lacks them.
|
||||
Bool m_fillModeNonSolidFeatureEnabled = false;
|
||||
Bool m_independentBlendFeatureEnabled = false;
|
||||
// dualSrcBlend gates GL_SRC1_* blend factors (glBindFragDataLocationIndexed dual-source blend);
|
||||
// primitiveTopologyListRestart gates primitive restart on *list* topologies (strip/fan restart
|
||||
// needs no feature). Both cached at device creation and drive a hard-fail-at-draw when absent.
|
||||
Bool m_dualSrcBlendFeatureEnabled = false;
|
||||
Bool m_primitiveTopologyListRestartFeatureEnabled = false;
|
||||
// Cached at device creation from the graphics queue family properties
|
||||
// and device limits; drives timer-query support.
|
||||
Uint32 m_timestampValidBits = 0;
|
||||
Float m_timestampPeriodNs = 0.0f;
|
||||
Bool m_timerQuerySupported = false;
|
||||
using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer,
|
||||
VkDeviceSize offset, VkBuffer countBuffer,
|
||||
VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
|
||||
Uint32 stride);
|
||||
PFNDrawIndexedIndirectCountFunc m_cmdDrawIndexedIndirectCount = nullptr;
|
||||
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
||||
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
|
||||
VkFormat m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
Vector<VkImage> m_depthStencilImages;
|
||||
Vector<VkDeviceMemory> m_depthStencilImageMemories;
|
||||
Vector<VkImageView> m_depthStencilImageViews;
|
||||
Vector<VkImageLayout> m_depthStencilImageLayouts;
|
||||
|
||||
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
|
||||
Vector<VkBufferObject> m_frameVertexUploadBuffers;
|
||||
Vector<VkDeviceSize> m_frameVertexUploadHeads;
|
||||
Vector<VkBufferObject> m_frameIndexUploadBuffers;
|
||||
Vector<VkDeviceSize> m_frameIndexUploadHeads;
|
||||
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||
VkBufferManager m_bufferManager;
|
||||
|
||||
Uint m_imageIndexAcquired = 0;
|
||||
FrameContext m_frameContext;
|
||||
UnorderedMap<Uint64, PendingClearState> m_pendingClears;
|
||||
Bool m_isMainRenderPassActive = false;
|
||||
VkRenderPass m_activeRenderPass = VK_NULL_HANDLE;
|
||||
VkExtent2D m_activeRenderExtent = {0, 0};
|
||||
VkFormat m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED;
|
||||
Bool m_activeRenderTargetIsDefault = true;
|
||||
Uint m_activeDrawFboExternalIndex = 0;
|
||||
|
||||
UniquePtr<PipelineFactory> m_pipelineFactory;
|
||||
// Single-slot "last pipeline" memo: skip the per-draw GetOrCreatePipeline work (state
|
||||
// gather + synthetic vertex-input rebuild + payload hash + lookup) when the full pipeline
|
||||
// state is unchanged from the previous draw. The key provably covers every pipeline field.
|
||||
// Reset per-frame and on pipeline destruction so the cached handle can never dangle.
|
||||
Bool m_lastPipelineValid = false;
|
||||
GLenum m_lastPipelineMode = 0;
|
||||
Uint64 m_lastPipelineProgramHash = 0;
|
||||
Uint64 m_lastPipelineVertexInputHash = 0;
|
||||
Uint64 m_lastPipelineRenderPassHash = 0;
|
||||
Uint m_lastPipelineRenderStateVersion = 0;
|
||||
ProgramFactory::CompileOptionFlags m_lastPipelineTransformFlags = {};
|
||||
VkPipeline m_lastPipelineResult = VK_NULL_HANDLE;
|
||||
UnorderedMap<ProgramFactory::HashType, VkPipeline> m_computePipelines;
|
||||
UniquePtr<ProgramFactory> m_programFactory;
|
||||
UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder;
|
||||
UniquePtr<UniformManager> m_uniformManager;
|
||||
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
|
||||
UniquePtr<VkFramebufferManager> m_framebufferManager;
|
||||
UniquePtr<VkClearManager> m_clearManager;
|
||||
UniquePtr<VkRenderPassManager> m_renderPassManager;
|
||||
UniquePtr<VkTextureSamplerManager> m_textureSamplerManager;
|
||||
UniquePtr<VkTextureManager> m_textureManager;
|
||||
UniquePtr<VkSamplerManager> m_samplerManager;
|
||||
UniquePtr<VkTimerQueryManager> m_timerQueryManager;
|
||||
BlitResources m_blitResources;
|
||||
DepthMipmapResources m_depthMipmapResources;
|
||||
Vector<DeferredDepthMipmapCleanup> m_deferredDepthMipmapCleanup;
|
||||
|
||||
// Skip the per-draw CollectSampledTextures walk (~5% of the render thread) when the sampled
|
||||
// texture SET is provably unchanged from the previous draw: same program (lifetime id +
|
||||
// backend-state version, which covers sampler-uniform reassignment / relink) and transform
|
||||
// flags, and no texture bind/unbind/delete since (GetTextureBindGeneration). On a hit,
|
||||
// m_sampledTexturesScratch still holds the previous draw's list and steps 2-4 (feedback /
|
||||
// layout probe / transition) re-run on it, so layout correctness is unaffected - only the GL
|
||||
// walk is skipped. The program lifetime id (never reused, unlike the GL name) and the
|
||||
// monotonic bind generation make the key ABA-proof; the per-command-buffer reset is a cheap
|
||||
// belt-and-suspenders.
|
||||
Bool m_lastSampledSetValid = false;
|
||||
Uint64 m_lastSampledSetProgramLifetimeId = 0;
|
||||
Uint32 m_lastSampledSetProgramVersion = 0;
|
||||
ProgramFactory::CompileOptionFlags m_lastSampledSetTransformFlags = {};
|
||||
Uint64 m_lastSampledSetBindGeneration = 0;
|
||||
|
||||
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
||||
// draw call and must not allocate.
|
||||
Vector<MG_State::GLState::ITextureObject*> m_sampledTexturesScratch;
|
||||
Vector<MG_State::GLState::ITextureObject*> m_storageImageTexturesScratch;
|
||||
Vector<VkBuffer> m_vertexBuffersScratch;
|
||||
Vector<VkDeviceSize> m_vertexOffsetsScratch;
|
||||
Vector<VkVertexInputAttributeDescription> m_patchedAttributesScratch;
|
||||
Vector<Float> m_vertexConversionScratch;
|
||||
Vector<Uint8> m_vertexRepackScratch;
|
||||
|
||||
struct ConvertedVertexStreamKey {
|
||||
const MG_State::GLState::BufferObject* buffer = nullptr;
|
||||
Uint64 changeSerial = 0;
|
||||
SizeT baseOffset = 0;
|
||||
Uint32 sourceStride = 0;
|
||||
DataType type = DataType::Float32;
|
||||
Int size = 0;
|
||||
Bool normalized = false;
|
||||
Bool isInteger = false;
|
||||
VertexInputStateFactory::VertexStreamConversion conversion =
|
||||
VertexInputStateFactory::VertexStreamConversion::None;
|
||||
|
||||
Bool operator==(const ConvertedVertexStreamKey& other) const {
|
||||
return buffer == other.buffer && changeSerial == other.changeSerial &&
|
||||
baseOffset == other.baseOffset && sourceStride == other.sourceStride &&
|
||||
type == other.type && size == other.size && normalized == other.normalized &&
|
||||
isInteger == other.isInteger && conversion == other.conversion;
|
||||
}
|
||||
};
|
||||
|
||||
struct ConvertedVertexStreamKeyHash {
|
||||
SizeT operator()(const ConvertedVertexStreamKey& key) const {
|
||||
SizeT hash = std::hash<const void*>{}(key.buffer);
|
||||
auto combine = [&hash](SizeT value) {
|
||||
hash ^= value + static_cast<SizeT>(0x9e3779b97f4a7c15ull) + (hash << 6) + (hash >> 2);
|
||||
};
|
||||
combine(std::hash<Uint64>{}(key.changeSerial));
|
||||
combine(std::hash<SizeT>{}(key.baseOffset));
|
||||
combine(std::hash<Uint32>{}(key.sourceStride));
|
||||
combine(std::hash<Uint32>{}(static_cast<Uint32>(key.type)));
|
||||
combine(std::hash<Int>{}(key.size));
|
||||
combine(std::hash<Bool>{}(key.normalized));
|
||||
combine(std::hash<Bool>{}(key.isInteger));
|
||||
combine(std::hash<Uint32>{}(static_cast<Uint32>(key.conversion)));
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
UnorderedMap<ConvertedVertexStreamKey, BufferSlice, ConvertedVertexStreamKeyHash>
|
||||
m_convertedVertexStreams;
|
||||
|
||||
void CreateInstance();
|
||||
VkResult SetupDebugMessenger();
|
||||
@@ -168,31 +502,55 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void DestroyAllocator();
|
||||
void CreateSwapchain();
|
||||
void CreateCommandPool();
|
||||
void CreateFrameContexts();
|
||||
void CreateDepthStencilResources();
|
||||
void DestroyDepthStencilResources();
|
||||
VkRenderPass GetDefaultLoadRenderPass() const;
|
||||
Bool GetDefaultRenderTargetForCurrentImage(VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const;
|
||||
Bool EnsureOffscreenRenderTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo,
|
||||
VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat);
|
||||
void PrepareDemoPipeline();
|
||||
VkPipeline GetOrCreatePipeline(const MG_State::GLState::ProgramObject& program, VkPipelineLayout pipelineLayout,
|
||||
Uint64 vertexInputHash,
|
||||
const VkPipelineVertexInputStateCreateInfo& vertexInputState);
|
||||
void TransitionSwapchainImageToColorAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex);
|
||||
void TransitionDepthStencilImageToAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex);
|
||||
void EndFrameRecordingIfNeeded();
|
||||
void DeferDestroyBuffer(VkBufferObject& buffer);
|
||||
void CollectDeferredBufferReleases(Uint32 frameIndex);
|
||||
Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset,
|
||||
VkDeviceSize minCapacity, VkBufferUsageFlags usage);
|
||||
Bool UploadAndBindVertexStreams(const VertexInputStateFactory::BackendVertexInputState& vertexInputState,
|
||||
const DrawArrayPayload& payload, VkCommandBuffer commandBuffer);
|
||||
|
||||
VkPipeline GetOrCreatePipeline(
|
||||
GLenum mode,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
ProgramFactory::CompileOptionFlags transformFlags,
|
||||
const MG_State::GLState::VertexArrayObject& vao,
|
||||
const RenderPassEntry& renderPassEntry);
|
||||
VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj);
|
||||
void DestroyComputePipelines();
|
||||
Bool PrepareStorageImageTextures(
|
||||
VkCommandBuffer commandBuffer,
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj);
|
||||
|
||||
Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
const DrawCmdParam& drawParams, Bool indexedDraw);
|
||||
Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame,
|
||||
const MG_State::GLState::VertexArrayObject& vao,
|
||||
const IndexBufferView* pIndexBufferView = nullptr);
|
||||
Bool InitializeBlitResources();
|
||||
Bool InitializeDepthMipmapResources();
|
||||
void ShutdownBlitResources();
|
||||
void ShutdownDepthMipmapResources();
|
||||
void CollectDeferredDepthMipmapCleanup(Uint32 frameIndex);
|
||||
void DestroyDeferredDepthMipmapCleanup();
|
||||
Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame,
|
||||
MG_State::GLState::FramebufferObject& readFbo,
|
||||
MG_State::GLState::FramebufferObject& drawFbo,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLenum filter);
|
||||
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture);
|
||||
VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry);
|
||||
Bool GenerateDepthMipmapWithShader(FrameContext::FrameData& frame,
|
||||
MG_State::GLState::ITextureObject& texture,
|
||||
VkTextureManager::TextureResource& resource,
|
||||
Uint32 baseMipLevel,
|
||||
Uint32 generateMipLevelCount,
|
||||
const IntVec3& storageBaseTexelSize,
|
||||
VkImageLayout originalLayout,
|
||||
VkImageLayout finalLayout);
|
||||
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
||||
|
||||
void ShutdownSwapchain();
|
||||
|
||||
// Static functions
|
||||
static Int GetPresentQueueFamilyIndex(const PhysicalDevice& physicalDevice, VkSurfaceKHR surface,
|
||||
const Vector<VkQueueFamilyProperties>& queueFamilies,
|
||||
Int preferredFamilyIndex = -1);
|
||||
@@ -212,11 +570,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface,
|
||||
const PhysicalDevice& compareWithDevice,
|
||||
PhysicalDevice& outBetterDevice);
|
||||
Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const;
|
||||
static Uint64 BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer);
|
||||
static Bool HasStencilComponent(VkFormat format);
|
||||
static VkFormat FindSupportedDepthStencilFormat(VkPhysicalDevice physicalDevice);
|
||||
static constexpr VkDynamicState s_dynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
static constexpr const char* s_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"};
|
||||
static constexpr const char* s_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||
static Bool CheckValidationLayerSupport();
|
||||
|
||||
@@ -10,16 +10,61 @@
|
||||
|
||||
#include "VulkanRendererConfig.h"
|
||||
|
||||
#define ENUM_STR_CASE(c) case c: return #c;
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
inline const char* VkResultToString(VkResult result) {
|
||||
switch (result) {
|
||||
ENUM_STR_CASE(VK_SUCCESS)
|
||||
ENUM_STR_CASE(VK_NOT_READY)
|
||||
ENUM_STR_CASE(VK_TIMEOUT)
|
||||
ENUM_STR_CASE(VK_EVENT_SET)
|
||||
ENUM_STR_CASE(VK_EVENT_RESET)
|
||||
ENUM_STR_CASE(VK_INCOMPLETE)
|
||||
ENUM_STR_CASE(VK_ERROR_OUT_OF_HOST_MEMORY)
|
||||
ENUM_STR_CASE(VK_ERROR_OUT_OF_DEVICE_MEMORY)
|
||||
ENUM_STR_CASE(VK_ERROR_INITIALIZATION_FAILED)
|
||||
ENUM_STR_CASE(VK_ERROR_DEVICE_LOST)
|
||||
ENUM_STR_CASE(VK_ERROR_MEMORY_MAP_FAILED)
|
||||
ENUM_STR_CASE(VK_ERROR_LAYER_NOT_PRESENT)
|
||||
ENUM_STR_CASE(VK_ERROR_EXTENSION_NOT_PRESENT)
|
||||
ENUM_STR_CASE(VK_ERROR_FEATURE_NOT_PRESENT)
|
||||
ENUM_STR_CASE(VK_ERROR_INCOMPATIBLE_DRIVER)
|
||||
ENUM_STR_CASE(VK_ERROR_TOO_MANY_OBJECTS)
|
||||
ENUM_STR_CASE(VK_ERROR_FORMAT_NOT_SUPPORTED)
|
||||
ENUM_STR_CASE(VK_ERROR_FRAGMENTED_POOL)
|
||||
ENUM_STR_CASE(VK_ERROR_UNKNOWN)
|
||||
ENUM_STR_CASE(VK_ERROR_OUT_OF_POOL_MEMORY)
|
||||
ENUM_STR_CASE(VK_ERROR_INVALID_EXTERNAL_HANDLE)
|
||||
ENUM_STR_CASE(VK_ERROR_FRAGMENTATION)
|
||||
ENUM_STR_CASE(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS)
|
||||
ENUM_STR_CASE(VK_PIPELINE_COMPILE_REQUIRED)
|
||||
ENUM_STR_CASE(VK_ERROR_SURFACE_LOST_KHR)
|
||||
ENUM_STR_CASE(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR)
|
||||
ENUM_STR_CASE(VK_SUBOPTIMAL_KHR)
|
||||
ENUM_STR_CASE(VK_ERROR_OUT_OF_DATE_KHR)
|
||||
ENUM_STR_CASE(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR)
|
||||
ENUM_STR_CASE(VK_ERROR_VALIDATION_FAILED_EXT)
|
||||
ENUM_STR_CASE(VK_ERROR_INVALID_SHADER_NV)
|
||||
default:
|
||||
return "VK_RESULT_UNKNOWN";
|
||||
}
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
#define VK_VERIFY(expr, ...) \
|
||||
do { \
|
||||
VkResult _vk_verify_result = (expr); \
|
||||
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _vk_verify_result, __FILE__, __LINE__); \
|
||||
if (_vk_verify_result != VK_SUCCESS) { \
|
||||
MGLOG_F("Vulkan error %s (%d) at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, \
|
||||
MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), \
|
||||
_vk_verify_result, __FILE__, __LINE__); \
|
||||
} \
|
||||
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %s (%d) at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), _vk_verify_result, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
|
||||
#define ENUM_STR_CASE(c) case c: return #c;
|
||||
|
||||
#define XXHASH_VERIFY(expr, ...) \
|
||||
do { \
|
||||
XXH_errorcode _xxh_verify_result = (expr); \
|
||||
MOBILEGL_ASSERT(_xxh_verify_result == XXH_OK, "XXHash error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _xxh_verify_result, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
} while (0)
|
||||
|
||||
@@ -11,10 +11,22 @@
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct VulkanRendererConfig {
|
||||
Uint32 MaxFramesInFlight = 2;
|
||||
// Fallback CPU pipeline depth used when the MOBILEGL_MAGMA_FRAMESINFLIGHT env var is
|
||||
// unset/invalid. A deeper pipeline lets the CPU run further ahead of the GPU, hiding
|
||||
// per-frame GPU-completion latency. Whatever value is chosen (env or this fallback) is
|
||||
// only a request: VulkanRenderer::Initialize clamps it down to the surface's maxImageCount
|
||||
// (and never below 2), since not every driver allows that many swapchain images.
|
||||
Uint32 MaxFramesInFlight = 3;
|
||||
String AppName = "MobileGL-VulkanRenderer";
|
||||
Version Version = MG_Config::CoreVersion;
|
||||
MobileGL::Version Version = MG_Config::CoreVersion;
|
||||
Uint64 CacheVersion = MG_Config::CacheVersion;
|
||||
Uint32 SurfaceWidth = 1;
|
||||
Uint32 SurfaceHeight = 1;
|
||||
Bool DisablePipelineCache = false;
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||
Bool EnableValidationLayers = true;
|
||||
#else
|
||||
Bool EnableValidationLayers = false;
|
||||
#endif
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -16,4 +16,5 @@ target_link_libraries(
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
add_test(NAME BufferBench COMMAND BufferBench --benchmark_counters_tabular=true)
|
||||
add_test(NAME BufferBench COMMAND BufferBench --benchmark_counters_tabular=true)
|
||||
set_tests_properties(BufferBench PROPERTIES LABELS benchmark)
|
||||
@@ -38,6 +38,7 @@ target_link_libraries(
|
||||
)
|
||||
|
||||
add_test(NAME SanityBench COMMAND SanityBench --benchmark_counters_tabular=true)
|
||||
set_tests_properties(SanityBench PROPERTIES LABELS benchmark)
|
||||
|
||||
add_subdirectory(Program)
|
||||
add_subdirectory(Buffer)
|
||||
@@ -16,4 +16,5 @@ target_link_libraries(
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
add_test(NAME ProgramBench COMMAND ProgramBench --benchmark_counters_tabular=true)
|
||||
add_test(NAME ProgramBench COMMAND ProgramBench --benchmark_counters_tabular=true)
|
||||
set_tests_properties(ProgramBench PROPERTIES LABELS benchmark)
|
||||
@@ -0,0 +1,700 @@
|
||||
// MobileGL - MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "CGLImpl.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include "../EGLImpl/EGLImpl.h"
|
||||
|
||||
namespace MobileGL::MG_Impl::CGLImpl {
|
||||
namespace {
|
||||
struct PixelFormatObject {
|
||||
Uint32 RetainCount = 1;
|
||||
Bool DoubleBuffer = true;
|
||||
GLint ColorSize = 24;
|
||||
GLint AlphaSize = 8;
|
||||
GLint DepthSize = 24;
|
||||
GLint StencilSize = 8;
|
||||
GLint SampleBuffers = 0;
|
||||
GLint Samples = 0;
|
||||
GLint Profile = kCGLOGLPVersion_3_2_Core;
|
||||
GLint RendererId = 0x4d474c;
|
||||
};
|
||||
|
||||
struct ContextObject {
|
||||
Uint32 RetainCount = 1;
|
||||
CGLPixelFormatObj PixelFormat = nullptr;
|
||||
CGLContextObj Share = nullptr;
|
||||
EGLDisplay Display = EGL_NO_DISPLAY;
|
||||
EGLConfig Config = nullptr;
|
||||
EGLContext Context = EGL_NO_CONTEXT;
|
||||
EGLSurface Surface = EGL_NO_SURFACE;
|
||||
void* NSObject = nullptr;
|
||||
void* View = nullptr;
|
||||
void* MetalLayer = nullptr;
|
||||
GLint SwapInterval = 1;
|
||||
GLint VirtualScreen = 0;
|
||||
GLint SurfaceBackingSize[2] = {0, 0};
|
||||
Bool HasDrawable = false;
|
||||
Bool Locked = false;
|
||||
};
|
||||
|
||||
std::recursive_mutex& RegistryMutex() {
|
||||
static auto* mutex = new std::recursive_mutex();
|
||||
return *mutex;
|
||||
}
|
||||
|
||||
Uint64& NextPixelFormatHandle() {
|
||||
static auto* handle = new Uint64(1);
|
||||
return *handle;
|
||||
}
|
||||
|
||||
Uint64& NextContextHandle() {
|
||||
static auto* handle = new Uint64(1);
|
||||
return *handle;
|
||||
}
|
||||
|
||||
UnorderedMap<CGLPixelFormatObj, PixelFormatObject>& PixelFormats() {
|
||||
static auto* formats = new UnorderedMap<CGLPixelFormatObj, PixelFormatObject>();
|
||||
return *formats;
|
||||
}
|
||||
|
||||
UnorderedMap<CGLContextObj, ContextObject>& Contexts() {
|
||||
static auto* contexts = new UnorderedMap<CGLContextObj, ContextObject>();
|
||||
return *contexts;
|
||||
}
|
||||
|
||||
UnorderedMap<std::thread::id, CGLContextObj>& CurrentContexts() {
|
||||
static auto* contexts = new UnorderedMap<std::thread::id, CGLContextObj>();
|
||||
return *contexts;
|
||||
}
|
||||
|
||||
CGLPixelFormatObj EncodePixelFormat(Uint64 handle) {
|
||||
return reinterpret_cast<CGLPixelFormatObj>(static_cast<SizeT>(handle));
|
||||
}
|
||||
|
||||
CGLContextObj EncodeContext(Uint64 handle) {
|
||||
return reinterpret_cast<CGLContextObj>(static_cast<SizeT>(handle));
|
||||
}
|
||||
|
||||
std::thread::id CurrentThreadKey() {
|
||||
return std::this_thread::get_id();
|
||||
}
|
||||
|
||||
Bool AttributeHasValue(CGLPixelFormatAttribute attrib) {
|
||||
switch (attrib) {
|
||||
case kCGLPFAColorSize:
|
||||
case kCGLPFAAlphaSize:
|
||||
case kCGLPFADepthSize:
|
||||
case kCGLPFAStencilSize:
|
||||
case kCGLPFASampleBuffers:
|
||||
case kCGLPFASamples:
|
||||
case kCGLPFARendererID:
|
||||
case kCGLPFADisplayMask:
|
||||
case kCGLPFAOpenGLProfile:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyPixelFormatAttribute(PixelFormatObject& pixelFormat,
|
||||
CGLPixelFormatAttribute attrib,
|
||||
GLint value) {
|
||||
switch (attrib) {
|
||||
case kCGLPFADoubleBuffer:
|
||||
pixelFormat.DoubleBuffer = true;
|
||||
break;
|
||||
case kCGLPFAColorSize:
|
||||
pixelFormat.ColorSize = value;
|
||||
break;
|
||||
case kCGLPFAAlphaSize:
|
||||
pixelFormat.AlphaSize = value;
|
||||
break;
|
||||
case kCGLPFADepthSize:
|
||||
pixelFormat.DepthSize = value;
|
||||
break;
|
||||
case kCGLPFAStencilSize:
|
||||
pixelFormat.StencilSize = value;
|
||||
break;
|
||||
case kCGLPFASampleBuffers:
|
||||
pixelFormat.SampleBuffers = value;
|
||||
break;
|
||||
case kCGLPFASamples:
|
||||
pixelFormat.Samples = value;
|
||||
break;
|
||||
case kCGLPFAOpenGLProfile:
|
||||
pixelFormat.Profile = value;
|
||||
break;
|
||||
case kCGLPFARendererID:
|
||||
pixelFormat.RendererId = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Bool InitEGLContext(ContextObject& object, CGLPixelFormatObj pix, CGLContextObj share) {
|
||||
auto* pixelFormat = [&]() -> PixelFormatObject* {
|
||||
auto& pixelFormats = PixelFormats();
|
||||
auto it = pixelFormats.find(pix);
|
||||
return it == pixelFormats.end() ? nullptr : &it->second;
|
||||
}();
|
||||
if (!pixelFormat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLDisplay display = EGLImpl::GetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (display == EGL_NO_DISPLAY) {
|
||||
return false;
|
||||
}
|
||||
if (!EGLImpl::Initialize(display, nullptr, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
EGLImpl::BindAPI(EGL_OPENGL_API);
|
||||
|
||||
const EGLint attribs[] = {
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, std::max(pixelFormat->AlphaSize, 0),
|
||||
EGL_DEPTH_SIZE, std::max(pixelFormat->DepthSize, 0),
|
||||
EGL_STENCIL_SIZE, std::max(pixelFormat->StencilSize, 0),
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_NONE,
|
||||
};
|
||||
|
||||
EGLConfig config = nullptr;
|
||||
EGLint count = 0;
|
||||
if (!EGLImpl::ChooseConfig(display, attribs, &config, 1, &count) || count <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLContext shareContext = EGL_NO_CONTEXT;
|
||||
if (share != nullptr) {
|
||||
auto& contexts = Contexts();
|
||||
auto shareIt = contexts.find(share);
|
||||
if (shareIt == contexts.end()) {
|
||||
return false;
|
||||
}
|
||||
shareContext = shareIt->second.Context;
|
||||
}
|
||||
|
||||
const EGLint contextAttribs[] = {
|
||||
EGL_CONTEXT_MAJOR_VERSION, 3,
|
||||
EGL_CONTEXT_MINOR_VERSION, 3,
|
||||
EGL_NONE,
|
||||
};
|
||||
EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs);
|
||||
if (eglContext == EGL_NO_CONTEXT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
object.Display = display;
|
||||
object.Config = config;
|
||||
object.Context = eglContext;
|
||||
object.PixelFormat = pix;
|
||||
object.Share = share;
|
||||
return true;
|
||||
}
|
||||
|
||||
ContextObject* TryGetContext(CGLContextObj ctx) {
|
||||
auto& contexts = Contexts();
|
||||
auto it = contexts.find(ctx);
|
||||
return it == contexts.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
const ContextObject* TryGetContext(CGLContextObj ctx, const std::lock_guard<std::recursive_mutex>&) {
|
||||
auto& contexts = Contexts();
|
||||
auto it = contexts.find(ctx);
|
||||
return it == contexts.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
PixelFormatObject* TryGetPixelFormat(CGLPixelFormatObj pix) {
|
||||
auto& pixelFormats = PixelFormats();
|
||||
auto it = pixelFormats.find(pix);
|
||||
return it == pixelFormats.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
CGLError MakeCurrentLocked(CGLContextObj ctx, ContextObject& object) {
|
||||
CurrentContexts()[CurrentThreadKey()] = ctx;
|
||||
if (!object.HasDrawable || object.Surface == EGL_NO_SURFACE) {
|
||||
return kCGLNoError;
|
||||
}
|
||||
if (!EGLImpl::MakeCurrent(object.Display, object.Surface, object.Surface, object.Context)) {
|
||||
return kCGLBadState;
|
||||
}
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
CGLError RecreateSurfaceLocked(CGLContextObj ctx, ContextObject& object) {
|
||||
if (!object.MetalLayer) {
|
||||
return kCGLBadDrawable;
|
||||
}
|
||||
if (object.Surface != EGL_NO_SURFACE) {
|
||||
EGLImpl::DestroySurface(object.Display, object.Surface);
|
||||
object.Surface = EGL_NO_SURFACE;
|
||||
}
|
||||
const EGLAttrib attribs[] = {
|
||||
EGL_WIDTH, std::max<GLint>(object.SurfaceBackingSize[0], 1),
|
||||
EGL_HEIGHT, std::max<GLint>(object.SurfaceBackingSize[1], 1),
|
||||
EGL_NONE,
|
||||
};
|
||||
EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(object.Display, object.Config,
|
||||
object.MetalLayer, attribs);
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
object.HasDrawable = false;
|
||||
return kCGLBadDrawable;
|
||||
}
|
||||
object.Surface = surface;
|
||||
object.HasDrawable = true;
|
||||
return GetCurrentContext() == ctx ? MakeCurrentLocked(ctx, object) : kCGLNoError;
|
||||
}
|
||||
|
||||
CGLError ResizeSurfaceLocked(ContextObject& object) {
|
||||
if (object.Surface == EGL_NO_SURFACE) {
|
||||
return kCGLBadDrawable;
|
||||
}
|
||||
return EGLImpl::ResizePlatformWindowSurface(
|
||||
object.Display, object.Surface,
|
||||
std::max<GLint>(object.SurfaceBackingSize[0], 1),
|
||||
std::max<GLint>(object.SurfaceBackingSize[1], 1))
|
||||
? kCGLNoError
|
||||
: kCGLBadDrawable;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CGLError ChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
if (!pix || !npix) {
|
||||
return kCGLBadAddress;
|
||||
}
|
||||
|
||||
PixelFormatObject object;
|
||||
if (attribs) {
|
||||
for (SizeT i = 0; attribs[i] != static_cast<CGLPixelFormatAttribute>(0); ++i) {
|
||||
const auto attrib = attribs[i];
|
||||
GLint value = 1;
|
||||
if (AttributeHasValue(attrib)) {
|
||||
value = static_cast<GLint>(attribs[++i]);
|
||||
}
|
||||
ApplyPixelFormatAttribute(object, attrib, value);
|
||||
}
|
||||
}
|
||||
|
||||
const auto handle = EncodePixelFormat(NextPixelFormatHandle()++);
|
||||
PixelFormats()[handle] = object;
|
||||
*pix = handle;
|
||||
*npix = 1;
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
CGLError DestroyPixelFormat(CGLPixelFormatObj pix) {
|
||||
ReleasePixelFormat(pix);
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
CGLError DescribePixelFormat(CGLPixelFormatObj pix, GLint pixNum, CGLPixelFormatAttribute attrib, GLint* value) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
if (!value) {
|
||||
return kCGLBadAddress;
|
||||
}
|
||||
if (pixNum != 0 && pixNum != 1) {
|
||||
return kCGLBadValue;
|
||||
}
|
||||
auto* pixelFormat = TryGetPixelFormat(pix);
|
||||
if (!pixelFormat) {
|
||||
return kCGLBadPixelFormat;
|
||||
}
|
||||
|
||||
switch (attrib) {
|
||||
case kCGLPFADoubleBuffer:
|
||||
*value = pixelFormat->DoubleBuffer ? 1 : 0;
|
||||
return kCGLNoError;
|
||||
case kCGLPFAAccelerated:
|
||||
case kCGLPFAAcceleratedCompute:
|
||||
case kCGLPFASupportsAutomaticGraphicsSwitching:
|
||||
*value = 1;
|
||||
return kCGLNoError;
|
||||
case kCGLPFAColorSize:
|
||||
*value = pixelFormat->ColorSize;
|
||||
return kCGLNoError;
|
||||
case kCGLPFAAlphaSize:
|
||||
*value = pixelFormat->AlphaSize;
|
||||
return kCGLNoError;
|
||||
case kCGLPFADepthSize:
|
||||
*value = pixelFormat->DepthSize;
|
||||
return kCGLNoError;
|
||||
case kCGLPFAStencilSize:
|
||||
*value = pixelFormat->StencilSize;
|
||||
return kCGLNoError;
|
||||
case kCGLPFASampleBuffers:
|
||||
*value = pixelFormat->SampleBuffers;
|
||||
return kCGLNoError;
|
||||
case kCGLPFASamples:
|
||||
*value = pixelFormat->Samples;
|
||||
return kCGLNoError;
|
||||
case kCGLPFARendererID:
|
||||
*value = pixelFormat->RendererId;
|
||||
return kCGLNoError;
|
||||
case kCGLPFAOpenGLProfile:
|
||||
*value = pixelFormat->Profile;
|
||||
return kCGLNoError;
|
||||
case kCGLPFAVirtualScreenCount:
|
||||
*value = 1;
|
||||
return kCGLNoError;
|
||||
default:
|
||||
*value = 0;
|
||||
return kCGLNoError;
|
||||
}
|
||||
}
|
||||
|
||||
void ReleasePixelFormat(CGLPixelFormatObj pix) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* pixelFormat = TryGetPixelFormat(pix);
|
||||
if (!pixelFormat) {
|
||||
return;
|
||||
}
|
||||
if (pixelFormat->RetainCount > 1) {
|
||||
--pixelFormat->RetainCount;
|
||||
return;
|
||||
}
|
||||
PixelFormats().erase(pix);
|
||||
}
|
||||
|
||||
CGLPixelFormatObj RetainPixelFormat(CGLPixelFormatObj pix) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* pixelFormat = TryGetPixelFormat(pix);
|
||||
if (pixelFormat) {
|
||||
++pixelFormat->RetainCount;
|
||||
}
|
||||
return pix;
|
||||
}
|
||||
|
||||
GLuint GetPixelFormatRetainCount(CGLPixelFormatObj pix) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* pixelFormat = TryGetPixelFormat(pix);
|
||||
return pixelFormat ? pixelFormat->RetainCount : 0;
|
||||
}
|
||||
|
||||
CGLError CreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
if (!ctx) {
|
||||
return kCGLBadAddress;
|
||||
}
|
||||
if (!TryGetPixelFormat(pix)) {
|
||||
return kCGLBadPixelFormat;
|
||||
}
|
||||
if (share && !TryGetContext(share)) {
|
||||
return kCGLBadMatch;
|
||||
}
|
||||
|
||||
ContextObject object;
|
||||
if (!InitEGLContext(object, pix, share)) {
|
||||
return kCGLBadAlloc;
|
||||
}
|
||||
RetainPixelFormat(pix);
|
||||
const auto handle = EncodeContext(NextContextHandle()++);
|
||||
Contexts()[handle] = object;
|
||||
*ctx = handle;
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
CGLError DestroyContext(CGLContextObj ctx) {
|
||||
ReleaseContext(ctx);
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
CGLContextObj RetainContext(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (object) {
|
||||
++object->RetainCount;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void ReleaseContext(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
if (object->RetainCount > 1) {
|
||||
--object->RetainCount;
|
||||
return;
|
||||
}
|
||||
if (object->Surface != EGL_NO_SURFACE) {
|
||||
EGLImpl::DestroySurface(object->Display, object->Surface);
|
||||
}
|
||||
if (object->Context != EGL_NO_CONTEXT) {
|
||||
EGLImpl::DestroyContext(object->Display, object->Context);
|
||||
}
|
||||
ReleasePixelFormat(object->PixelFormat);
|
||||
auto& currentContexts = CurrentContexts();
|
||||
for (auto it = currentContexts.begin(); it != currentContexts.end();) {
|
||||
if (it->second == ctx) {
|
||||
it = currentContexts.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
Contexts().erase(ctx);
|
||||
}
|
||||
|
||||
GLuint GetContextRetainCount(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
return object ? object->RetainCount : 0;
|
||||
}
|
||||
|
||||
CGLPixelFormatObj GetPixelFormat(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
return object ? object->PixelFormat : nullptr;
|
||||
}
|
||||
|
||||
CGLError SetCurrentContext(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
if (!ctx) {
|
||||
CurrentContexts().erase(CurrentThreadKey());
|
||||
EGLImpl::MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
return kCGLNoError;
|
||||
}
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return kCGLBadContext;
|
||||
}
|
||||
return MakeCurrentLocked(ctx, *object);
|
||||
}
|
||||
|
||||
CGLContextObj GetCurrentContext() {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto& currentContexts = CurrentContexts();
|
||||
auto it = currentContexts.find(CurrentThreadKey());
|
||||
return it == currentContexts.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return kCGLBadContext;
|
||||
}
|
||||
if (!params && pname != kCGLCPReclaimResources) {
|
||||
return kCGLBadAddress;
|
||||
}
|
||||
switch (pname) {
|
||||
case kCGLCPSwapInterval:
|
||||
object->SwapInterval = params[0];
|
||||
EGLImpl::SwapInterval(object->Display, object->SwapInterval);
|
||||
return kCGLNoError;
|
||||
case kCGLCPSurfaceBackingSize:
|
||||
{
|
||||
const GLint width = std::max<GLint>(params[0], 1);
|
||||
const GLint height = std::max<GLint>(params[1], 1);
|
||||
if (object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height) {
|
||||
return kCGLNoError;
|
||||
}
|
||||
object->SurfaceBackingSize[0] = width;
|
||||
object->SurfaceBackingSize[1] = height;
|
||||
if (object->MetalLayer && object->Surface != EGL_NO_SURFACE) {
|
||||
return ResizeSurfaceLocked(*object);
|
||||
}
|
||||
return kCGLNoError;
|
||||
}
|
||||
case kCGLCPSurfaceOpacity:
|
||||
case kCGLCPSurfaceOrder:
|
||||
case kCGLCPMPSwapsInFlight:
|
||||
case kCGLCPReclaimResources:
|
||||
return kCGLNoError;
|
||||
default:
|
||||
return kCGLNoError;
|
||||
}
|
||||
}
|
||||
|
||||
CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return kCGLBadContext;
|
||||
}
|
||||
if (!params) {
|
||||
return kCGLBadAddress;
|
||||
}
|
||||
switch (pname) {
|
||||
case kCGLCPSwapInterval:
|
||||
params[0] = object->SwapInterval;
|
||||
return kCGLNoError;
|
||||
case kCGLCPSurfaceBackingSize:
|
||||
params[0] = object->SurfaceBackingSize[0];
|
||||
params[1] = object->SurfaceBackingSize[1];
|
||||
return kCGLNoError;
|
||||
case kCGLCPCurrentRendererID:
|
||||
params[0] = 0x4d474c;
|
||||
return kCGLNoError;
|
||||
case kCGLCPGPUVertexProcessing:
|
||||
case kCGLCPGPUFragmentProcessing:
|
||||
case kCGLCPHasDrawable:
|
||||
params[0] = object->HasDrawable ? 1 : 0;
|
||||
return kCGLNoError;
|
||||
case kCGLCPMPSwapsInFlight:
|
||||
params[0] = 1;
|
||||
return kCGLNoError;
|
||||
default:
|
||||
params[0] = 0;
|
||||
return kCGLNoError;
|
||||
}
|
||||
}
|
||||
|
||||
CGLError UpdateContext(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
return TryGetContext(ctx) ? kCGLNoError : kCGLBadContext;
|
||||
}
|
||||
|
||||
CGLError ClearDrawable(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return kCGLBadContext;
|
||||
}
|
||||
if (object->Surface != EGL_NO_SURFACE) {
|
||||
EGLImpl::DestroySurface(object->Display, object->Surface);
|
||||
}
|
||||
object->Surface = EGL_NO_SURFACE;
|
||||
object->View = nullptr;
|
||||
object->MetalLayer = nullptr;
|
||||
object->HasDrawable = false;
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
CGLError FlushDrawable(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return kCGLBadContext;
|
||||
}
|
||||
if (!object->HasDrawable || object->Surface == EGL_NO_SURFACE) {
|
||||
return kCGLBadDrawable;
|
||||
}
|
||||
const auto currentError = MakeCurrentLocked(ctx, *object);
|
||||
if (currentError != kCGLNoError) {
|
||||
return currentError;
|
||||
}
|
||||
return EGLImpl::SwapBuffers(object->Display, object->Surface) ? kCGLNoError : kCGLBadDrawable;
|
||||
}
|
||||
|
||||
CGLError LockContext(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return kCGLBadContext;
|
||||
}
|
||||
object->Locked = true;
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
CGLError UnlockContext(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return kCGLBadContext;
|
||||
}
|
||||
object->Locked = false;
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
void GetVersion(GLint* majorvers, GLint* minorvers) {
|
||||
if (majorvers) {
|
||||
*majorvers = 1;
|
||||
}
|
||||
if (minorvers) {
|
||||
*minorvers = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char* ErrorString(CGLError error) {
|
||||
switch (error) {
|
||||
case kCGLNoError:
|
||||
return "no error";
|
||||
case kCGLBadAttribute:
|
||||
return "invalid pixel format attribute";
|
||||
case kCGLBadPixelFormat:
|
||||
return "invalid pixel format";
|
||||
case kCGLBadContext:
|
||||
return "invalid context";
|
||||
case kCGLBadDrawable:
|
||||
return "invalid drawable";
|
||||
case kCGLBadState:
|
||||
return "invalid context state";
|
||||
case kCGLBadValue:
|
||||
return "invalid numerical value";
|
||||
case kCGLBadMatch:
|
||||
return "invalid share context";
|
||||
case kCGLBadAddress:
|
||||
return "invalid pointer";
|
||||
case kCGLBadAlloc:
|
||||
return "invalid memory allocation";
|
||||
default:
|
||||
return "unknown CGL error";
|
||||
}
|
||||
}
|
||||
|
||||
CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer, GLint width, GLint height) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (!object) {
|
||||
return kCGLBadContext;
|
||||
}
|
||||
if (!metalLayer) {
|
||||
return kCGLBadDrawable;
|
||||
}
|
||||
width = std::max<GLint>(width, 1);
|
||||
height = std::max<GLint>(height, 1);
|
||||
const Bool sameSize = object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height;
|
||||
object->SurfaceBackingSize[0] = width;
|
||||
object->SurfaceBackingSize[1] = height;
|
||||
if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer && sameSize) {
|
||||
object->View = nsView;
|
||||
object->HasDrawable = true;
|
||||
return kCGLNoError;
|
||||
}
|
||||
if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer) {
|
||||
object->View = nsView;
|
||||
object->HasDrawable = true;
|
||||
return ResizeSurfaceLocked(*object);
|
||||
}
|
||||
if (object->Surface != EGL_NO_SURFACE) {
|
||||
EGLImpl::DestroySurface(object->Display, object->Surface);
|
||||
object->Surface = EGL_NO_SURFACE;
|
||||
}
|
||||
object->View = nsView;
|
||||
object->MetalLayer = metalLayer;
|
||||
const auto recreateError = RecreateSurfaceLocked(ctx, *object);
|
||||
if (recreateError != kCGLNoError) {
|
||||
return recreateError;
|
||||
}
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
void* GetContextNSObject(CGLContextObj ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
return object ? object->NSObject : nullptr;
|
||||
}
|
||||
|
||||
void SetContextNSObject(CGLContextObj ctx, void* nsObject) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||
auto* object = TryGetContext(ctx);
|
||||
if (object) {
|
||||
object->NSObject = nsObject;
|
||||
}
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::CGLImpl
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
// MobileGL - MobileGL/MG_Impl/CGLImpl/CGLImpl.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#ifndef GL_SILENCE_DEPRECATION
|
||||
#define GL_SILENCE_DEPRECATION
|
||||
#endif
|
||||
#include <OpenGL/OpenGL.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::CGLImpl {
|
||||
CGLError ChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix);
|
||||
CGLError DestroyPixelFormat(CGLPixelFormatObj pix);
|
||||
CGLError DescribePixelFormat(CGLPixelFormatObj pix, GLint pixNum, CGLPixelFormatAttribute attrib, GLint* value);
|
||||
void ReleasePixelFormat(CGLPixelFormatObj pix);
|
||||
CGLPixelFormatObj RetainPixelFormat(CGLPixelFormatObj pix);
|
||||
GLuint GetPixelFormatRetainCount(CGLPixelFormatObj pix);
|
||||
|
||||
CGLError CreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx);
|
||||
CGLError DestroyContext(CGLContextObj ctx);
|
||||
CGLContextObj RetainContext(CGLContextObj ctx);
|
||||
void ReleaseContext(CGLContextObj ctx);
|
||||
GLuint GetContextRetainCount(CGLContextObj ctx);
|
||||
CGLPixelFormatObj GetPixelFormat(CGLContextObj ctx);
|
||||
|
||||
CGLError SetCurrentContext(CGLContextObj ctx);
|
||||
CGLContextObj GetCurrentContext();
|
||||
CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params);
|
||||
CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params);
|
||||
CGLError UpdateContext(CGLContextObj ctx);
|
||||
CGLError ClearDrawable(CGLContextObj ctx);
|
||||
CGLError FlushDrawable(CGLContextObj ctx);
|
||||
CGLError LockContext(CGLContextObj ctx);
|
||||
CGLError UnlockContext(CGLContextObj ctx);
|
||||
void GetVersion(GLint* majorvers, GLint* minorvers);
|
||||
const char* ErrorString(CGLError error);
|
||||
|
||||
CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer, GLint width, GLint height);
|
||||
void* GetContextNSObject(CGLContextObj ctx);
|
||||
void SetContextNSObject(CGLContextObj ctx, void* nsObject);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
// MobileGL - MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "../CGLImpl.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLChoosePixelFormat(const CGLPixelFormatAttribute* attribs,
|
||||
CGLPixelFormatObj* pix,
|
||||
GLint* npix) {
|
||||
return MobileGL::MG_Impl::CGLImpl::ChoosePixelFormat(attribs, pix, npix);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLDestroyPixelFormat(CGLPixelFormatObj pix) {
|
||||
return MobileGL::MG_Impl::CGLImpl::DestroyPixelFormat(pix);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLDescribePixelFormat(CGLPixelFormatObj pix,
|
||||
GLint pix_num,
|
||||
CGLPixelFormatAttribute attrib,
|
||||
GLint* value) {
|
||||
return MobileGL::MG_Impl::CGLImpl::DescribePixelFormat(pix, pix_num, attrib, value);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API void CGLReleasePixelFormat(CGLPixelFormatObj pix) {
|
||||
MobileGL::MG_Impl::CGLImpl::ReleasePixelFormat(pix);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLPixelFormatObj CGLRetainPixelFormat(CGLPixelFormatObj pix) {
|
||||
return MobileGL::MG_Impl::CGLImpl::RetainPixelFormat(pix);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pix) {
|
||||
return MobileGL::MG_Impl::CGLImpl::GetPixelFormatRetainCount(pix);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLCreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::CreateContext(pix, share, ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLDestroyContext(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::DestroyContext(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLContextObj CGLRetainContext(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::RetainContext(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API void CGLReleaseContext(CGLContextObj ctx) {
|
||||
MobileGL::MG_Impl::CGLImpl::ReleaseContext(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API GLuint CGLGetContextRetainCount(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::GetContextRetainCount(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLPixelFormatObj CGLGetPixelFormat(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::GetPixelFormat(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLSetCurrentContext(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::SetCurrentContext(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLContextObj CGLGetCurrentContext(void) {
|
||||
return MobileGL::MG_Impl::CGLImpl::GetCurrentContext();
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLSetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
|
||||
return MobileGL::MG_Impl::CGLImpl::SetParameter(ctx, pname, params);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLGetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params) {
|
||||
return MobileGL::MG_Impl::CGLImpl::GetParameter(ctx, pname, params);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLUpdateContext(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::UpdateContext(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLClearDrawable(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::ClearDrawable(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLFlushDrawable(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::FlushDrawable(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLLockContext(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::LockContext(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API CGLError CGLUnlockContext(CGLContextObj ctx) {
|
||||
return MobileGL::MG_Impl::CGLImpl::UnlockContext(ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API void CGLGetVersion(GLint* majorvers, GLint* minorvers) {
|
||||
MobileGL::MG_Impl::CGLImpl::GetVersion(majorvers, minorvers);
|
||||
}
|
||||
|
||||
MOBILEGL_CGL_API const char* CGLErrorString(CGLError error) {
|
||||
return MobileGL::MG_Impl::CGLImpl::ErrorString(error);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,56 @@
|
||||
// MobileGL - MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#include "MG_Impl/GetProcAddress.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace {
|
||||
struct DyldInterposeEntry {
|
||||
const void* Replacement;
|
||||
const void* Replacee;
|
||||
};
|
||||
|
||||
bool IsGLProcName(const char* name) {
|
||||
if (name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strncmp(name, "CGL", 3) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strncmp(name, "gl", 2) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid stealing glfw*/glib*/glX*/global application symbols.
|
||||
return name[2] >= 'A' && name[2] <= 'Z' && name[2] != 'X';
|
||||
}
|
||||
|
||||
void* MobileGLDlsym(void* handle, const char* symbol) {
|
||||
if (IsGLProcName(symbol)) {
|
||||
if (void* proc = MobileGL::MG_Impl::GetProcAddress(symbol)) {
|
||||
return proc;
|
||||
}
|
||||
}
|
||||
|
||||
return dlsym(handle, symbol);
|
||||
}
|
||||
|
||||
__attribute__((used)) static const DyldInterposeEntry kMobileGLDyldInterpose[]
|
||||
__attribute__((section("__DATA,__interpose"))) = {
|
||||
{reinterpret_cast<const void*>(MobileGLDlsym), reinterpret_cast<const void*>(dlsym)},
|
||||
};
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "../GetProcAddress.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/EGLState/Core.h>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
namespace MobileGL::MG_Impl::EGLImpl {
|
||||
@@ -20,7 +22,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (!MG_State::pEGLContext) {
|
||||
MGLOG_E("pEGLContext is null. MG_State may not be initialized.");
|
||||
}
|
||||
return MG_State::pEGLContext;
|
||||
return MG_State::pEGLContext.get();
|
||||
}
|
||||
|
||||
MG_Backend::BackendObject* GetBackendObject(EGLStateContext* state) {
|
||||
@@ -31,14 +33,53 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return backendObject;
|
||||
}
|
||||
|
||||
std::recursive_mutex& EGLOperationMutex() {
|
||||
static std::recursive_mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
String CurrentThreadIdString() {
|
||||
std::ostringstream stream;
|
||||
stream << std::this_thread::get_id();
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
MG_Backend::WindowBackend DetectWindowBackend() {
|
||||
#if defined(ANDROID) || defined(__ANDROID__)
|
||||
return MG_Backend::WindowBackend::Android;
|
||||
#elif defined(__APPLE__)
|
||||
return MG_Backend::WindowBackend::MetalLayer;
|
||||
#elif defined(__linux__)
|
||||
return MG_Backend::WindowBackend::X11;
|
||||
#else
|
||||
return MG_Backend::WindowBackend::Unknown;
|
||||
#endif
|
||||
}
|
||||
|
||||
EGLint GetAttribValue(const EGLint* attribList, EGLint attrib, EGLint defaultValue) {
|
||||
if (!attribList) {
|
||||
return defaultValue;
|
||||
}
|
||||
for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) {
|
||||
if (attribList[i] == attrib) {
|
||||
return attribList[i + 1];
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
EGLint GetAttribValueAttrib(const EGLAttrib* attribList, EGLint attrib, EGLint defaultValue) {
|
||||
if (!attribList) {
|
||||
return defaultValue;
|
||||
}
|
||||
for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) {
|
||||
if (attribList[i] == attrib) {
|
||||
return static_cast<EGLint>(attribList[i + 1]);
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
template <typename NativeType>
|
||||
Bool IsNullNativeHandle(NativeType nativeHandle) {
|
||||
if constexpr (std::is_pointer_v<NativeType>) {
|
||||
@@ -77,25 +118,35 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
const MG_Backend::WindowHandle windowHandle = {
|
||||
.Backend = DetectWindowBackend(),
|
||||
.Handle = ToVoidHandle(window),
|
||||
.Width = static_cast<Uint32>(std::max<EGLint>(GetAttribValue(attrib_list, EGL_WIDTH, 0), 0)),
|
||||
.Height = static_cast<Uint32>(std::max<EGLint>(GetAttribValue(attrib_list, EGL_HEIGHT, 0), 0)),
|
||||
};
|
||||
if (!backendObject->CreateEGLWindowSurface(windowHandle)) {
|
||||
|
||||
EGLSurface surface = state->CreateWindowSurface(dpy, config, window, attrib_list);
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
state->DestroySurface(dpy, surface);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (!backendObject->CreateEGLWindowSurface(surface, windowHandle)) {
|
||||
state->DestroySurface(dpy, surface);
|
||||
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
return state->CreateWindowSurface(dpy, config, window, attrib_list);
|
||||
return surface;
|
||||
}
|
||||
|
||||
EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
@@ -111,6 +162,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!backendObject->SwapEGLBuffers(dpy, draw)) {
|
||||
MGLOG_E("eglSwapBuffers failed on thread=%s dpy=%p draw=%p", CurrentThreadIdString().c_str(), dpy, draw);
|
||||
state->SetError(EGL_BAD_SURFACE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
@@ -172,6 +224,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
}
|
||||
|
||||
EGLBoolean MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
@@ -181,17 +234,30 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
const auto oldDraw = state->GetCurrentSurface(EGL_DRAW);
|
||||
const auto oldRead = state->GetCurrentSurface(EGL_READ);
|
||||
const auto oldContext = state->GetCurrentContext();
|
||||
const String threadId = CurrentThreadIdString();
|
||||
|
||||
MGLOG_D("eglMakeCurrent begin thread=%s dpy=%p draw=%p read=%p ctx=%p oldDpy=%p oldDraw=%p oldRead=%p oldCtx=%p",
|
||||
threadId.c_str(), dpy, draw, read, ctx, oldDisplay, oldDraw, oldRead, oldContext);
|
||||
|
||||
if (!state->MakeCurrent(dpy, draw, read, ctx)) {
|
||||
const EGLint error = state->ConsumeError();
|
||||
MGLOG_D("eglMakeCurrent rejected by EGLState thread=%s error=0x%04x", threadId.c_str(), error);
|
||||
state->SetError(error);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
|
||||
const Bool releaseCurrentRequest =
|
||||
dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||
if (releaseCurrentRequest) {
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||
(void)backendObject->MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
|
||||
MGLOG_E("eglMakeCurrent release failed in backend thread=%s", threadId.c_str());
|
||||
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
||||
state->SetError(EGL_BAD_ACCESS);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
}
|
||||
MGLOG_D("eglMakeCurrent release succeeded thread=%s", threadId.c_str());
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
@@ -202,10 +268,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
|
||||
MGLOG_E("eglMakeCurrent backend attach failed thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(),
|
||||
dpy, draw, read, ctx);
|
||||
state->SetError(EGL_BAD_ACCESS);
|
||||
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
MGLOG_D("eglMakeCurrent attach succeeded thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(), dpy, draw,
|
||||
read, ctx);
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
@@ -218,11 +288,18 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
}
|
||||
|
||||
EGLBoolean DestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
||||
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->DestroySurface(dpy, surface) ? EGL_TRUE : EGL_FALSE;
|
||||
if (!state->DestroySurface(dpy, surface)) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||
backendObject->ReleaseEGLSurface(surface);
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean Terminate(EGLDisplay dpy) {
|
||||
@@ -230,7 +307,13 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->TerminateDisplay(dpy) ? EGL_TRUE : EGL_FALSE;
|
||||
if (!state->TerminateDisplay(dpy)) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||
backendObject->ReleaseEGLResources();
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLBoolean ReleaseThread() {
|
||||
@@ -238,6 +321,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||
(void)backendObject->MakeEGLCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
state->ReleaseThread();
|
||||
return EGL_TRUE;
|
||||
}
|
||||
@@ -310,7 +396,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
case EGL_CLIENT_APIS:
|
||||
return "OpenGL OpenGL_ES";
|
||||
case EGL_EXTENSIONS:
|
||||
return "";
|
||||
if (display == EGL_NO_DISPLAY) {
|
||||
return "EGL_EXT_client_extensions "
|
||||
"EGL_EXT_platform_base "
|
||||
"EGL_KHR_platform_base "
|
||||
"EGL_MESA_platform_surfaceless";
|
||||
}
|
||||
return "EGL_KHR_create_context "
|
||||
"EGL_MESA_platform_surfaceless";
|
||||
default:
|
||||
state->SetError(EGL_BAD_PARAMETER);
|
||||
return nullptr;
|
||||
@@ -322,7 +415,17 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return state->SwapInterval(dpy, interval) ? EGL_TRUE : EGL_FALSE;
|
||||
if (!state->SwapInterval(dpy, interval)) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
// Forward the request to the backend's native presentation path; without this
|
||||
// the app's vsync setting only ever reaches MobileGL's shadow state and the
|
||||
// native surface stays at the driver default (interval 1 = always vsynced).
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (backendObject) {
|
||||
backendObject->SetEGLSwapInterval(static_cast<Int>(interval));
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
||||
@@ -330,7 +433,24 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (!state) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
return state->CreatePbufferSurface(dpy, config, attrib_list);
|
||||
const EGLint width = GetAttribValue(attrib_list, EGL_WIDTH, 1);
|
||||
const EGLint height = GetAttribValue(attrib_list, EGL_HEIGHT, 1);
|
||||
EGLSurface surface = state->CreatePbufferSurface(dpy, config, attrib_list);
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (!backendObject->CreateEGLPbufferSurface(surface, width, height)) {
|
||||
state->DestroySurface(dpy, surface);
|
||||
state->SetError(EGL_BAD_ALLOC);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
EGLBoolean BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||
@@ -547,22 +667,53 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
const MG_Backend::WindowHandle windowHandle = {
|
||||
.Backend = DetectWindowBackend(),
|
||||
.Handle = native_window,
|
||||
.Width = static_cast<Uint32>(std::max<EGLint>(GetAttribValueAttrib(attrib_list, EGL_WIDTH, 0), 0)),
|
||||
.Height = static_cast<Uint32>(std::max<EGLint>(GetAttribValueAttrib(attrib_list, EGL_HEIGHT, 0), 0)),
|
||||
};
|
||||
if (!backendObject->CreateEGLWindowSurface(windowHandle)) {
|
||||
|
||||
EGLSurface surface = state->CreatePlatformWindowSurface(dpy, config, native_window, attrib_list);
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
state->DestroySurface(dpy, surface);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
if (!backendObject->CreateEGLWindowSurface(surface, windowHandle)) {
|
||||
state->DestroySurface(dpy, surface);
|
||||
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
return state->CreatePlatformWindowSurface(dpy, config, native_window, attrib_list);
|
||||
return surface;
|
||||
}
|
||||
|
||||
EGLBoolean ResizePlatformWindowSurface(EGLDisplay dpy, EGLSurface surface, EGLint width, EGLint height) {
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
if (!state->ResizeSurface(dpy, surface, width, height)) {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
if (!backendObject) {
|
||||
MGLOG_E("activeBackendObject not initialized!");
|
||||
return EGL_FALSE;
|
||||
}
|
||||
width = std::max<EGLint>(width, 1);
|
||||
height = std::max<EGLint>(height, 1);
|
||||
if (!backendObject->ResizeEGLWindowSurface(surface, static_cast<Uint32>(width), static_cast<Uint32>(height))) {
|
||||
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list);
|
||||
EGLSurface CreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
||||
const EGLAttrib* attrib_list);
|
||||
EGLBoolean ResizePlatformWindowSurface(EGLDisplay dpy, EGLSurface surface, EGLint width, EGLint height);
|
||||
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
||||
const EGLAttrib* attrib_list);
|
||||
EGLBoolean WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags);
|
||||
|
||||
@@ -235,6 +235,14 @@ MOBILEGL_EGL_API EGLDisplay eglGetPlatformDisplay(EGLenum platform, void* native
|
||||
return MobileGL::MG_Impl::EGLImpl::GetPlatformDisplay(platform, native_display, attrib_list);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLDisplay eglGetPlatformDisplayEXT(EGLenum platform, void* native_display,
|
||||
const EGLint* attrib_list) {
|
||||
MGLOG_D("eglGetPlatformDisplayEXT(platform=%u, native_display=%p, attrib_list=%p)", platform, native_display,
|
||||
attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetPlatformDisplay(
|
||||
platform, native_display, reinterpret_cast<const EGLAttrib*>(attrib_list));
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLSurface eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
||||
const EGLAttrib* attrib_list) {
|
||||
MGLOG_D("eglCreatePlatformWindowSurface(dpy=%p, config=%p, native_window=%p, attrib_list=%p)", dpy, config,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,24 +9,45 @@
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||
GLboolean IsBuffer(GLuint buffer);
|
||||
void DeleteBuffers(GLsizei n, const GLuint* buffers);
|
||||
void FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length);
|
||||
GLboolean UnmapBuffer(GLenum target);
|
||||
void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||
void* MapBuffer(GLenum target, GLenum access);
|
||||
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset,
|
||||
GLsizeiptr size);
|
||||
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||
void BindBuffer(GLenum target, GLuint buffer);
|
||||
void GenBuffers(GLsizei n, GLuint* buffers);
|
||||
void BindBufferBase(GLenum target, GLuint index, GLuint buffer);
|
||||
void BindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||
void GetBufferParameteri64v(GLenum target, GLenum pname, GLint64* params);
|
||||
void GetBufferPointerv(GLenum target, GLenum pname, void** params);
|
||||
GLboolean IsBuffer(GLuint buffer);
|
||||
void DeleteBuffers(GLsizei n, const GLuint* buffers);
|
||||
void FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length);
|
||||
GLboolean UnmapBuffer(GLenum target);
|
||||
void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||
void* MapBuffer(GLenum target, GLenum access);
|
||||
void BufferStorage(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags);
|
||||
void CreateBuffers(GLsizei n, GLuint* buffers);
|
||||
void NamedBufferStorage(GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags);
|
||||
void NamedBufferData(GLuint buffer, GLsizeiptr size, const void* data, GLenum usage);
|
||||
void NamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data);
|
||||
void CopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset,
|
||||
GLsizeiptr size);
|
||||
void ClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data);
|
||||
void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format,
|
||||
GLenum type, const void* data);
|
||||
void* MapNamedBuffer(GLuint buffer, GLenum access);
|
||||
void* MapNamedBufferRange(GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||
GLboolean UnmapNamedBuffer(GLuint buffer);
|
||||
void FlushMappedNamedBufferRange(GLuint buffer, GLintptr offset, GLsizeiptr length);
|
||||
void GetNamedBufferParameteriv(GLuint buffer, GLenum pname, GLint* params);
|
||||
void GetNamedBufferParameteri64v(GLuint buffer, GLenum pname, GLint64* params);
|
||||
void GetNamedBufferPointerv(GLuint buffer, GLenum pname, void** params);
|
||||
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset,
|
||||
GLsizeiptr size);
|
||||
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data);
|
||||
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||
void BindBuffer(GLenum target, GLuint buffer);
|
||||
void GenBuffers(GLsizei n, GLuint* buffers);
|
||||
void BindBufferBase(GLenum target, GLuint index, GLuint buffer);
|
||||
void BindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
void BindBuffersBase(GLenum target, GLuint first, GLsizei count, const GLuint* buffers);
|
||||
void BindBuffersRange(GLenum target, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets,
|
||||
const GLsizeiptr* sizes);
|
||||
|
||||
} // namespace MG_Impl::GLImpl
|
||||
} // namespace MobileGL
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
@@ -7,111 +7,127 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "Validators.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/ErrorState/Error.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/BufferEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToStr/BufferEnumConverter.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace BufferImpl {
|
||||
Bool ValidateBufferTarget(BufferTarget target) {
|
||||
if (target == BufferTarget::Unknown) {
|
||||
using namespace MG_Util;
|
||||
String bufferTargetStr = ConvertBufferTargetToString(target);
|
||||
String glTargetStr = ConvertGLEnumToString(ConvertBufferTargetToGLEnum(target));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeShared<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferTarget",
|
||||
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == BufferTarget::Index && MG_State::pGLContext->GetBoundVertexArray() == nullptr) {
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
||||
"ValidateBufferTarget",
|
||||
"No vertex array object is bound."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateBufferBindingPointTarget(BufferTarget target) {
|
||||
if (target != BufferTarget::Uniform && target != BufferTarget::AtomicCounter &&
|
||||
target != BufferTarget::TransformFeedback && target != BufferTarget::ShaderStorage) {
|
||||
using namespace MG_Util;
|
||||
String bufferTargetStr = ConvertBufferTargetToString(target);
|
||||
String glTargetStr = ConvertGLEnumToString(ConvertBufferTargetToGLEnum(target));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeShared<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferTarget",
|
||||
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateBufferName(Uint index, Bool allowZero) {
|
||||
if (index == 0) {
|
||||
if (allowZero) return true;
|
||||
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidValue,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
||||
"ValidateBufferName",
|
||||
"Buffer name 0 is not valid."));
|
||||
return false;
|
||||
}
|
||||
Bool isValid = MG_State::pGLContext->ValidateBufferName(index);
|
||||
if (isValid) return true;
|
||||
namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
||||
Bool ValidateBufferTarget(BufferTarget target) {
|
||||
if (target == BufferTarget::Unknown) {
|
||||
using namespace MG_Util;
|
||||
String bufferTargetStr = ConvertBufferTargetToString(target);
|
||||
String glTargetStr = ConvertGLEnumToString(ConvertBufferTargetToGLEnum(target));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferName",
|
||||
std::format("Buffer name {} is not valid.", index)));
|
||||
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferTarget",
|
||||
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ValidateBufferUsage(BufferUsage usage) {
|
||||
if (usage != BufferUsage::Unknown) {
|
||||
return true;
|
||||
}
|
||||
if (target == BufferTarget::Index && MG_State::pGLContext->GetBoundVertexArray() == nullptr) {
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
||||
"ValidateBufferTarget",
|
||||
"No vertex array object is bound."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateBufferBindingPointTarget(BufferTarget target) {
|
||||
if (target != BufferTarget::Uniform && target != BufferTarget::AtomicCounter &&
|
||||
target != BufferTarget::TransformFeedback && target != BufferTarget::ShaderStorage) {
|
||||
using namespace MG_Util;
|
||||
String bufferUsageStr = ConvertBufferUsageToString(usage);
|
||||
String glUsageStr = ConvertGLEnumToString(ConvertBufferUsageToGLEnum(usage));
|
||||
String bufferTargetStr = ConvertBufferTargetToString(target);
|
||||
String glTargetStr = ConvertGLEnumToString(ConvertBufferTargetToGLEnum(target));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferTarget",
|
||||
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateBufferBindingPointIndex(BufferTarget target, Uint index) {
|
||||
SizeT pointCount = MG_State::pGLContext->GetBufferBindingPointCount(target);
|
||||
if (target == BufferTarget::ShaderStorage && MG_Backend::pActiveBackendObject) {
|
||||
const Int backendCount =
|
||||
MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings;
|
||||
pointCount = std::min(pointCount, static_cast<SizeT>(std::max(backendCount, 0)));
|
||||
}
|
||||
|
||||
if (index < pointCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferBindingPointIndex",
|
||||
std::format("Binding point index {} is out of range for target {}.", index,
|
||||
MG_Util::ConvertBufferTargetToString(target))));
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ValidateBufferName(Uint index, Bool allowZero) {
|
||||
if (index == 0) {
|
||||
if (allowZero) return true;
|
||||
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferName",
|
||||
"Buffer name 0 is not valid."));
|
||||
return false;
|
||||
}
|
||||
Bool isValid = MG_State::pGLContext->ValidateBufferName(index);
|
||||
if (isValid) return true;
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferName",
|
||||
std::format("Buffer name {} is not valid.", index)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ValidateBufferUsage(BufferUsage usage) {
|
||||
if (usage != BufferUsage::Unknown) {
|
||||
return true;
|
||||
}
|
||||
using namespace MG_Util;
|
||||
String bufferUsageStr = ConvertBufferUsageToString(usage);
|
||||
String glUsageStr = ConvertGLEnumToString(ConvertBufferUsageToGLEnum(usage));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferUsage",
|
||||
std::format("Usage {} ({}) is not one of the allowable values.", bufferUsageStr, glUsageStr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
||||
if (accessBits == BufferMappingAccessBit::Null) {
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
||||
"ValidateBufferMappingAccess",
|
||||
"Access bits cannot be null."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto validBits = BufferMappingAccessBit::Read | BufferMappingAccessBit::Write |
|
||||
BufferMappingAccessBit::InvalidateRange | BufferMappingAccessBit::InvalidateBuffer |
|
||||
BufferMappingAccessBit::FlushExplicit | BufferMappingAccessBit::Unsynchronized |
|
||||
BufferMappingAccessBit::Persistent | BufferMappingAccessBit::Coherent;
|
||||
|
||||
if ((accessBits & validBits) != accessBits) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeShared<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferUsage",
|
||||
std::format("Usage {} ({}) is not one of the allowable values.", bufferUsageStr, glUsageStr)));
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferMappingAccess",
|
||||
"Access bits cannot contain invalid flags."));
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
||||
if (accessBits == BufferMappingAccessBit::Null) {
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
||||
"ValidateBufferMappingAccess",
|
||||
"Access bits cannot be null."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto validBits = BufferMappingAccessBit::Read | BufferMappingAccessBit::Write |
|
||||
BufferMappingAccessBit::InvalidateRange | BufferMappingAccessBit::InvalidateBuffer |
|
||||
BufferMappingAccessBit::FlushExplicit | BufferMappingAccessBit::Unsynchronized |
|
||||
BufferMappingAccessBit::Persistent | BufferMappingAccessBit::Coherent;
|
||||
|
||||
if ((accessBits & validBits) != accessBits) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferMappingAccess",
|
||||
"Access bits cannot contain invalid flags."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace BufferImpl
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
return true;
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::BufferImpl
|
||||
|
||||
@@ -10,12 +10,11 @@
|
||||
#include <Includes.h>
|
||||
#include <MG_State/GLState/BufferState/BufferObject.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace BufferImpl {
|
||||
Bool ValidateBufferTarget(BufferTarget target);
|
||||
Bool ValidateBufferName(Uint index, Bool allowZero = false);
|
||||
Bool ValidateBufferUsage(BufferUsage usage);
|
||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits);
|
||||
Bool ValidateBufferBindingPointTarget(BufferTarget target);
|
||||
} // namespace BufferImpl
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
||||
Bool ValidateBufferTarget(BufferTarget target);
|
||||
Bool ValidateBufferName(Uint index, Bool allowZero = false);
|
||||
Bool ValidateBufferUsage(BufferUsage usage);
|
||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits);
|
||||
Bool ValidateBufferBindingPointTarget(BufferTarget target);
|
||||
Bool ValidateBufferBindingPointIndex(BufferTarget target, Uint index);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::BufferImpl
|
||||
|
||||
@@ -9,9 +9,75 @@
|
||||
#include "GL_Drawing.h"
|
||||
#include <Config.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/EGLState/Core.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
||||
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (!currentProgram) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "There is no current program object."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!currentProgram->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"The current program object is not linked."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Bool ValidateCurrentProgramForCompute(const char* functionName) {
|
||||
if (!ValidateCurrentProgramForExecution(functionName)) return false;
|
||||
|
||||
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (currentProgram->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"The current program object has no compute shader stage."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) {
|
||||
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
||||
if (!activeBackendObject) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "No active backend object."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activeBackendObject->GetBackendType() == BackendType::DirectVulkan && mode == GL_LINE_LOOP) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", functionName,
|
||||
"Primitive mode GL_LINE_LOOP is not supported by the DirectVulkan backend."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||
if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"Default vertex array object cannot be used for drawing in core profile."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Clear_Backend(GLbitfield mask) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
@@ -50,6 +116,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawArrays(mode, first, count);
|
||||
}
|
||||
|
||||
void MultiDrawArrays_Backend(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArrays(mode, first, count, drawcount);
|
||||
}
|
||||
|
||||
void DrawElementsBaseVertex_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLint basevertex) {
|
||||
#ifdef TRACY_ENABLE
|
||||
@@ -73,6 +146,24 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirect(mode, indirect, drawcount, stride);
|
||||
}
|
||||
|
||||
void MultiDrawElementsIndirectCount_Backend(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount(mode, type, indirect, drawcount,
|
||||
maxdrawcount, stride);
|
||||
}
|
||||
|
||||
void MultiDrawArraysIndirectCount_Backend(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount(mode, indirect, drawcount, maxdrawcount,
|
||||
stride);
|
||||
}
|
||||
|
||||
void DrawRangeElementsBaseVertex_Backend(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex) {
|
||||
#ifdef TRACY_ENABLE
|
||||
@@ -156,75 +247,193 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
|
||||
auto dispatchCompute = MG_Backend::gBackendFunctionsTable.GL.DispatchCompute;
|
||||
if (!dispatchCompute) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Backend does not support compute dispatch."));
|
||||
return;
|
||||
}
|
||||
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||
dispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
|
||||
}
|
||||
|
||||
void DispatchComputeIndirect(GLintptr indirect) {
|
||||
auto dispatchComputeIndirect = MG_Backend::gBackendFunctionsTable.GL.DispatchComputeIndirect;
|
||||
if (!dispatchComputeIndirect) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Backend does not support indirect compute dispatch."));
|
||||
return;
|
||||
}
|
||||
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||
dispatchComputeIndirect(indirect);
|
||||
}
|
||||
|
||||
void MemoryBarrier(GLbitfield barriers) {
|
||||
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
|
||||
if (!memoryBarrier) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Backend does not support memory barriers."));
|
||||
return;
|
||||
}
|
||||
memoryBarrier(barriers);
|
||||
}
|
||||
|
||||
void MemoryBarrierByRegion(GLbitfield barriers) {
|
||||
auto memoryBarrierByRegion = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrierByRegion;
|
||||
if (!memoryBarrierByRegion) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Backend does not support regional memory barriers."));
|
||||
return;
|
||||
}
|
||||
memoryBarrierByRegion(barriers);
|
||||
}
|
||||
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride);
|
||||
}
|
||||
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
MultiDrawArraysIndirect_Backend(mode, indirect, drawcount, stride);
|
||||
}
|
||||
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride) {
|
||||
auto multiDrawElementsIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount;
|
||||
if (!multiDrawElementsIndirectCount) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Backend does not support indirect-parameter indexed draws."));
|
||||
return;
|
||||
}
|
||||
MultiDrawElementsIndirectCount_Backend(mode, type, indirect, drawcount, maxdrawcount, stride);
|
||||
}
|
||||
|
||||
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride) {
|
||||
auto multiDrawArraysIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount;
|
||||
if (!multiDrawArraysIndirectCount) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Backend does not support indirect-parameter array draws."));
|
||||
return;
|
||||
}
|
||||
MultiDrawArraysIndirectCount_Backend(mode, indirect, drawcount, maxdrawcount, stride);
|
||||
}
|
||||
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawRangeElementsBaseVertex_Backend(mode, start, end, count, type, indices, basevertex);
|
||||
}
|
||||
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawRangeElements_Backend(mode, start, end, count, type, indices);
|
||||
}
|
||||
|
||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawElementsInstancedBaseVertexBaseInstance_Backend(mode, count, type, indices, instancecount, basevertex,
|
||||
baseinstance);
|
||||
}
|
||||
|
||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawElementsInstancedBaseVertex_Backend(mode, count, type, indices, instancecount, basevertex);
|
||||
}
|
||||
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawElementsInstancedBaseInstance_Backend(mode, count, type, indices, instancecount, baseinstance);
|
||||
}
|
||||
|
||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawElementsInstanced_Backend(mode, count, type, indices, instancecount);
|
||||
}
|
||||
|
||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawElementsIndirect_Backend(mode, type, indirect);
|
||||
}
|
||||
|
||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
GLuint baseinstance) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawArraysInstancedBaseInstance_Backend(mode, first, count, instancecount, baseinstance);
|
||||
}
|
||||
|
||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawArraysInstanced_Backend(mode, first, count, instancecount);
|
||||
}
|
||||
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawArraysIndirect_Backend(mode, indirect);
|
||||
}
|
||||
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex);
|
||||
}
|
||||
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawArrays_Backend(mode, first, count);
|
||||
}
|
||||
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
if (drawcount < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "drawcount must be non-negative."));
|
||||
return;
|
||||
}
|
||||
MultiDrawArrays_Backend(mode, first, count, drawcount);
|
||||
}
|
||||
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||
GLsizei drawcount) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
MultiDrawElements_Backend(mode, count, type, indices, drawcount);
|
||||
}
|
||||
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
MultiDrawElementsBaseVertex_Backend(mode, count, type, indices, drawcount, basevertex);
|
||||
}
|
||||
|
||||
@@ -233,6 +442,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
DrawElements_Backend(mode, count, type, indices);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,34 +9,40 @@
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
||||
GLsizei stride);
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex);
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance);
|
||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex);
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance);
|
||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount);
|
||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect);
|
||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
GLuint baseinstance);
|
||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex);
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||
GLsizei drawcount);
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex);
|
||||
void Clear(GLbitfield mask);
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||
} // namespace MG_Impl::GLImpl
|
||||
} // namespace MobileGL
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void DispatchComputeIndirect(GLintptr indirect);
|
||||
void MemoryBarrier(GLbitfield barriers);
|
||||
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex);
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance);
|
||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex);
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance);
|
||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount);
|
||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect);
|
||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
GLuint baseinstance);
|
||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex);
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||
GLsizei drawcount);
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex);
|
||||
void Clear(GLbitfield mask);
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -11,51 +11,73 @@
|
||||
#include <Includes.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
||||
GLsizei height);
|
||||
void RenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
GLboolean IsRenderbuffer(GLuint renderbuffer);
|
||||
void GetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||
void GenRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||
void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
||||
void DeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers);
|
||||
void BindRenderbuffer(GLenum target, GLuint renderbuffer);
|
||||
void SampleMaski(GLuint maskNumber, GLbitfield mask);
|
||||
GLboolean IsFramebuffer(GLuint framebuffer);
|
||||
void GetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint* params);
|
||||
void GenFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||
void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
||||
void FramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
||||
GLint zoffset);
|
||||
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
void FramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
void FramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
|
||||
void DrawBuffer(GLenum buf);
|
||||
void DrawBuffers(GLsizei n, const GLenum* bufs);
|
||||
void ReadBuffer(GLenum src);
|
||||
void DeleteFramebuffers(GLsizei n, const GLuint* framebuffers);
|
||||
GLenum CheckFramebufferStatus(GLenum target);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void BindFramebuffer(GLenum target, GLuint framebuffer);
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
||||
GLsizei height);
|
||||
void RenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
GLboolean IsRenderbuffer(GLuint renderbuffer);
|
||||
void GetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||
void GenRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||
void CreateRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||
void NamedRenderbufferStorage(GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
void NamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat,
|
||||
GLsizei width, GLsizei height);
|
||||
void GetNamedRenderbufferParameteriv(GLuint renderbuffer, GLenum pname, GLint* params);
|
||||
void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
||||
void NamedFramebufferRenderbuffer(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget,
|
||||
GLuint renderbuffer);
|
||||
void DeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers);
|
||||
void BindRenderbuffer(GLenum target, GLuint renderbuffer);
|
||||
void SampleMaski(GLuint maskNumber, GLbitfield mask);
|
||||
GLboolean IsFramebuffer(GLuint framebuffer);
|
||||
void GetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint* params);
|
||||
void GenFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||
void CreateFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||
void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
||||
void FramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
||||
GLint zoffset);
|
||||
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
void FramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
void FramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
|
||||
void NamedFramebufferTexture(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level);
|
||||
void NamedFramebufferTexture1D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
void NamedFramebufferTexture2D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
void NamedFramebufferTexture3D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
||||
GLint zoffset);
|
||||
void NamedFramebufferTextureLayer(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
||||
void NamedFramebufferDrawBuffer(GLuint framebuffer, GLenum buf);
|
||||
void NamedFramebufferDrawBuffers(GLuint framebuffer, GLsizei n, const GLenum* bufs);
|
||||
void NamedFramebufferReadBuffer(GLuint framebuffer, GLenum src);
|
||||
void ClearNamedFramebufferfv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferfi(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
GLenum CheckNamedFramebufferStatus(GLuint framebuffer, GLenum target);
|
||||
void GetNamedFramebufferAttachmentParameteriv(GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params);
|
||||
void BlitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1,
|
||||
GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask,
|
||||
GLenum filter);
|
||||
void DrawBuffer(GLenum buf);
|
||||
void DrawBuffers(GLsizei n, const GLenum* bufs);
|
||||
void ReadBuffer(GLenum src);
|
||||
void DeleteFramebuffers(GLsizei n, const GLuint* framebuffers);
|
||||
GLenum CheckFramebufferStatus(GLenum target);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void BindFramebuffer(GLenum target, GLuint framebuffer);
|
||||
|
||||
namespace FramebufferImpl {
|
||||
struct DefaultFramebufferInfo {
|
||||
SharedPtr<MG_State::GLState::FramebufferObject> defaultFBO;
|
||||
SharedPtr<MG_State::GLState::ITextureObject> colorAttachment;
|
||||
SharedPtr<MG_State::GLState::ITextureObject> depthAttachment;
|
||||
SharedPtr<MG_State::GLState::ITextureObject> stencilAttachment;
|
||||
};
|
||||
namespace FramebufferImpl {
|
||||
struct DefaultFramebufferInfo {
|
||||
SharedPtr<MG_State::GLState::FramebufferObject> defaultFBO;
|
||||
SharedPtr<MG_State::GLState::ITextureObject> colorAttachment;
|
||||
SharedPtr<MG_State::GLState::ITextureObject> depthAttachment;
|
||||
SharedPtr<MG_State::GLState::ITextureObject> stencilAttachment;
|
||||
};
|
||||
|
||||
extern DefaultFramebufferInfo* pDefaultFramebufferInfo;
|
||||
} // namespace FramebufferImpl
|
||||
} // namespace MG_Impl::GLImpl
|
||||
} // namespace MobileGL
|
||||
extern UniquePtr<DefaultFramebufferInfo> pDefaultFramebufferInfo;
|
||||
} // namespace FramebufferImpl
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
@@ -13,85 +13,88 @@
|
||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToStr/FramebufferEnumConverter.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace FramebufferImpl {
|
||||
Bool ValidateFramebufferTarget(FramebufferTarget target) {
|
||||
if (target == FramebufferTarget::Unknown) {
|
||||
using namespace MG_Util;
|
||||
String bufferTargetStr = ConvertFramebufferTargetToString(target);
|
||||
String glTargetStr = ConvertGLEnumToString(ConvertFramebufferTargetToGLEnum(target));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeShared<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferTarget",
|
||||
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateFramebufferName(Uint index, Bool allowZero) {
|
||||
if (index == 0 && !allowZero) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferName",
|
||||
"Framebuffer name 0 is not valid in this situation."));
|
||||
return false;
|
||||
}
|
||||
Bool isValid = MG_State::pGLContext->ValidateFramebufferName(index);
|
||||
if (isValid) return true;
|
||||
namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
||||
Bool ValidateFramebufferTarget(FramebufferTarget target) {
|
||||
if (target == FramebufferTarget::Unknown) {
|
||||
using namespace MG_Util;
|
||||
String bufferTargetStr = ConvertFramebufferTargetToString(target);
|
||||
String glTargetStr = ConvertGLEnumToString(ConvertFramebufferTargetToGLEnum(target));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferName",
|
||||
std::format("Framebuffer name {} is not valid.", index)));
|
||||
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferTarget",
|
||||
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment) {
|
||||
if (attachment == FramebufferAttachmentType::Unknown) {
|
||||
using namespace MG_Util;
|
||||
String attachmentStr = ConvertFramebufferAttachmentTypeToString(attachment);
|
||||
String glAttachmentStr = ConvertGLEnumToString(ConvertFramebufferAttachmentTypeToGLEnum(attachment));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeShared<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferAttachmentType",
|
||||
std::format("Attachment type {} ({}) is not valid.", attachmentStr, glAttachmentStr)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
|
||||
if (target == RenderbufferTarget::Unknown) {
|
||||
using namespace MG_Util;
|
||||
String renderbufferTargetStr = ConvertRenderbufferTargetToString(target);
|
||||
String glTargetStr = ConvertGLEnumToString(ConvertRenderbufferTargetToGLEnum(target));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeShared<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferTarget",
|
||||
std::format("Target {} ({}) is not valid.", renderbufferTargetStr, glTargetStr)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero) {
|
||||
if (index == 0 && !allowZero) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
||||
"Renderbuffer name 0 is not valid in this situation."));
|
||||
return false;
|
||||
}
|
||||
Bool isValid = MG_State::pGLContext->ValidateRenderbufferName(index);
|
||||
if (isValid) return true;
|
||||
Bool ValidateFramebufferName(Uint index, Bool allowZero) {
|
||||
if (index == 0 && !allowZero) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
||||
std::format("Renderbuffer name {} is not valid.", index)));
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferName",
|
||||
"Framebuffer name 0 is not valid in this situation."));
|
||||
return false;
|
||||
}
|
||||
} // namespace FramebufferImpl
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
Bool isValid = MG_State::pGLContext->ValidateFramebufferName(index);
|
||||
if (isValid) return true;
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferName",
|
||||
std::format("Framebuffer name {} is not valid.", index)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment) {
|
||||
if (attachment == FramebufferAttachmentType::Unknown) {
|
||||
using namespace MG_Util;
|
||||
String attachmentStr = ConvertFramebufferAttachmentTypeToString(attachment);
|
||||
String glAttachmentStr = ConvertGLEnumToString(ConvertFramebufferAttachmentTypeToGLEnum(attachment));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferAttachmentType",
|
||||
std::format("Attachment type {} ({}) is not valid.", attachmentStr, glAttachmentStr)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
|
||||
if (target == RenderbufferTarget::Unknown) {
|
||||
using namespace MG_Util;
|
||||
String renderbufferTargetStr = ConvertRenderbufferTargetToString(target);
|
||||
String glTargetStr = ConvertGLEnumToString(ConvertRenderbufferTargetToGLEnum(target));
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferTarget",
|
||||
std::format("Target {} ({}) is not valid.", renderbufferTargetStr, glTargetStr)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero) {
|
||||
if (index == 0) {
|
||||
// Zero is never a GenRenderbuffers name, so it must not reach the name-table lookup
|
||||
// below: where it is allowed (glBindRenderbuffer / FramebufferRenderbuffer detach) it
|
||||
// means "unbind", and looking it up would record a bogus INVALID_OPERATION - GL CTS's
|
||||
// per-case state reset calls glBindRenderbuffer(GL_RENDERBUFFER, 0) after every case.
|
||||
if (allowZero) return true;
|
||||
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
||||
"Renderbuffer name 0 is not valid in this situation."));
|
||||
return false;
|
||||
}
|
||||
Bool isValid = MG_State::pGLContext->ValidateRenderbufferName(index);
|
||||
if (isValid) return true;
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
||||
std::format("Renderbuffer name {} is not valid.", index)));
|
||||
return false;
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
#include <Includes.h>
|
||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace FramebufferImpl {
|
||||
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
||||
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
|
||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
||||
} // namespace FramebufferImpl
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
||||
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
||||
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
|
||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
const GLubyte* GetString(GLenum name);
|
||||
const GLubyte* GetStringi(GLenum name, GLuint index);
|
||||
void GetBooleanv(GLenum pname, GLboolean* params);
|
||||
void GetFloatv(GLenum pname, GLfloat* params);
|
||||
void GetDoublev(GLenum pname, GLdouble* params);
|
||||
void GetIntegerv(GLenum pname, GLint* params);
|
||||
void GetInteger64v(GLenum pname, GLint64* params);
|
||||
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||
void GetInteger64i_v(GLenum target, GLuint index, GLint64* data);
|
||||
GLenum GetError();
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,61 +9,133 @@
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Impl::GLImpl {
|
||||
void AttachShader(GLuint program, GLuint shader);
|
||||
void BindAttribLocation(GLuint program, GLuint index, const GLchar* name);
|
||||
void CompileShader(GLuint shader);
|
||||
GLuint CreateProgram(void);
|
||||
GLuint CreateShader(GLenum type);
|
||||
void DeleteProgram(GLuint program);
|
||||
void DeleteShader(GLuint shader);
|
||||
void DetachShader(GLuint program, GLuint shader);
|
||||
void GetActiveAttrib(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type,
|
||||
GLchar* name);
|
||||
void GetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type,
|
||||
GLchar* name);
|
||||
void GetAttachedShaders(GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders);
|
||||
GLint GetAttribLocation(GLuint program, const GLchar* name);
|
||||
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||
void GetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
|
||||
void GetShaderiv(GLuint shader, GLenum pname, GLint* params);
|
||||
void GetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
|
||||
void GetShaderSource(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* source);
|
||||
GLint GetUniformLocation(GLuint program, const GLchar* name);
|
||||
void GetUniformfv(GLuint program, GLint location, GLfloat* params);
|
||||
void GetUniformiv(GLuint program, GLint location, GLint* params);
|
||||
GLboolean IsProgram(GLuint program);
|
||||
GLboolean IsShader(GLuint shader);
|
||||
void LinkProgram(GLuint program);
|
||||
void ShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length);
|
||||
void UseProgram(GLuint program);
|
||||
void Uniform1f(GLint location, GLfloat v0);
|
||||
void Uniform2f(GLint location, GLfloat v0, GLfloat v1);
|
||||
void Uniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
|
||||
void Uniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
||||
void Uniform1i(GLint location, GLint v0);
|
||||
void Uniform2i(GLint location, GLint v0, GLint v1);
|
||||
void Uniform3i(GLint location, GLint v0, GLint v1, GLint v2);
|
||||
void Uniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
|
||||
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform2fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform3fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform4fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform1iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform2iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform3iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform4iv(GLint location, GLsizei count, const GLint* value);
|
||||
void UniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
GLuint GetUniformBlockIndex(GLuint program, const GLchar* uniformBlockName);
|
||||
void UniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
|
||||
void GetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params);
|
||||
void GetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length,
|
||||
GLchar* uniformBlockName);
|
||||
void BindFragDataLocation(GLuint program, GLuint colorNumber, const char* name);
|
||||
GLint GetFragDataLocation(GLuint program, const char* name);
|
||||
void ValidateProgram(GLuint program);
|
||||
} // namespace MG_Impl::GLImpl
|
||||
} // namespace MobileGL
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
void AttachShader(GLuint program, GLuint shader);
|
||||
void BindAttribLocation(GLuint program, GLuint index, const GLchar* name);
|
||||
void CompileShader(GLuint shader);
|
||||
GLuint CreateProgram(void);
|
||||
GLuint CreateShader(GLenum type);
|
||||
void DeleteProgram(GLuint program);
|
||||
void DeleteShader(GLuint shader);
|
||||
void DetachShader(GLuint program, GLuint shader);
|
||||
void GetActiveAttrib(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type,
|
||||
GLchar* name);
|
||||
void GetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type,
|
||||
GLchar* name);
|
||||
void GetActiveUniformName(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length,
|
||||
GLchar* uniformName);
|
||||
void GetUniformIndices(GLuint program, GLsizei uniformCount, const GLchar* const* uniformNames,
|
||||
GLuint* uniformIndices);
|
||||
void GetActiveUniformsiv(GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname,
|
||||
GLint* params);
|
||||
void GetAttachedShaders(GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders);
|
||||
GLint GetAttribLocation(GLuint program, const GLchar* name);
|
||||
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||
void GetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
|
||||
void GetShaderiv(GLuint shader, GLenum pname, GLint* params);
|
||||
void GetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
|
||||
void GetShaderSource(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* source);
|
||||
GLint GetUniformLocation(GLuint program, const GLchar* name);
|
||||
void GetUniformfv(GLuint program, GLint location, GLfloat* params);
|
||||
void GetUniformiv(GLuint program, GLint location, GLint* params);
|
||||
void GetUniformuiv(GLuint program, GLint location, GLuint* params);
|
||||
GLboolean IsProgram(GLuint program);
|
||||
GLboolean IsShader(GLuint shader);
|
||||
void LinkProgram(GLuint program);
|
||||
void ShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length);
|
||||
void UseProgram(GLuint program);
|
||||
void Uniform1f(GLint location, GLfloat v0);
|
||||
void Uniform2f(GLint location, GLfloat v0, GLfloat v1);
|
||||
void Uniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
|
||||
void Uniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
||||
void Uniform1i(GLint location, GLint v0);
|
||||
void Uniform2i(GLint location, GLint v0, GLint v1);
|
||||
void Uniform3i(GLint location, GLint v0, GLint v1, GLint v2);
|
||||
void Uniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
|
||||
void Uniform1ui(GLint location, GLuint v0);
|
||||
void Uniform2ui(GLint location, GLuint v0, GLuint v1);
|
||||
void Uniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
|
||||
void Uniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
|
||||
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform2fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform3fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform4fv(GLint location, GLsizei count, const GLfloat* value);
|
||||
void Uniform1iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform2iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform3iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform4iv(GLint location, GLsizei count, const GLint* value);
|
||||
void Uniform1uiv(GLint location, GLsizei count, const GLuint* value);
|
||||
void Uniform2uiv(GLint location, GLsizei count, const GLuint* value);
|
||||
void Uniform3uiv(GLint location, GLsizei count, const GLuint* value);
|
||||
void Uniform4uiv(GLint location, GLsizei count, const GLuint* value);
|
||||
void UniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void ProgramUniform1f(GLuint program, GLint location, GLfloat v0);
|
||||
void ProgramUniform2f(GLuint program, GLint location, GLfloat v0, GLfloat v1);
|
||||
void ProgramUniform3f(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
|
||||
void ProgramUniform4f(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
||||
void ProgramUniform1i(GLuint program, GLint location, GLint v0);
|
||||
void ProgramUniform2i(GLuint program, GLint location, GLint v0, GLint v1);
|
||||
void ProgramUniform3i(GLuint program, GLint location, GLint v0, GLint v1, GLint v2);
|
||||
void ProgramUniform4i(GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
|
||||
void ProgramUniform1ui(GLuint program, GLint location, GLuint v0);
|
||||
void ProgramUniform2ui(GLuint program, GLint location, GLuint v0, GLuint v1);
|
||||
void ProgramUniform3ui(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2);
|
||||
void ProgramUniform4ui(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
|
||||
void ProgramUniform1fv(GLuint program, GLint location, GLsizei count, const GLfloat* value);
|
||||
void ProgramUniform2fv(GLuint program, GLint location, GLsizei count, const GLfloat* value);
|
||||
void ProgramUniform3fv(GLuint program, GLint location, GLsizei count, const GLfloat* value);
|
||||
void ProgramUniform4fv(GLuint program, GLint location, GLsizei count, const GLfloat* value);
|
||||
void ProgramUniform1iv(GLuint program, GLint location, GLsizei count, const GLint* value);
|
||||
void ProgramUniform2iv(GLuint program, GLint location, GLsizei count, const GLint* value);
|
||||
void ProgramUniform3iv(GLuint program, GLint location, GLsizei count, const GLint* value);
|
||||
void ProgramUniform4iv(GLuint program, GLint location, GLsizei count, const GLint* value);
|
||||
void ProgramUniform1uiv(GLuint program, GLint location, GLsizei count, const GLuint* value);
|
||||
void ProgramUniform2uiv(GLuint program, GLint location, GLsizei count, const GLuint* value);
|
||||
void ProgramUniform3uiv(GLuint program, GLint location, GLsizei count, const GLuint* value);
|
||||
void ProgramUniform4uiv(GLuint program, GLint location, GLsizei count, const GLuint* value);
|
||||
void ProgramUniformMatrix2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
void ProgramUniformMatrix3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
void ProgramUniformMatrix4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
void UniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void UniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||
void ProgramUniformMatrix2x3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
void ProgramUniformMatrix3x2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
void ProgramUniformMatrix2x4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
void ProgramUniformMatrix4x2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
void ProgramUniformMatrix3x4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
void ProgramUniformMatrix4x3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLfloat* value);
|
||||
GLuint GetUniformBlockIndex(GLuint program, const GLchar* uniformBlockName);
|
||||
void UniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
|
||||
void GetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params);
|
||||
void GetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length,
|
||||
GLchar* uniformBlockName);
|
||||
void BindFragDataLocation(GLuint program, GLuint colorNumber, const char* name);
|
||||
void BindFragDataLocationIndexed(GLuint program, GLuint colorNumber, GLuint index, const char* name);
|
||||
GLint GetFragDataLocation(GLuint program, const char* name);
|
||||
GLint GetFragDataIndex(GLuint program, const char* name);
|
||||
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
||||
GLsizei* length, GLchar* name);
|
||||
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||
void ValidateProgram(GLuint program);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
// MobileGL - MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "GL_Query.h"
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/ErrorState/ErrorInfo.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace {
|
||||
// Frontend query object (GL_ARB_timer_query): wraps an optional backend
|
||||
// timer-query handle. A null backend handle (backend has no timer-query
|
||||
// support, timer queries are disabled by config, or the backend could
|
||||
// not create a query at call time) keeps a graceful fallback: the query
|
||||
// result is immediately available and reads as zero.
|
||||
struct QueryObject {
|
||||
GLuint id = 0;
|
||||
GLenum target = 0; // 0 = gen'd but never used with BeginQuery/QueryCounter
|
||||
MG_Backend::BackendQueryHandle backendHandle = nullptr;
|
||||
Bool active = false;
|
||||
Bool ended = false;
|
||||
Bool resultCached = false;
|
||||
Uint64 cachedResult = 0;
|
||||
};
|
||||
|
||||
// Query calls may arrive from any thread (launchers migrate the context
|
||||
// across JVM threads), so the live-object registry is mutex-guarded,
|
||||
// like the sync-object registry in GL_Sync.cpp. Entries left at process
|
||||
// shutdown are simply dropped; their backend handles die with the
|
||||
// backend.
|
||||
std::mutex g_queryObjectsMutex;
|
||||
UnorderedMap<GLuint, QueryObject*> g_liveQueryObjects;
|
||||
// Monotonically increasing id allocator; ids are valid query objects
|
||||
// immediately after GenQueries.
|
||||
GLuint g_nextQueryId = 1;
|
||||
// Id of the query currently active on GL_TIME_ELAPSED (0 = none).
|
||||
GLuint g_activeTimeElapsedQueryId = 0;
|
||||
|
||||
Bool TimerQueryDisabled() {
|
||||
return MG_Config::Features.DisableTimerQuery;
|
||||
}
|
||||
|
||||
void RecordQueryError(ErrorCode code, const char* function, const char* message) {
|
||||
MG_State::pGLContext->RecordError(code,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", function, message));
|
||||
}
|
||||
|
||||
// Callers must hold g_queryObjectsMutex.
|
||||
QueryObject* FindQueryObjectLocked(GLuint id) {
|
||||
const auto it = g_liveQueryObjects.find(id);
|
||||
return it != g_liveQueryObjects.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
// Callers must hold g_queryObjectsMutex. Releases the backend handle
|
||||
// (if any) and clears any cached result, so the object can be reused.
|
||||
void ResetQueryObjectLocked(QueryObject* queryObject) {
|
||||
if (queryObject->backendHandle) {
|
||||
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||
deleteBackendQuery(queryObject->backendHandle);
|
||||
}
|
||||
queryObject->backendHandle = nullptr;
|
||||
}
|
||||
queryObject->active = false;
|
||||
queryObject->ended = false;
|
||||
queryObject->resultCached = false;
|
||||
queryObject->cachedResult = 0;
|
||||
}
|
||||
|
||||
// Callers must hold g_queryObjectsMutex.
|
||||
void EndTimeElapsedQueryLocked(QueryObject* queryObject) {
|
||||
const auto endTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.EndTimeElapsedQuery;
|
||||
if (endTimeElapsedQuery && queryObject->backendHandle) {
|
||||
endTimeElapsedQuery(queryObject->backendHandle);
|
||||
}
|
||||
queryObject->active = false;
|
||||
queryObject->ended = true;
|
||||
g_activeTimeElapsedQueryId = 0;
|
||||
}
|
||||
|
||||
// Shared GetQueryObject* implementation. Returns false when an error
|
||||
// was recorded and no value should be written back.
|
||||
Bool GetQueryObjectValue(GLuint id, GLenum pname, const char* function, Uint64& outValue) {
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
auto* queryObject = FindQueryObjectLocked(id);
|
||||
if (!queryObject) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, function, "Query object does not exist.");
|
||||
return false;
|
||||
}
|
||||
if (queryObject->active) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, function, "Query object is still active.");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (pname) {
|
||||
case GL_QUERY_RESULT_AVAILABLE: {
|
||||
if (queryObject->resultCached || !queryObject->backendHandle) {
|
||||
outValue = 1;
|
||||
return true;
|
||||
}
|
||||
const auto isQueryResultAvailable = MG_Backend::gBackendFunctionsTable.GL.IsQueryResultAvailable;
|
||||
outValue = (!isQueryResultAvailable || isQueryResultAvailable(queryObject->backendHandle)) ? 1 : 0;
|
||||
return true;
|
||||
}
|
||||
case GL_QUERY_RESULT: {
|
||||
if (queryObject->resultCached) {
|
||||
outValue = queryObject->cachedResult;
|
||||
return true;
|
||||
}
|
||||
Uint64 result = 0;
|
||||
if (queryObject->backendHandle) {
|
||||
const auto getQueryResult64 = MG_Backend::gBackendFunctionsTable.GL.GetQueryResult64;
|
||||
if (getQueryResult64 &&
|
||||
!getQueryResult64(queryObject->backendHandle, /*wait=*/true, &result)) {
|
||||
// The backend could not produce the result YET (e.g. a
|
||||
// Vulkan wait refusing to block on a not-yet-submitted
|
||||
// frame serial). Per the documented no-stall tradeoff
|
||||
// this call reads 0, but the value is NOT cached and
|
||||
// the backend handle is kept, so a later AVAILABLE
|
||||
// poll / RESULT read still produces the real value.
|
||||
outValue = 0;
|
||||
return true;
|
||||
}
|
||||
// Final value produced (or no GetQueryResult64 hook: the
|
||||
// query degrades to a zero result); the backend handle is
|
||||
// consumed and the value cached for later reads.
|
||||
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||
deleteBackendQuery(queryObject->backendHandle);
|
||||
}
|
||||
queryObject->backendHandle = nullptr;
|
||||
}
|
||||
queryObject->cachedResult = result;
|
||||
queryObject->resultCached = true;
|
||||
outValue = result;
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
RecordQueryError(ErrorCode::InvalidEnum, function, "Unsupported query object parameter.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void GenQueries(GLsizei n, GLuint* ids) {
|
||||
if (n < 0) {
|
||||
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
||||
return;
|
||||
}
|
||||
if (!ids) {
|
||||
return;
|
||||
}
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
const GLuint id = g_nextQueryId++;
|
||||
auto* queryObject = new QueryObject;
|
||||
queryObject->id = id;
|
||||
g_liveQueryObjects[id] = queryObject;
|
||||
ids[i] = id;
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteQueries(GLsizei n, const GLuint* ids) {
|
||||
if (n < 0) {
|
||||
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
||||
return;
|
||||
}
|
||||
if (!ids) {
|
||||
return;
|
||||
}
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
const auto it = g_liveQueryObjects.find(ids[i]);
|
||||
if (it == g_liveQueryObjects.end()) {
|
||||
continue; // unknown ids are silently ignored
|
||||
}
|
||||
QueryObject* queryObject = it->second;
|
||||
if (queryObject->active) {
|
||||
EndTimeElapsedQueryLocked(queryObject); // implicitly end before deletion
|
||||
}
|
||||
if (queryObject->backendHandle) {
|
||||
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||
deleteBackendQuery(queryObject->backendHandle);
|
||||
}
|
||||
queryObject->backendHandle = nullptr;
|
||||
}
|
||||
g_liveQueryObjects.erase(it);
|
||||
delete queryObject;
|
||||
}
|
||||
}
|
||||
|
||||
GLboolean IsQuery(GLuint id) {
|
||||
if (id == 0) {
|
||||
return GL_FALSE;
|
||||
}
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
// Gen'd ids count as query objects here: the registry creates live
|
||||
// objects at GenQueries time.
|
||||
return FindQueryObjectLocked(id) != nullptr ? GL_TRUE : GL_FALSE;
|
||||
}
|
||||
|
||||
void BeginQuery(GLenum target, GLuint id) {
|
||||
if (target != GL_TIME_ELAPSED) {
|
||||
// Only GL_TIME_ELAPSED timer queries are implemented (occlusion and
|
||||
// primitive queries remain stubs); GL_TIMESTAMP is not a valid
|
||||
// BeginQuery target either.
|
||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||
return;
|
||||
}
|
||||
if (id == 0) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query id 0 cannot be used.");
|
||||
return;
|
||||
}
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
auto* queryObject = FindQueryObjectLocked(id);
|
||||
if (!queryObject) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
||||
return;
|
||||
}
|
||||
if (g_activeTimeElapsedQueryId != 0) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||
"A query is already active on GL_TIME_ELAPSED.");
|
||||
return;
|
||||
}
|
||||
if (queryObject->active) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object is already active.");
|
||||
return;
|
||||
}
|
||||
if (queryObject->target != 0 && queryObject->target != target) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||
"Query object was already used with a different target.");
|
||||
return;
|
||||
}
|
||||
|
||||
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||
queryObject->target = target;
|
||||
queryObject->active = true;
|
||||
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
||||
queryObject->backendHandle =
|
||||
(!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr;
|
||||
g_activeTimeElapsedQueryId = id;
|
||||
}
|
||||
|
||||
void EndQuery(GLenum target) {
|
||||
if (target != GL_TIME_ELAPSED) {
|
||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||
return;
|
||||
}
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
if (g_activeTimeElapsedQueryId == 0) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on GL_TIME_ELAPSED.");
|
||||
return;
|
||||
}
|
||||
auto* queryObject = FindQueryObjectLocked(g_activeTimeElapsedQueryId);
|
||||
if (!queryObject) {
|
||||
g_activeTimeElapsedQueryId = 0; // should not happen; keep state consistent
|
||||
return;
|
||||
}
|
||||
EndTimeElapsedQueryLocked(queryObject);
|
||||
}
|
||||
|
||||
void QueryCounter(GLuint id, GLenum target) {
|
||||
if (target != GL_TIMESTAMP) {
|
||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "QueryCounter target must be GL_TIMESTAMP.");
|
||||
return;
|
||||
}
|
||||
if (id == 0) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query id 0 cannot be used.");
|
||||
return;
|
||||
}
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
auto* queryObject = FindQueryObjectLocked(id);
|
||||
if (!queryObject) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
||||
return;
|
||||
}
|
||||
if (queryObject->active) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object is currently active.");
|
||||
return;
|
||||
}
|
||||
if (queryObject->target != 0 && queryObject->target != target) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||
"Query object was already used with a different target.");
|
||||
return;
|
||||
}
|
||||
|
||||
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||
queryObject->target = target;
|
||||
const auto queryCounterTimestamp = MG_Backend::gBackendFunctionsTable.GL.QueryCounterTimestamp;
|
||||
queryObject->backendHandle =
|
||||
(!TimerQueryDisabled() && queryCounterTimestamp) ? queryCounterTimestamp() : nullptr;
|
||||
queryObject->ended = true;
|
||||
}
|
||||
|
||||
void GetQueryiv(GLenum target, GLenum pname, GLint* params) {
|
||||
if (!params) {
|
||||
return;
|
||||
}
|
||||
switch (pname) {
|
||||
case GL_CURRENT_QUERY: {
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
// Only GL_TIME_ELAPSED queries can be active; GL_TIMESTAMP queries
|
||||
// never are, and other targets remain unimplemented.
|
||||
*params = target == GL_TIME_ELAPSED ? static_cast<GLint>(g_activeTimeElapsedQueryId) : 0;
|
||||
return;
|
||||
}
|
||||
case GL_QUERY_COUNTER_BITS: {
|
||||
// 64 bits are advertised only while the live backend can actually
|
||||
// time: IsTimerQuerySupported is the dynamic truth (extension /
|
||||
// entry points / timestamp valid bits at call time, not at table
|
||||
// init), and the MOBILEGL_DISABLE_TIMERQUERY kill switch always
|
||||
// wins. Non-timer targets remain unimplemented and report 0.
|
||||
const Bool timerTarget = target == GL_TIME_ELAPSED || target == GL_TIMESTAMP;
|
||||
const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported;
|
||||
const Bool supported =
|
||||
timerTarget && !TimerQueryDisabled() && isTimerQuerySupported && isTimerQuerySupported();
|
||||
*params = supported ? 64 : 0;
|
||||
return;
|
||||
}
|
||||
default:
|
||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Unsupported query parameter.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params) {
|
||||
Uint64 value = 0;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||
return;
|
||||
}
|
||||
constexpr Uint64 kMaxInt = static_cast<Uint64>(INT_MAX);
|
||||
*params = value > kMaxInt ? INT_MAX : static_cast<GLint>(value);
|
||||
}
|
||||
|
||||
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params) {
|
||||
Uint64 value = 0;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||
return;
|
||||
}
|
||||
*params = static_cast<GLuint>(value & 0xFFFFFFFFull);
|
||||
}
|
||||
|
||||
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params) {
|
||||
Uint64 value = 0;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||
return;
|
||||
}
|
||||
*params = static_cast<GLint64>(value);
|
||||
}
|
||||
|
||||
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) {
|
||||
Uint64 value = 0;
|
||||
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||
return;
|
||||
}
|
||||
*params = static_cast<GLuint64>(value);
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
@@ -0,0 +1,24 @@
|
||||
// MobileGL - MobileGL/MG_Impl/GLImpl/Query/GL_Query.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
void GenQueries(GLsizei n, GLuint* ids);
|
||||
void DeleteQueries(GLsizei n, const GLuint* ids);
|
||||
GLboolean IsQuery(GLuint id);
|
||||
void BeginQuery(GLenum target, GLuint id);
|
||||
void EndQuery(GLenum target);
|
||||
void GetQueryiv(GLenum target, GLenum pname, GLint* params);
|
||||
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params);
|
||||
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params);
|
||||
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params);
|
||||
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params);
|
||||
void QueryCounter(GLuint id, GLenum target);
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user