mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
Compare commits
683
Commits
Feat/EGL
...
piglit-gl33
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4322427e78 | ||
|
|
203d4bce5e | ||
|
|
761114d022 | ||
|
|
9caf34d5b1 | ||
|
|
5e676b338b | ||
|
|
db01bfa3e8 | ||
|
|
cc3dcfd80e | ||
|
|
c8632dfefe | ||
|
|
d9556ff041 | ||
|
|
b8a8a660e1 | ||
|
|
39f21e52ea | ||
|
|
0e933b8f2f | ||
|
|
7ab83861ca | ||
|
|
eaeba556a3 | ||
|
|
72fa1221a5 | ||
|
|
c4254c4bbd | ||
|
|
199164c2e0 | ||
|
|
e87063e90c | ||
|
|
122da27249 | ||
|
|
bc2d698b3e | ||
|
|
3049c4b82b | ||
|
|
2b3850b76b | ||
|
|
2a0ae743a0 | ||
|
|
6839219c10 | ||
|
|
52ddb440ca | ||
|
|
79feeffd25 | ||
|
|
202037b5a3 | ||
|
|
bce9c48c8e | ||
|
|
b6a7807a3a | ||
|
|
48ba622387 | ||
|
|
05260d1262 | ||
|
|
6eb5ff51c5 | ||
|
|
e526f8e8ac | ||
|
|
e724e88eec | ||
|
|
3b175fb88a | ||
|
|
b5a4e7075a | ||
|
|
520c2b6750 | ||
|
|
57cc652b1d | ||
|
|
65ea54da9e | ||
|
|
c81dd04f08 | ||
|
|
c158bfa584 | ||
|
|
f9f455144c | ||
|
|
64e4840de2 | ||
|
|
bf7b5755cc | ||
|
|
293f64b3c2 | ||
|
|
f0cc07c937 | ||
|
|
4658536652 | ||
|
|
04b4627c65 | ||
|
|
68e13705c8 | ||
|
|
e5388c0e7e | ||
|
|
8bc4808b1a | ||
|
|
5d8a5387e2 | ||
|
|
aa2184e47a | ||
|
|
9152a4a4bc | ||
|
|
1963b427db | ||
|
|
f3def150e7 | ||
|
|
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-inaccurate-erase,
|
||||||
bugprone-incorrect-roundings,
|
bugprone-incorrect-roundings,
|
||||||
bugprone-integer-division,
|
bugprone-integer-division,
|
||||||
bugprone-lambda-function-name,
|
|
||||||
bugprone-macro-parentheses,
|
bugprone-macro-parentheses,
|
||||||
bugprone-macro-repeated-side-effects,
|
bugprone-macro-repeated-side-effects,
|
||||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||||
@@ -63,7 +62,6 @@ cert-str34-c,
|
|||||||
cppcoreguidelines-interfaces-global-init,
|
cppcoreguidelines-interfaces-global-init,
|
||||||
cppcoreguidelines-narrowing-conversions,
|
cppcoreguidelines-narrowing-conversions,
|
||||||
cppcoreguidelines-pro-type-member-init,
|
cppcoreguidelines-pro-type-member-init,
|
||||||
cppcoreguidelines-pro-type-static-cast-downcast,
|
|
||||||
cppcoreguidelines-slicing,
|
cppcoreguidelines-slicing,
|
||||||
google-default-arguments,
|
google-default-arguments,
|
||||||
google-runtime-operator,
|
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,239 @@
|
|||||||
|
#!/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}"
|
||||||
|
# Fixture mirrors, tried in order before falling back to Git LFS. Override the
|
||||||
|
# whole list with MOBILEGL_TRACE_FIXTURE_MIRROR_BASES (whitespace separated);
|
||||||
|
# MOBILEGL_TRACE_FIXTURE_MIRROR_BASE still works and is tried first.
|
||||||
|
default_mirror_bases=(
|
||||||
|
"https://git.hit.moe/swung0x48/MobileGL/media/branch/dev/tools/trace_replay/fixtures"
|
||||||
|
"https://repo.miawa.cn/mgl/tools/trace_replay/fixtures"
|
||||||
|
)
|
||||||
|
if [ -n "${MOBILEGL_TRACE_FIXTURE_MIRROR_BASES:-}" ]; then
|
||||||
|
read -r -a mirror_bases <<< "${MOBILEGL_TRACE_FIXTURE_MIRROR_BASES}"
|
||||||
|
else
|
||||||
|
mirror_bases=("${default_mirror_bases[@]}")
|
||||||
|
fi
|
||||||
|
if [ -n "${MOBILEGL_TRACE_FIXTURE_MIRROR_BASE:-}" ]; then
|
||||||
|
mirror_bases=("${MOBILEGL_TRACE_FIXTURE_MIRROR_BASE}" "${mirror_bases[@]}")
|
||||||
|
fi
|
||||||
|
# Optional bearer token for mirrors that require authentication (private Gitea).
|
||||||
|
mirror_token="${MOBILEGL_TRACE_FIXTURE_MIRROR_TOKEN:-}"
|
||||||
|
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}")"
|
||||||
|
# Strip CR so the script also works when python emits CRLF (Git Bash on Windows).
|
||||||
|
mapfile -t files < <(printf '%s\n' "${fixture_list}" | tr -d '\r')
|
||||||
|
|
||||||
|
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
|
||||||
|
local curl_auth
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
curl_auth=()
|
||||||
|
if [ -n "${mirror_token}" ]; then
|
||||||
|
curl_auth=(--header "Authorization: token ${mirror_token}")
|
||||||
|
fi
|
||||||
|
if curl -L --fail --show-error --continue-at - "${curl_auth[@]}" --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
|
||||||
|
local base
|
||||||
|
local fetched=0
|
||||||
|
name="$(basename "${file}")"
|
||||||
|
for base in "${mirror_bases[@]}"; do
|
||||||
|
url="${base%/}/${name}"
|
||||||
|
echo "Fetching trace fixture from mirror: ${url}"
|
||||||
|
if fetch_file_from_mirror "${file}" "${url}"; then
|
||||||
|
fetched=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Mirror did not serve ${name}; trying the next mirror" >&2
|
||||||
|
done
|
||||||
|
if [ "${fetched}" -ne 1 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if fetch_from_mirror; then
|
||||||
|
echo "Fetched trace fixture files for ${case_name} from mirror: ${include}"
|
||||||
|
else
|
||||||
|
echo "All mirrors 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
|
|
||||||
+518
-23
@@ -6,27 +6,41 @@ on:
|
|||||||
- dev
|
- dev
|
||||||
- Feat/Backend-Direct-GLES
|
- Feat/Backend-Direct-GLES
|
||||||
- Feat/Backend-Direct-Vulkan
|
- Feat/Backend-Direct-Vulkan
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
# TEST_ROOT: ${{github.workspace}}/MobileGL/MG_Test
|
BUILD_DIR: build-linux
|
||||||
TEST_ROOT: ${{github.workspace}}
|
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||||
|
CCACHE_COMPRESS: "true"
|
||||||
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
CCACHE_MAXSIZE: 4G
|
||||||
|
CCACHE_NOHASHDIR: "true"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set Swap Space
|
- name: Set Swap Space
|
||||||
uses: pierotofy/set-swap-space@master
|
uses: pierotofy/set-swap-space@v1.0
|
||||||
with:
|
with:
|
||||||
swap-size-gb: 32
|
swap-size-gb: 32
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: recursive
|
||||||
|
|
||||||
- name: Get CMake
|
- 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
|
- name: Prepare Vulkan SDK
|
||||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
||||||
@@ -36,39 +50,520 @@ jobs:
|
|||||||
vulkan-use-cache: true
|
vulkan-use-cache: true
|
||||||
|
|
||||||
- name: Update glslang external sources
|
- name: Update glslang external sources
|
||||||
working-directory: ${{env.TEST_ROOT}}/3rdparty/glslang
|
working-directory: 3rdparty/glslang
|
||||||
run: python update_glslang_sources.py
|
run: python update_glslang_sources.py
|
||||||
|
|
||||||
- name: Install clang-20
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
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
|
- name: Show installed toolchain
|
||||||
run: |
|
run: |
|
||||||
|
ccache --version
|
||||||
clang-20 --version
|
clang-20 --version
|
||||||
clang++-20 --version
|
clang++-20 --version
|
||||||
ld.lld-20 --version || ld.lld --version || true
|
ld.lld-20 --version || ld.lld --version || true
|
||||||
dpkg -l 'libc++*' || true
|
dpkg -l 'libc++*' 'libegl*' 'libgles*' 'mesa*' 'vulkan*' || true
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
working-directory: ${{env.TEST_ROOT}}
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" == "true" ]; then
|
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
|
BUILD_TYPE=Debug
|
||||||
else
|
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
|
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
|
- name: Build
|
||||||
working-directory: ${{env.TEST_ROOT}}/build-test
|
run: cmake --build "${BUILD_DIR}" --parallel "$(nproc)"
|
||||||
run: cmake --build .
|
|
||||||
|
- 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
|
- name: Test
|
||||||
working-directory: ${{env.TEST_ROOT}}/build-test/MobileGL/MG_Test
|
working-directory: build-linux
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" == "true" ]; then
|
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then
|
||||||
ctest -V
|
ctest -V -L unit --no-tests=error
|
||||||
else
|
else
|
||||||
ctest
|
ctest --output-on-failure -L unit --no-tests=error
|
||||||
fi
|
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
|
||||||
|
# The blended depth-write quirk auto-enables only on Qualcomm, which no CI
|
||||||
|
# runner has, so force it on for the OIT case it exists to fix. ForceOn
|
||||||
|
# bypasses only the vendor gate, so this exercises the real strip on
|
||||||
|
# lavapipe. The Android AVD lane deliberately leaves it off, keeping the
|
||||||
|
# unstripped path covered for the same trace.
|
||||||
|
if [ '${{ matrix.backend }}' = 'DirectVulkan' ] \
|
||||||
|
&& [ '${{ matrix.case }}' = 'improved-transparency-minecraft-26.3' ]; then
|
||||||
|
export MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE=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*
|
/cmake-build*
|
||||||
.idea
|
.idea
|
||||||
MobileGL/MG*/build*
|
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"]
|
[submodule "3rdparty/VulkanMemoryAllocator"]
|
||||||
path = 3rdparty/VulkanMemoryAllocator
|
path = 3rdparty/VulkanMemoryAllocator
|
||||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
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
+156
-5
@@ -6,6 +6,11 @@ option(MOBILEGL_BUILD_TEST "Build MobileGL tests"
|
|||||||
option(MOBILEGL_BUILD_BENCHMARK "Build MobileGL benchmarks" ON )
|
option(MOBILEGL_BUILD_BENCHMARK "Build MobileGL benchmarks" ON )
|
||||||
option(MOBILEGL_FORCE_RELEASE_OPT "Enable Release optimization flags in Debug build" 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_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)
|
if (ANDROID)
|
||||||
set(MOBILEGL_BUILD_TEST OFF CACHE BOOL "Build MobileGL tests" FORCE)
|
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_CLI OFF CACHE BOOL "Disable CLI binary" FORCE)
|
||||||
set(SPIRV_CROSS_STATIC ON CACHE BOOL "Prefer static libs" 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/DiligentCore)
|
||||||
add_subdirectory(3rdparty/glslang)
|
add_subdirectory(3rdparty/glslang)
|
||||||
add_subdirectory(3rdparty/SPIRV-Cross)
|
add_subdirectory(3rdparty/SPIRV-Cross)
|
||||||
add_subdirectory(3rdparty/VulkanMemoryAllocator)
|
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)
|
set(XXHASH_BUILD_XXHSUM OFF)
|
||||||
option(BUILD_SHARED_LIBS 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/RenderStateEnumConverter.cpp
|
||||||
MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp
|
MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp
|
||||||
MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.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
|
MobileGL/MG_Util/Classifiers/TextureEnumClassifier.cpp
|
||||||
|
|
||||||
@@ -167,11 +184,25 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp
|
MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.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/EliminateFloatEqualsZeroPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.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/ShaderTranspiler/SpirvPasses/StripNoPerspectivePass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateNoPerspectivePass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FoldConstOffsetFor1DFetchPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerClipDistanceForEsslPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DefeatConstStructArrayLutPass.cpp
|
||||||
|
|
||||||
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
||||||
MobileGL/MG_Util/BackendLoaders/Vulkan/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/PixelStoreProcessor.cpp
|
||||||
MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp
|
MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp
|
||||||
|
|
||||||
@@ -199,6 +230,7 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp
|
MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp
|
MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.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/Init.cpp
|
||||||
MobileGL/MG_Impl/GetProcAddress.cpp
|
MobileGL/MG_Impl/GetProcAddress.cpp
|
||||||
@@ -219,13 +251,17 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp
|
MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp
|
||||||
MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp
|
MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp
|
||||||
MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.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/VertexInputStateBuilder.cpp
|
||||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp
|
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp
|
||||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.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/VkRenderPassManager.cpp
|
||||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.cpp
|
|
||||||
|
|
||||||
MobileGL/MG_State/GLState/Core.cpp
|
MobileGL/MG_State/GLState/Core.cpp
|
||||||
MobileGL/MG_State/EGLState/Core.cpp
|
MobileGL/MG_State/EGLState/Core.cpp
|
||||||
@@ -255,6 +291,28 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp
|
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()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
list(APPEND SOURCE_FILES
|
||||||
|
MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp
|
||||||
|
MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(MOBILEGL_LINK_LIBRARIES
|
set(MOBILEGL_LINK_LIBRARIES
|
||||||
glslang::glslang
|
glslang::glslang
|
||||||
spirv-cross-c
|
spirv-cross-c
|
||||||
@@ -262,6 +320,8 @@ set(MOBILEGL_LINK_LIBRARIES
|
|||||||
SPIRV-Tools
|
SPIRV-Tools
|
||||||
xxHash::xxhash
|
xxHash::xxhash
|
||||||
GPUOpen::VulkanMemoryAllocator
|
GPUOpen::VulkanMemoryAllocator
|
||||||
|
Vulkan::UtilityHeaders
|
||||||
|
spirv-reflect-static
|
||||||
)
|
)
|
||||||
|
|
||||||
set(MOBILEGL_COMPILE_DEF
|
set(MOBILEGL_COMPILE_DEF
|
||||||
@@ -281,10 +341,18 @@ set(MOBILEGL_INCLUDE_DIR
|
|||||||
${SPIRV-Headers_SOURCE_DIR}/include
|
${SPIRV-Headers_SOURCE_DIR}/include
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
${SOURCE_FILES}
|
${SOURCE_FILES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
# The wgl* entry points are exported via .def (see the comment in wgl.def);
|
||||||
|
# only the shared library links it.
|
||||||
|
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
|
||||||
|
MobileGL/MG_Impl/WGLImpl/Exporting/wgl.def
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES
|
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES
|
||||||
C_VISIBILITY_PRESET default
|
C_VISIBILITY_PRESET default
|
||||||
@@ -311,8 +379,34 @@ target_link_libraries(${CMAKE_PROJECT_NAME}
|
|||||||
target_compile_definitions(${CMAKE_PROJECT_NAME}
|
target_compile_definitions(${CMAKE_PROJECT_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
${MOBILEGL_COMPILE_DEF}
|
${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(WIN32)
|
||||||
|
# Drop-in for the classic GL loader path: a copy named opengl32.dll placed
|
||||||
|
# next to a host executable is what LoadLibrary("opengl32.dll") and gdi32's
|
||||||
|
# pixel-format forwarding will resolve.
|
||||||
|
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_FILE:${CMAKE_PROJECT_NAME}>"
|
||||||
|
"$<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/opengl32.dll"
|
||||||
|
COMMENT "Creating opengl32.dll drop-in copy"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
add_library(${CMAKE_PROJECT_NAME}_s STATIC
|
add_library(${CMAKE_PROJECT_NAME}_s STATIC
|
||||||
${SOURCE_FILES}
|
${SOURCE_FILES}
|
||||||
@@ -344,6 +438,7 @@ if(NOT ANDROID)
|
|||||||
target_compile_definitions(${CMAKE_PROJECT_NAME}_s
|
target_compile_definitions(${CMAKE_PROJECT_NAME}_s
|
||||||
PUBLIC
|
PUBLIC
|
||||||
${MOBILEGL_COMPILE_DEF}
|
${MOBILEGL_COMPILE_DEF}
|
||||||
|
MOBILEGL_LOG_ACTIVE_LEVEL=${MOBILEGL_LOG_ACTIVE_LEVEL}
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -362,7 +457,48 @@ if (ANDROID)
|
|||||||
)
|
)
|
||||||
endif()
|
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)
|
find_package(Vulkan)
|
||||||
if (Vulkan_FOUND)
|
if (Vulkan_FOUND)
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Vulkan::Vulkan Vulkan::Headers)
|
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Vulkan::Vulkan Vulkan::Headers)
|
||||||
@@ -370,6 +506,17 @@ if (NOT ANDROID)
|
|||||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${Vulkan_INCLUDE_DIR})
|
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${Vulkan_INCLUDE_DIR})
|
||||||
target_include_directories(${CMAKE_PROJECT_NAME}_s PUBLIC ${Vulkan_INCLUDE_DIR})
|
target_include_directories(${CMAKE_PROJECT_NAME}_s PUBLIC ${Vulkan_INCLUDE_DIR})
|
||||||
endif ()
|
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)
|
if (MOBILEGL_BUILD_TEST)
|
||||||
add_subdirectory(MobileGL/MG_Test)
|
add_subdirectory(MobileGL/MG_Test)
|
||||||
@@ -378,4 +525,8 @@ if (NOT ANDROID)
|
|||||||
if (MOBILEGL_BUILD_BENCHMARK)
|
if (MOBILEGL_BUILD_BENCHMARK)
|
||||||
add_subdirectory(MobileGL/MG_Benchmark)
|
add_subdirectory(MobileGL/MG_Benchmark)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (MOBILEGL_BUILD_TRACE_REPLAY)
|
||||||
|
add_subdirectory(tools/trace_replay)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
+80
-1
@@ -14,9 +14,88 @@ namespace MobileGL::MG_Config {
|
|||||||
inline const String ProjectName = "MobileGL";
|
inline const String ProjectName = "MobileGL";
|
||||||
inline const String CoreName = "MobileGL Core";
|
inline const String CoreName = "MobileGL Core";
|
||||||
inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)";
|
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 VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
|
||||||
inline const Uint64 CacheVersion = 0;
|
inline const Uint64 CacheVersion = 0;
|
||||||
|
|
||||||
extern BackendType ActiveBackendType;
|
extern BackendType ActiveBackendType;
|
||||||
|
|
||||||
|
// Tri-state override for device-specific quirks: Auto lets the detected device decide,
|
||||||
|
// ForceOn/ForceOff bypass the detection in either direction. ForceOn only bypasses the
|
||||||
|
// device gate - each quirk keeps its structural safety checks.
|
||||||
|
enum class QuirkOverride : Uint8 {
|
||||||
|
Auto = 0,
|
||||||
|
ForceOn,
|
||||||
|
ForceOff,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN: overrides the shader-source quirk that
|
||||||
|
// rewrites the recognized workgroup prefix-scan template on Qualcomm devices with
|
||||||
|
// subgroups wider than 32 lanes (see ShaderSourceProcessor's quirk registry).
|
||||||
|
QuirkOverride SubgroupPrefixScanQuirk = QuirkOverride::Auto;
|
||||||
|
// MOBILEGL_QUIRK_CLIP_DISTANCE: overrides the DirectGLES quirk that lowers
|
||||||
|
// gl_ClipDistance for Adreno's ESSL compiler (shadow Private arrays with
|
||||||
|
// constant-index builtin flushes, dynamic-index gl_in copy loop, redeclaration
|
||||||
|
// strip, and const struct-array LUT splitting). Auto detects Qualcomm.
|
||||||
|
QuirkOverride ClipDistanceQuirk = QuirkOverride::Auto;
|
||||||
|
// MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE: overrides the DirectVulkan quirk that
|
||||||
|
// strips depth writes from accumulation-blended pipelines (MIN/MAX or additive
|
||||||
|
// ONE+ONE - the multi-pass depth-equality signature) on drivers without
|
||||||
|
// cross-pipeline vertex position invariance. Sorted-transparency "over" blends,
|
||||||
|
// gl_FragDepth writers, and fully color-masked attachments are exempt (see
|
||||||
|
// PipelineFactory::ShouldSuppressDepthWrite). Auto detects Qualcomm.
|
||||||
|
QuirkOverride MagmaDisableBlendedDepthWriteQuirk = QuirkOverride::Auto;
|
||||||
|
// MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS: leave the Vulkan robustBufferAccess device
|
||||||
|
// feature off. It is enabled by default to match GL's defined out-of-range fetch
|
||||||
|
// behavior; this escape hatch exists to measure or dodge its GPU cost on a device.
|
||||||
|
Bool DisableRobustBufferAccess = false;
|
||||||
|
};
|
||||||
|
extern FeaturesTable Features;
|
||||||
} // namespace MobileGL::MG_Config
|
} // namespace MobileGL::MG_Config
|
||||||
|
|||||||
@@ -8,6 +8,19 @@
|
|||||||
|
|
||||||
#include "Config.h"
|
#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 {
|
namespace MobileGL::MG_ConfigLoader {
|
||||||
static UniquePtr<UnorderedMap<String, String>> acceptedEnvVariablesMap;
|
static UniquePtr<UnorderedMap<String, String>> acceptedEnvVariablesMap;
|
||||||
|
|
||||||
@@ -56,6 +69,78 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quirk overrides are tri-state: an unset variable keeps device auto-detection, a truthy
|
||||||
|
// value forces the quirk on, anything else set ("0", "false", "") forces it off.
|
||||||
|
inline MG_Config::QuirkOverride QueryEnvQuirkOverride(const String& key) {
|
||||||
|
auto it = acceptedEnvVariablesMap->find(key);
|
||||||
|
if (it == acceptedEnvVariablesMap->end()) {
|
||||||
|
return MG_Config::QuirkOverride::Auto;
|
||||||
|
}
|
||||||
|
return IsTruthyValue(it->second) ? MG_Config::QuirkOverride::ForceOn
|
||||||
|
: MG_Config::QuirkOverride::ForceOff;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
|
||||||
|
features.ClipDistanceQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_CLIP_DISTANCE");
|
||||||
|
features.MagmaDisableBlendedDepthWriteQuirk =
|
||||||
|
QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE");
|
||||||
|
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
|
||||||
|
}
|
||||||
|
|
||||||
inline void InitBackendType() {
|
inline void InitBackendType() {
|
||||||
String backendTypeStr;
|
String backendTypeStr;
|
||||||
QueryEnvVariable("MOBILEGL_BACKEND_TYPE", backendTypeStr, "DirectGLES");
|
QueryEnvVariable("MOBILEGL_BACKEND_TYPE", backendTypeStr, "DirectGLES");
|
||||||
@@ -77,6 +162,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
InitializeAcceptedEnvVariables();
|
InitializeAcceptedEnvVariables();
|
||||||
|
|
||||||
InitBackendType();
|
InitBackendType();
|
||||||
|
InitFeatures();
|
||||||
|
|
||||||
// Destroy the map since we won't need it anymore
|
// Destroy the map since we won't need it anymore
|
||||||
acceptedEnvVariablesMap.reset();
|
acceptedEnvVariablesMap.reset();
|
||||||
|
|||||||
+17
-8
@@ -32,9 +32,14 @@
|
|||||||
#define MOBILEGL_GLX_API MOBILEGL_API
|
#define MOBILEGL_GLX_API MOBILEGL_API
|
||||||
#define MOBILEGL_GL_API MOBILEGL_API
|
#define MOBILEGL_GL_API MOBILEGL_API
|
||||||
#define MOBILEGL_EGL_API MOBILEGL_API
|
#define MOBILEGL_EGL_API MOBILEGL_API
|
||||||
|
#define MOBILEGL_CGL_API MOBILEGL_API
|
||||||
|
#define MOBILEGL_NSOPENGL_API MOBILEGL_API
|
||||||
|
#define MOBILEGL_WGL_API MOBILEGL_API
|
||||||
|
|
||||||
// ====================== MobileGL configurations ======================= //
|
// ====================== MobileGL configurations ======================= //
|
||||||
|
#ifndef MOBILEGL_LOG_ACTIVE_LEVEL
|
||||||
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO
|
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO
|
||||||
|
#endif
|
||||||
|
|
||||||
#define MOBILEGL_LOG_ENABLE_CONSOLE 0
|
#define MOBILEGL_LOG_ENABLE_CONSOLE 0
|
||||||
#define MOBILEGL_LOG_ENABLE_FILE 1
|
#define MOBILEGL_LOG_ENABLE_FILE 1
|
||||||
@@ -63,11 +68,15 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// =============================== Utils ================================ //
|
// =============================== Utils ================================ //
|
||||||
#define MOBILEGL_ASSERT(condition, ...) \
|
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||||
do { \
|
#define MOBILEGL_ASSERT(condition, ...) \
|
||||||
if (!(condition)) { \
|
do { \
|
||||||
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
|
if (!(condition)) { \
|
||||||
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
|
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
|
||||||
TRAP; \
|
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
|
||||||
} \
|
TRAP; \
|
||||||
} while (0)
|
} \
|
||||||
|
} while (0)
|
||||||
|
#else
|
||||||
|
#define MOBILEGL_ASSERT(condition, ...)
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ namespace MobileGL {
|
|||||||
} // namespace MG_Config
|
} // namespace MG_Config
|
||||||
|
|
||||||
namespace MG_Backend {
|
namespace MG_Backend {
|
||||||
UniquePtr<BackendObject> pActiveBackendObject;
|
// Leak-at-exit storage: the UniquePtr itself lives on the heap and is
|
||||||
|
// never destroyed by the runtime, so process exit runs no backend
|
||||||
|
// destructors (static destruction order across TUs is undefined).
|
||||||
|
// Deterministic teardown happens inside the EGL lifecycle instead:
|
||||||
|
// the last eglTerminate calls MobileGL::Destroy(), which .reset()s
|
||||||
|
// these singletons while the process is still healthy.
|
||||||
|
UniquePtr<BackendObject>& pActiveBackendObject = *new UniquePtr<BackendObject>();
|
||||||
GlobalBackendFunctionsTable gBackendFunctionsTable;
|
GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||||||
} // namespace MG_Backend
|
} // namespace MG_Backend
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
@@ -108,10 +110,32 @@
|
|||||||
#define VK_USE_PLATFORM_WIN32_KHR
|
#define VK_USE_PLATFORM_WIN32_KHR
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#define VK_USE_PLATFORM_METAL_EXT
|
#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
|
#else
|
||||||
#warning "VK_USE_PLATFORM_*_KHR not defined for this platform!"
|
#warning "VK_USE_PLATFORM_*_KHR not defined for this platform!"
|
||||||
#endif
|
#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>
|
#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
|
#ifdef TRACY_ENABLE
|
||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
|
|||||||
+72
-34
@@ -9,13 +9,55 @@
|
|||||||
#include "Init.h"
|
#include "Init.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include <MG_Backend/BackendObjects.h>
|
#include <MG_Backend/BackendObjects.h>
|
||||||
|
#include <MG_Backend/DirectVulkan/DirectVulkan.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/EGLState/Core.h>
|
#include <MG_State/EGLState/Core.h>
|
||||||
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
||||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
|
namespace {
|
||||||
|
std::atomic<Bool> g_isInitialized = false;
|
||||||
|
thread_local Bool tl_initializing = false;
|
||||||
|
|
||||||
|
std::mutex& InitMutex() {
|
||||||
|
static std::mutex mutex;
|
||||||
|
return mutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
void Initialize() {
|
||||||
|
if (g_isInitialized) {
|
||||||
|
MGLOG_D("MobileGL already initialized; skipping duplicate Initialize()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MG_Util::Debug::InitFile();
|
MG_Util::Debug::InitFile();
|
||||||
MGLOG_I("Initializing MobileGL...");
|
MGLOG_I("Initializing MobileGL...");
|
||||||
MG_ConfigLoader::Init();
|
MG_ConfigLoader::Init();
|
||||||
@@ -28,43 +70,39 @@ namespace MobileGL {
|
|||||||
MGLOG_D("MG_Impl initialized");
|
MGLOG_D("MG_Impl initialized");
|
||||||
glslang::InitializeProcess();
|
glslang::InitializeProcess();
|
||||||
MGLOG_D("glslang initialized");
|
MGLOG_D("glslang initialized");
|
||||||
|
g_isInitialized = true;
|
||||||
MGLOG_I("MobileGL initialized");
|
MGLOG_I("MobileGL initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Destroy() {
|
void EnsureInitialized() {
|
||||||
MGLOG_I("MobileGL closing...");
|
if (g_isInitialized.load(std::memory_order_acquire)) {
|
||||||
glslang::FinalizeProcess();
|
return;
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
__attribute__((constructor)) static void AutoInit() {
|
|
||||||
Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((destructor)) static void AutoDestroy() {
|
|
||||||
Destroy();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
|
||||||
switch (ul_reason_for_call) {
|
|
||||||
case DLL_PROCESS_ATTACH:
|
|
||||||
Initialize();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DLL_PROCESS_DETACH:
|
|
||||||
Destroy();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return TRUE;
|
// Re-entrant call while this thread is already inside Initialize()
|
||||||
|
// (e.g. an init step routing back through a public entry point).
|
||||||
|
if (tl_initializing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(InitMutex());
|
||||||
|
if (g_isInitialized.load(std::memory_order_acquire)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tl_initializing = true;
|
||||||
|
Initialize();
|
||||||
|
tl_initializing = false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
void Destroy() {
|
||||||
|
DestroyImpl(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MobileGL's lifecycle is owned entirely by the host-API layers
|
||||||
|
// (EGL/WGL/CGL): initialization happens lazily on the first entry point
|
||||||
|
// via EnsureInitialized(), and full teardown happens deterministically
|
||||||
|
// when the last EGL display is terminated with nothing current (EGLImpl
|
||||||
|
// calls Destroy()). There is intentionally no static constructor, no
|
||||||
|
// static destructor, and no DllMain: the global singletons use
|
||||||
|
// leak-at-exit storage (see GlobalObjects.cpp), so a process that exits
|
||||||
|
// without eglTerminate simply leaks them to the OS instead of running
|
||||||
|
// backend destructors during static teardown.
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
void Initialize();
|
void Initialize();
|
||||||
|
// Thread-safe, idempotent, and re-entrant wrapper around Initialize().
|
||||||
|
// Host layers (EGL/WGL/CGL entry points) call this lazily on first use so
|
||||||
|
// MobileGL's lifecycle never depends on ELF/DLL static constructors, and
|
||||||
|
// so a fresh init can follow a full Destroy() (e.g. after the last
|
||||||
|
// eglTerminate).
|
||||||
|
void EnsureInitialized();
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
|
||||||
namespace MG_Util::Debug {
|
namespace MG_Util::Debug {
|
||||||
|
|||||||
@@ -7,18 +7,164 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "BackendObject.h"
|
#include "BackendObject.h"
|
||||||
|
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend {
|
namespace MobileGL::MG_Backend {
|
||||||
namespace {
|
namespace {
|
||||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
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() {
|
std::thread::id CurrentThreadKey() {
|
||||||
return std::this_thread::get_id();
|
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
|
} // 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) {
|
Bool BackendObject::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
if (dpy == EGL_NO_DISPLAY) {
|
if (dpy == EGL_NO_DISPLAY) {
|
||||||
@@ -42,28 +188,116 @@ namespace MobileGL::MG_Backend {
|
|||||||
return true;
|
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);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
if (!m_eglDisplayInitialized) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (handle.Backend == WindowBackend::Unknown || !handle.Handle) {
|
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;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetWindowHandle(handle);
|
if (surfaceState->Kind == SurfaceKind::Window) {
|
||||||
if (!InitWindowSurface()) {
|
SetWindowHandle(surfaceState->Window);
|
||||||
MGLOG_E("CreateEGLWindowSurface failed: backend InitWindowSurface failed");
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_eglWindowSurfaceInitialized = true;
|
m_eglSurface = surface;
|
||||||
|
m_eglSurfaceInitialized = true;
|
||||||
|
m_eglSurfaceKind = surfaceState->Kind;
|
||||||
m_eglCurrentThreads.clear();
|
m_eglCurrentThreads.clear();
|
||||||
m_backendCapabilitiesInitialized = false;
|
m_backendCapabilitiesInitialized = false;
|
||||||
return true;
|
return true;
|
||||||
@@ -73,7 +307,7 @@ namespace MobileGL::MG_Backend {
|
|||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
const auto threadKey = CurrentThreadKey();
|
const auto threadKey = CurrentThreadKey();
|
||||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
||||||
m_eglCurrentThreads.erase(threadKey);
|
ReleaseEGLCurrentThread(threadKey);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +315,22 @@ namespace MobileGL::MG_Backend {
|
|||||||
MGLOG_E("MakeEGLCurrent failed: EGL display mismatch or not initialized");
|
MGLOG_E("MakeEGLCurrent failed: EGL display mismatch or not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!m_eglWindowSurfaceInitialized) {
|
if (!m_eglSurfaceInitialized) {
|
||||||
MGLOG_E("MakeEGLCurrent failed: EGL window surface is not initialized");
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE || ctx == EGL_NO_CONTEXT) {
|
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_backendCapabilitiesInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_eglCurrentThreads[threadKey] = true;
|
ReleaseEGLCurrentThread(threadKey);
|
||||||
|
m_eglCurrentThreads[threadKey] = EGLCurrentState{
|
||||||
|
.Display = dpy,
|
||||||
|
.DrawSurface = draw,
|
||||||
|
.ReadSurface = read,
|
||||||
|
.Context = ctx,
|
||||||
|
};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendObject::ResetEGLRuntimeState() {
|
void BackendObject::ResetEGLRuntimeState() {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
m_eglWindowSurfaceInitialized = false;
|
m_eglSurfaceInitialized = false;
|
||||||
m_backendCapabilitiesInitialized = false;
|
m_backendCapabilitiesInitialized = false;
|
||||||
|
m_eglSurfaceKind = SurfaceKind::None;
|
||||||
|
m_eglSurface = EGL_NO_SURFACE;
|
||||||
|
m_windowHandle = {};
|
||||||
m_eglCurrentThreads.clear();
|
m_eglCurrentThreads.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +372,17 @@ namespace MobileGL::MG_Backend {
|
|||||||
MGLOG_E("SwapEGLBuffers failed: EGL display mismatch or not initialized");
|
MGLOG_E("SwapEGLBuffers failed: EGL display mismatch or not initialized");
|
||||||
return false;
|
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");
|
MGLOG_E("SwapEGLBuffers failed: no current context attached");
|
||||||
return false;
|
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");
|
MGLOG_E("SwapEGLBuffers failed: invalid draw surface");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -134,8 +397,99 @@ namespace MobileGL::MG_Backend {
|
|||||||
return true;
|
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) {
|
void BackendObject::SetWindowHandle(const WindowHandle& handle) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
m_windowHandle = handle;
|
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
|
} // namespace MobileGL::MG_Backend
|
||||||
|
|||||||
@@ -8,8 +8,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
|
namespace MG_State::GLState {
|
||||||
|
class FramebufferObject;
|
||||||
|
class ITextureObject;
|
||||||
|
}
|
||||||
|
|
||||||
enum class BackendType {
|
enum class BackendType {
|
||||||
DirectGLES,
|
DirectGLES,
|
||||||
DirectVulkan,
|
DirectVulkan,
|
||||||
@@ -18,11 +24,88 @@ namespace MobileGL {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace MG_Backend {
|
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 {
|
struct GLFunctionsTable {
|
||||||
void (*DrawArrays)(GLenum mode, GLint first, GLsizei count);
|
void (*DrawArrays)(GLenum mode, GLint first, GLsizei count);
|
||||||
void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||||
void (*DrawElementsBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void (*DrawElementsBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLint basevertex);
|
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,
|
void (*MultiDrawElements)(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount);
|
GLsizei drawcount);
|
||||||
void (*MultiDrawElementsBaseVertex)(GLenum mode, const GLsizei* count, GLenum type,
|
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,
|
void (*MultiDrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
||||||
GLsizei stride);
|
GLsizei stride);
|
||||||
void (*MultiDrawArraysIndirect)(GLenum mode, 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,
|
void (*DrawRangeElementsBaseVertex)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex);
|
const void* indices, GLint basevertex);
|
||||||
void (*DrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void (*DrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
@@ -54,29 +141,180 @@ namespace MobileGL {
|
|||||||
void (*ClearBufferfv)(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void (*ClearBufferfv)(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void (*ClearBufferuiv)(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
void (*ClearBufferuiv)(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
void (*ClearBufferiv)(GLenum buffer, GLint drawbuffer, const GLint* 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,
|
void (*BlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
||||||
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
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,
|
void (*CopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height, GLint border);
|
GLsizei height, GLint border);
|
||||||
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
||||||
GLsizei width, GLsizei height);
|
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 (*GenerateMipmap)(GLenum target);
|
||||||
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||||
void* pixels);
|
void* pixels);
|
||||||
void (*GetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* 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 {
|
struct GlobalBackendFunctionsTable {
|
||||||
GLFunctionsTable GL;
|
GLFunctionsTable GL;
|
||||||
void (*Present)();
|
void (*Present)();
|
||||||
|
// Optional: applies the app-requested eglSwapInterval to the native
|
||||||
|
// presentation path (null = backend keeps its own pacing policy).
|
||||||
|
void (*SetSwapInterval)(Int interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Coarse GPU vendor identity for gating device-specific quirks. Detected from the
|
||||||
|
// Vulkan physical-device vendorID or the GLES GL_VENDOR/GL_RENDERER strings; stays
|
||||||
|
// Unknown when detection is inconclusive, in which case auto-gated quirks stay off.
|
||||||
|
enum class GpuVendorKind : Uint8 {
|
||||||
|
Unknown = 0,
|
||||||
|
Qualcomm,
|
||||||
|
Arm,
|
||||||
|
Nvidia,
|
||||||
|
Amd,
|
||||||
|
Intel,
|
||||||
|
ImgTec,
|
||||||
|
// Software rasterizers (llvmpipe/lavapipe, SwiftShader).
|
||||||
|
Software,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DynamicBackendParameters {
|
struct DynamicBackendParameters {
|
||||||
SizeT UniformBufferOffsetAlignment = 256;
|
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;
|
||||||
|
GpuVendorKind GpuVendor = GpuVendorKind::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class WindowBackend {
|
enum class WindowBackend {
|
||||||
Android,
|
Android,
|
||||||
// TODO: X11, Wayland, Windows, macOS, etc.
|
X11,
|
||||||
|
MetalLayer,
|
||||||
|
Win32, // Handle is an HWND
|
||||||
|
// TODO: Wayland, etc.
|
||||||
WindowBackendCount,
|
WindowBackendCount,
|
||||||
Unknown = -1
|
Unknown = -1
|
||||||
};
|
};
|
||||||
@@ -84,6 +322,8 @@ namespace MobileGL {
|
|||||||
struct WindowHandle {
|
struct WindowHandle {
|
||||||
WindowBackend Backend = WindowBackend::Unknown;
|
WindowBackend Backend = WindowBackend::Unknown;
|
||||||
void* Handle = nullptr;
|
void* Handle = nullptr;
|
||||||
|
Uint32 Width = 0;
|
||||||
|
Uint32 Height = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BackendObject {
|
class BackendObject {
|
||||||
@@ -95,9 +335,16 @@ namespace MobileGL {
|
|||||||
virtual Bool InitWindowSurface() = 0;
|
virtual Bool InitWindowSurface() = 0;
|
||||||
|
|
||||||
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
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 MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||||
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
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);
|
void SetWindowHandle(const WindowHandle& handle);
|
||||||
|
|
||||||
@@ -105,18 +352,56 @@ namespace MobileGL {
|
|||||||
virtual String GetBackendAPIVersionString() const = 0;
|
virtual String GetBackendAPIVersionString() const = 0;
|
||||||
virtual const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0;
|
virtual const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0;
|
||||||
virtual const DynamicBackendParameters& GetDynamicParameters() const = 0;
|
virtual const DynamicBackendParameters& GetDynamicParameters() const = 0;
|
||||||
|
const FormatCapabilityCache& GetFormatCapabilities() const;
|
||||||
virtual BackendType GetBackendType() const = 0;
|
virtual BackendType GetBackendType() const = 0;
|
||||||
|
|
||||||
protected:
|
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();
|
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;
|
mutable std::recursive_mutex m_eglStateMutex;
|
||||||
|
FormatCapabilityCache m_formatCapabilities;
|
||||||
WindowHandle m_windowHandle;
|
WindowHandle m_windowHandle;
|
||||||
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
||||||
|
EGLSurface m_eglSurface = EGL_NO_SURFACE;
|
||||||
Bool m_eglDisplayInitialized = false;
|
Bool m_eglDisplayInitialized = false;
|
||||||
Bool m_eglWindowSurfaceInitialized = false;
|
Bool m_eglSurfaceInitialized = false;
|
||||||
Bool m_backendCapabilitiesInitialized = 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 MG_Backend
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
@@ -13,6 +13,6 @@
|
|||||||
#include "DirectVulkan/BackendObject_DirectVulkan.h"
|
#include "DirectVulkan/BackendObject_DirectVulkan.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend {
|
namespace MobileGL::MG_Backend {
|
||||||
extern UniquePtr<BackendObject> pActiveBackendObject;
|
extern UniquePtr<BackendObject>& pActiveBackendObject;
|
||||||
extern GlobalBackendFunctionsTable gBackendFunctionsTable;
|
extern GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||||||
} // namespace MobileGL::MG_Backend
|
} // namespace MobileGL::MG_Backend
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,12 @@
|
|||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
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 {
|
class BackendObject_DirectGLES : public BackendObject {
|
||||||
public:
|
public:
|
||||||
~BackendObject_DirectGLES() override;
|
~BackendObject_DirectGLES() override;
|
||||||
@@ -20,9 +26,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Bool InitCapabilities() override;
|
Bool InitCapabilities() override;
|
||||||
Bool InitWindowSurface() override;
|
Bool InitWindowSurface() override;
|
||||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) 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 MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||||
|
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||||
|
void ReleaseEGLResources() override;
|
||||||
|
|
||||||
const RendererInfo& GetRendererInfo() const override;
|
const RendererInfo& GetRendererInfo() const override;
|
||||||
String GetBackendAPIVersionString() const override;
|
String GetBackendAPIVersionString() const override;
|
||||||
@@ -32,9 +41,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
const MG_External::GLESFunctionsTable& GetGLESFunctions() const;
|
const MG_External::GLESFunctionsTable& GetGLESFunctions() const;
|
||||||
const MG_External::EGLFunctionsTable& GetEGLFunctions() const;
|
const MG_External::EGLFunctionsTable& GetEGLFunctions() const;
|
||||||
|
void ApplyGLESCapabilitiesForTesting(const MG_External::GLESCapabilities& capabilities);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateDynamicBackendParameters();
|
void UpdateDynamicBackendParameters();
|
||||||
|
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||||
|
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||||
|
|
||||||
Bool m_initialized = false;
|
Bool m_initialized = false;
|
||||||
MG_External::EGLFunctionsTable m_EGLFunctions;
|
MG_External::EGLFunctionsTable m_EGLFunctions;
|
||||||
@@ -42,4 +54,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MG_External::GLESCapabilities m_GLESCapabilities;
|
MG_External::GLESCapabilities m_GLESCapabilities;
|
||||||
DynamicBackendParameters m_dynamicParameters;
|
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
|
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#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/TextureState/TextureState.h>
|
||||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
@@ -17,6 +19,10 @@
|
|||||||
operation Utils::CheckGLESError();
|
operation Utils::CheckGLESError();
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
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 ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* 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 DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
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,
|
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount);
|
GLsizei drawcount);
|
||||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount, const GLint* basevertex);
|
GLsizei drawcount, const GLint* basevertex);
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
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 MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex);
|
const void* indices, GLint basevertex);
|
||||||
@@ -46,18 +55,100 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
GLuint baseinstance);
|
GLuint baseinstance);
|
||||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
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,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
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,
|
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height, GLint border);
|
GLsizei height, GLint border);
|
||||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height);
|
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 GenerateMipmap(GLenum target);
|
||||||
const GLubyte* GetString(GLenum name);
|
const GLubyte* GetString(GLenum name);
|
||||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
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 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 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();
|
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 SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
|
||||||
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
|
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
|
||||||
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,67 +8,324 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
#include "DirectGLES.h"
|
#include "DirectGLES.h"
|
||||||
#include "MG_State/GLState/SamplerState/SamplerObject.h"
|
#include "MG_State/GLState/SamplerState/SamplerObject.h"
|
||||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
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 {
|
namespace BufferImpl {
|
||||||
const GLenum TempBufferTarget = GL_ARRAY_BUFFER;
|
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:
|
public:
|
||||||
BackendBufferObject();
|
~GLESBufferResource() override = default;
|
||||||
void SyncToBackend(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
|
||||||
Uint GetBackendBufferId() { return m_backendBufferId; }
|
|
||||||
void Bind(GLenum target = TempBufferTarget);
|
|
||||||
|
|
||||||
private:
|
Uint id = 0;
|
||||||
void SyncToBackend_glBufferData(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
SizeT storageSize = 0;
|
||||||
void SyncToBackend_glBufferSubData(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
Bool storageInitialized = false;
|
||||||
void SyncToBackend_glMapBufferRange(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject,
|
// ES context generation this resource's id belongs to; ids from a
|
||||||
Bool invalidate = true, Bool unsynchronized = true);
|
// destroyed context are invalid and must not be deleted or reused.
|
||||||
|
Uint contextGeneration = 0;
|
||||||
Uint m_backendBufferId = 0;
|
// Frontend change serial the backend storage reflects. When immediate
|
||||||
SizeT m_prevBufferSize = 0;
|
// ops cannot run (ops unregistered, no current context), this lags and
|
||||||
Bool m_isInitialized = false;
|
// 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;
|
// Registered as the frontend's BufferBackendOps at backend init and on
|
||||||
extern UnorderedMap<SharedPtr<MG_State::GLState::BufferObject>, SharedPtr<BackendBufferObject>>
|
// every MakeCurrent (the ES context can be destroyed and recreated, e.g.
|
||||||
g_backendBufferObjects;
|
// 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 caches for the driver-level GL_PIXEL_PACK/UNPACK_BUFFER
|
||||||
|
// bindings. Every backend readback (glReadPixels / pack-PBO map) and pixel
|
||||||
|
// upload site routes its binding through these so the shadow always matches
|
||||||
|
// the driver; the resting state between operations is 0, which keeps any
|
||||||
|
// path that implicitly assumes "no PBO bound" correct. Scrubbed when a
|
||||||
|
// buffer id is deleted/pooled (GL resets a deleted buffer's bindings to 0,
|
||||||
|
// and a recycled name matching the shadow would false-skip the rebind) and
|
||||||
|
// invalidated on MakeCurrent (context may reset).
|
||||||
|
void BindPixelPackBufferId(Uint id);
|
||||||
|
void BindPixelUnpackBufferId(Uint id);
|
||||||
|
void InvalidatePixelBufferBindingCaches();
|
||||||
|
// A GL buffer id is being deleted by code outside BufferImpl (e.g. the VAO
|
||||||
|
// client-attribute staging buffers): scrub every buffer-binding shadow that
|
||||||
|
// could false-skip when the name is recycled.
|
||||||
|
void NoteBufferIdDeleted(Uint id);
|
||||||
|
// 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 BufferImpl
|
||||||
|
|
||||||
namespace VertexArrayImpl {
|
namespace VertexArrayImpl {
|
||||||
class BackendVertexArrayObject {
|
class BackendVertexArrayObject {
|
||||||
public:
|
public:
|
||||||
BackendVertexArrayObject();
|
BackendVertexArrayObject();
|
||||||
void SyncToBackend(SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
|
~BackendVertexArrayObject();
|
||||||
Uint GetBackendVertexArrayId() { return m_backendVAOId; }
|
void SyncToBackend(const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
|
||||||
void Bind();
|
void SyncClientSideAttributesForDrawArrays(
|
||||||
|
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
|
||||||
|
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
||||||
|
void Bind() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BindAttributeBuffer(Uint index, const MG_State::GLState::VertexAttribute& attrib);
|
|
||||||
|
|
||||||
Uint m_backendVAOId = 0;
|
Uint m_backendVAOId = 0;
|
||||||
|
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
||||||
|
// Attribs the frontend has Enabled but that have no source at all (no buffer object
|
||||||
|
// and NULL client pointer). GL keeps such attribs latently enabled, but Adreno's ES
|
||||||
|
// driver treats them as client arrays and memcpys from address 0 at draw time
|
||||||
|
// (SIGSEGV), so they are kept disabled on the backend VAO until they gain a source.
|
||||||
|
Uint32 m_forceDisabledAttribsMask = 0;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
Uint16 m_syncedIndexBufferVersion = 0;
|
Uint16 m_syncedIndexBufferVersion = 0;
|
||||||
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
||||||
m_syncedAttributeVersions;
|
m_syncedAttributeVersions;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern UnorderedMap<SharedPtr<MG_State::GLState::VertexArrayObject>, SharedPtr<BackendVertexArrayObject>>
|
extern StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject>
|
||||||
g_backendVertexArrayObjects;
|
g_backendVertexArrayObjects;
|
||||||
} // namespace VertexArrayImpl
|
} // namespace VertexArrayImpl
|
||||||
|
|
||||||
namespace TextureImpl {
|
namespace TextureImpl {
|
||||||
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
||||||
if (target == TextureTarget::Texture1D || target == TextureTarget::TextureRectangle ||
|
// Rectangle textures need non-normalized sampling ES cannot express; everything else is
|
||||||
target == TextureTarget::Texture2DMultisampleArray || target == TextureTarget::Texture1DArray ||
|
// either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see
|
||||||
target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DArray)
|
// MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and
|
||||||
return false;
|
// coordinate padding for 1D/1D-array shaders.
|
||||||
return true;
|
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
|
struct StateTextureBasicInfo { // Used for tracking texture state changes
|
||||||
@@ -78,11 +335,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
SizeT depth = 0;
|
SizeT depth = 0;
|
||||||
SizeT mipmapLevels = 0;
|
SizeT mipmapLevels = 0;
|
||||||
Uint bufferExternalIndex = 0;
|
Uint bufferExternalIndex = 0;
|
||||||
|
Int samples = 0;
|
||||||
|
Bool fixedSampleLocations = true;
|
||||||
|
|
||||||
bool operator==(const StateTextureBasicInfo& other) const {
|
bool operator==(const StateTextureBasicInfo& other) const {
|
||||||
return internalFormat == other.internalFormat && width == other.width && height == other.height &&
|
return internalFormat == other.internalFormat && width == other.width && height == other.height &&
|
||||||
depth == other.depth && mipmapLevels == other.mipmapLevels &&
|
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); }
|
bool operator!=(const StateTextureBasicInfo& other) const { return !(*this == other); }
|
||||||
@@ -92,15 +352,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
class BackendTextureObject {
|
class BackendTextureObject {
|
||||||
public:
|
public:
|
||||||
BackendTextureObject();
|
BackendTextureObject();
|
||||||
void SyncMipmapsToBackend(SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
// Deletes the GL texture (frontend glDeleteTextures used to leak every
|
||||||
void SyncBuiltinSamplerToBackend(SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
// backend id for the context lifetime) and scrubs the binding/scratch-FBO
|
||||||
void SyncTextureParamsToBackend(SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
// shadows so a recycled name or heap address cannot false-skip a rebind.
|
||||||
|
~BackendTextureObject();
|
||||||
|
BackendTextureObject(const BackendTextureObject&) = delete;
|
||||||
|
BackendTextureObject& operator=(const BackendTextureObject&) = delete;
|
||||||
|
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);
|
void Bind(GLenum target, Uint unit = TempTextureUnit);
|
||||||
Uint GetBackendTextureId();
|
Uint GetBackendTextureId() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void RecreateBackendTexture();
|
||||||
|
|
||||||
Uint m_backendTextureId = 0;
|
Uint m_backendTextureId = 0;
|
||||||
|
// ES context generation the id was created under; a dtor running after
|
||||||
|
// that context died must not delete a foreign (recycled) name.
|
||||||
|
Uint m_contextGeneration = 0;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
|
Bool m_imageBindableStorageRequired = false;
|
||||||
|
Bool m_backendStorageImmutable = false;
|
||||||
StateTextureBasicInfo m_prevTextureInfo;
|
StateTextureBasicInfo m_prevTextureInfo;
|
||||||
SamplerParameters m_cacheSamplerParameters;
|
SamplerParameters m_cacheSamplerParameters;
|
||||||
UintVec2 m_cacheLodRange = {0, 1000};
|
UintVec2 m_cacheLodRange = {0, 1000};
|
||||||
@@ -113,25 +387,33 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
void ActivateTextureUnit(Uint unit);
|
void ActivateTextureUnit(Uint unit);
|
||||||
void UnbindTexture(Uint unit, GLenum target);
|
void UnbindTexture(Uint unit, GLenum target);
|
||||||
extern UnorderedMap<SharedPtr<MG_State::GLState::ITextureObject>, SharedPtr<BackendTextureObject>>
|
extern StateBackendObjectRegistry<MG_State::GLState::ITextureObject, BackendTextureObject>
|
||||||
g_backendTextureObjects;
|
g_backendTextureObjects;
|
||||||
|
SharedPtr<BackendTextureObject>& SyncTextureObjectToBackend(
|
||||||
|
const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||||
|
Bool imageBindableStorageRequired = false);
|
||||||
extern Array<Array<BackendTextureObject*, (SizeT)TextureTarget::TextureTargetCount>,
|
extern Array<Array<BackendTextureObject*, (SizeT)TextureTarget::TextureTargetCount>,
|
||||||
MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
|
MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
|
||||||
g_boundTexturesCache;
|
g_boundTexturesCache;
|
||||||
extern Uint g_activeTextureUnit;
|
extern Uint g_activeTextureUnit;
|
||||||
|
// Bumped when the backend ES context is destroyed; texture ids stamped with
|
||||||
|
// an older generation belong to a dead context and must not be deleted.
|
||||||
|
extern Uint g_textureContextGeneration;
|
||||||
} // namespace TextureImpl
|
} // namespace TextureImpl
|
||||||
|
|
||||||
namespace FramebufferImpl {
|
namespace FramebufferImpl {
|
||||||
class BackendFramebufferObject {
|
class BackendFramebufferObject {
|
||||||
public:
|
public:
|
||||||
BackendFramebufferObject();
|
BackendFramebufferObject();
|
||||||
void SyncToBackend(SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
void SyncToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
||||||
FramebufferTarget asTarget);
|
FramebufferTarget asTarget);
|
||||||
Uint GetBackendFramebufferId() { return m_backendFBOId; }
|
// Apply only this FBO's read buffer (glReadBuffer) to the backend. Split out so it can
|
||||||
void Bind(FramebufferTarget target);
|
// still run when SyncCurrentFBO skips the READ-target sync because the same GL FBO is
|
||||||
bool SyncAttachmentObject(GLenum glFBOTarget,
|
// bound as both draw and read (otherwise glReadBuffer changes would be silently dropped).
|
||||||
const MG_State::GLState::FramebufferAttachmentObject& attachmentObject,
|
void SyncReadBufferToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject);
|
||||||
GLenum glBackendAttachment);
|
void InvalidateSyncedState();
|
||||||
|
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
|
||||||
|
void Bind(FramebufferTarget target) const;
|
||||||
// FramebufferAttachmentType GetCompactedAttachmentTypeAtDrawBufferIndex(Int index);
|
// FramebufferAttachmentType GetCompactedAttachmentTypeAtDrawBufferIndex(Int index);
|
||||||
GLenum GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const;
|
GLenum GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const;
|
||||||
|
|
||||||
@@ -159,28 +441,204 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
FramebufferObject::FramebufferAttachmentVersionArray m_syncedFrontendAttachmentVersions = {0};
|
FramebufferObject::FramebufferAttachmentVersionArray m_syncedFrontendAttachmentVersions = {0};
|
||||||
};
|
};
|
||||||
|
|
||||||
extern UnorderedMap<SharedPtr<MG_State::GLState::FramebufferObject>, SharedPtr<BackendFramebufferObject>>
|
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
||||||
g_backendFramebufferObjects;
|
g_backendFramebufferObjects;
|
||||||
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
|
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;
|
||||||
|
|
||||||
|
// Driver-level READ/DRAW framebuffer-binding shadow. Every backend
|
||||||
|
// glBindFramebuffer routes through BindFramebufferId so scoped helpers can
|
||||||
|
// save/restore the current binding without a glGetIntegerv round-trip (that
|
||||||
|
// query forces a driver pipeline sync) and so redundant rebinds no-op.
|
||||||
|
// Starts unknown; the first CurrentFramebufferBinding() query pins it from
|
||||||
|
// the driver once. Invalidated on MakeCurrent (context may reset).
|
||||||
|
// GL_FRAMEBUFFER binds both targets.
|
||||||
|
void BindFramebufferId(GLenum fbTarget, Uint id);
|
||||||
|
Uint CurrentFramebufferBinding(FramebufferTarget target);
|
||||||
|
void InvalidateFramebufferBindingCache();
|
||||||
} // namespace FramebufferImpl
|
} // namespace FramebufferImpl
|
||||||
|
|
||||||
|
// Shared scratch framebuffers for the readback/copy/blit emulation paths, with a
|
||||||
|
// driver-side attachment shadow: repeated uses skip redundant detach/attach GL
|
||||||
|
// calls, and an attachment left by one use (e.g. a depth copy's DEPTH_STENCIL
|
||||||
|
// texture) is detached exactly when a later use of another aspect would
|
||||||
|
// otherwise inherit it (stale cross-aspect attachments made the shared temp FBO
|
||||||
|
// incomplete and silently degraded later readbacks).
|
||||||
|
namespace ScratchFBOImpl {
|
||||||
|
struct ScratchFramebuffer {
|
||||||
|
Uint id = 0;
|
||||||
|
// false => attachment state unknown; scrub every point on next use.
|
||||||
|
// A fresh FBO starts with nothing attached, so creation sets it true.
|
||||||
|
Bool attachmentsKnown = false;
|
||||||
|
Uint colorTex = 0;
|
||||||
|
GLenum colorTarget = 0;
|
||||||
|
GLint colorLevel = 0;
|
||||||
|
GLint colorLayer = -1; // >= 0 => attached via glFramebufferTextureLayer
|
||||||
|
Uint depthTex = 0;
|
||||||
|
GLenum depthTarget = 0;
|
||||||
|
GLint depthLevel = 0;
|
||||||
|
Bool depthHasStencil = false;
|
||||||
|
// Per-FBO read/draw buffer state (0 = unknown, set on first use).
|
||||||
|
GLenum readBuffer = 0;
|
||||||
|
GLenum drawBuffer = 0;
|
||||||
|
};
|
||||||
|
ScratchFramebuffer& TempFramebuffer(); // GetTexImage READ / CopyTex*Image2D depth DRAW
|
||||||
|
ScratchFramebuffer& BlitReadFramebuffer(); // texture-to-texture blit source
|
||||||
|
ScratchFramebuffer& BlitDrawFramebuffer(); // texture-to-texture blit destination
|
||||||
|
// Returns the GL id, generating it if needed (requires a current ES context).
|
||||||
|
Uint EnsureId(ScratchFramebuffer& fb);
|
||||||
|
// The fb must currently be bound at fbTarget (glReadBuffer/glDrawBuffers
|
||||||
|
// target the READ/DRAW binding respectively). Each Ensure* performs the
|
||||||
|
// minimal detach/attach set and keeps the shadow in sync; a failed attach
|
||||||
|
// records the point as detached so the completeness check fails instead of
|
||||||
|
// silently reading a stale attachment.
|
||||||
|
void EnsureColorAttachment2D(ScratchFramebuffer& fb, GLenum fbTarget, Uint tex, GLenum texTarget, GLint level);
|
||||||
|
void EnsureColorAttachmentLayer(ScratchFramebuffer& fb, GLenum fbTarget, Uint tex, GLint level, GLint layer);
|
||||||
|
void EnsureDepthAttachment2D(ScratchFramebuffer& fb, GLenum fbTarget, Uint tex, GLenum texTarget, GLint level,
|
||||||
|
Bool withStencil);
|
||||||
|
void EnsureNoColorAttachment(ScratchFramebuffer& fb, GLenum fbTarget);
|
||||||
|
void EnsureNoDepthAttachment(ScratchFramebuffer& fb, GLenum fbTarget);
|
||||||
|
void EnsureReadBuffer(ScratchFramebuffer& fb, GLenum readBuffer);
|
||||||
|
void EnsureDrawBuffer(ScratchFramebuffer& fb, GLenum drawBuffer);
|
||||||
|
// A 1x1 RGBA8-renderbuffer-complete FBO (GenerateMipmap needs a complete
|
||||||
|
// binding while respecifying texture storage). Attachment is set once at
|
||||||
|
// creation and never changes.
|
||||||
|
Uint EnsureCompleteTinyFramebufferId();
|
||||||
|
// A backend texture id is being deleted or respecified: a scratch FBO still
|
||||||
|
// referencing it would hold a dangling attachment (ES only auto-detaches
|
||||||
|
// from the *bound* framebuffer), and a recycled name could false-skip a
|
||||||
|
// re-attach; force a full scrub on next use.
|
||||||
|
void NoteTextureIdDeleted(Uint textureId);
|
||||||
|
// The ES context (and the scratch FBO ids with it) is going away.
|
||||||
|
void OnBackendContextDestroyed();
|
||||||
|
} // namespace ScratchFBOImpl
|
||||||
|
|
||||||
|
// Driver-level GL_PACK_* pixel-store shadow, the readback-side sibling of the
|
||||||
|
// upload path's ScopedDefaultUnpackState (Managers.cpp): the backend PACK state
|
||||||
|
// is written ONLY through ApplyPackState, so scoped helpers can save/restore it
|
||||||
|
// from the shadow instead of glGetIntegerv (which forces a driver pipeline
|
||||||
|
// sync), and redundant glPixelStorei calls no-op. The first Apply/Current call
|
||||||
|
// pins the driver to the shadow by writing all fields once. Invalidated on
|
||||||
|
// MakeCurrent (context may reset). PACK_IMAGE_HEIGHT/SKIP_IMAGES/SWAP_BYTES/
|
||||||
|
// LSB_FIRST have no ES equivalents; readbacks honor them on the CPU from the
|
||||||
|
// frontend context state instead.
|
||||||
|
namespace PixelStoreImpl {
|
||||||
|
struct PackState {
|
||||||
|
GLint Alignment = 4;
|
||||||
|
GLint RowLength = 0;
|
||||||
|
GLint SkipRows = 0;
|
||||||
|
GLint SkipPixels = 0;
|
||||||
|
Bool operator==(const PackState& o) const {
|
||||||
|
return Alignment == o.Alignment && RowLength == o.RowLength && SkipRows == o.SkipRows &&
|
||||||
|
SkipPixels == o.SkipPixels;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void ApplyPackState(const PackState& desired);
|
||||||
|
PackState CurrentPackState();
|
||||||
|
void InvalidatePackStateCache();
|
||||||
|
} // namespace PixelStoreImpl
|
||||||
|
|
||||||
|
// 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 {
|
namespace PrgramImpl {
|
||||||
class BackendProgramObjectImpl {
|
class BackendProgramObjectImpl {
|
||||||
public:
|
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();
|
||||||
~BackendProgramObjectImpl();
|
~BackendProgramObjectImpl();
|
||||||
void SyncToBackend(SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||||
void Use();
|
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 GetBackendProgramId() const { return m_backendProgramId; }
|
||||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
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:
|
private:
|
||||||
|
void CacheResourceLocations(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||||
|
|
||||||
Uint m_backendProgramId = 0;
|
Uint m_backendProgramId = 0;
|
||||||
Uint m_backendGlobalUBOId = 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;
|
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;
|
g_backendProgramObjects;
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
|
|
||||||
@@ -188,9 +646,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
class BackendSamplerObject {
|
class BackendSamplerObject {
|
||||||
public:
|
public:
|
||||||
BackendSamplerObject();
|
BackendSamplerObject();
|
||||||
void SyncToBackend(SharedPtr<MG_State::GLState::SamplerObject>& stateSamplerObject);
|
void SyncToBackend(const SharedPtr<MG_State::GLState::SamplerObject>& stateSamplerObject);
|
||||||
void Bind(Uint unit);
|
void Bind(Uint unit);
|
||||||
Uint GetBackendSamplerId();
|
Uint GetBackendSamplerId() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Uint m_backendSamplerId = 0;
|
Uint m_backendSamplerId = 0;
|
||||||
@@ -203,7 +661,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
extern Array<BackendSamplerObject*, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
|
extern Array<BackendSamplerObject*, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
|
||||||
g_boundSamplersCache;
|
g_boundSamplersCache;
|
||||||
extern UnorderedMap<SharedPtr<MG_State::GLState::SamplerObject>, SharedPtr<BackendSamplerObject>>
|
extern StateBackendObjectRegistry<MG_State::GLState::SamplerObject, BackendSamplerObject>
|
||||||
g_backendSamplerObjects;
|
g_backendSamplerObjects;
|
||||||
} // namespace SamplerImpl
|
} // namespace SamplerImpl
|
||||||
|
|
||||||
@@ -212,8 +670,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
public:
|
public:
|
||||||
BackendRenderbufferObject();
|
BackendRenderbufferObject();
|
||||||
void SyncToBackend(const SharedPtr<MG_State::GLState::RenderbufferObject>& stateRBOObject);
|
void SyncToBackend(const SharedPtr<MG_State::GLState::RenderbufferObject>& stateRBOObject);
|
||||||
Uint GetBackendRenderbufferId() { return m_backendRBOId; }
|
Uint GetBackendRenderbufferId() const { return m_backendRBOId; }
|
||||||
void Bind();
|
void Bind() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Uint m_backendRBOId = 0;
|
Uint m_backendRBOId = 0;
|
||||||
@@ -221,9 +679,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
TextureInternalFormat m_cacheInternalFormat = TextureInternalFormat::Unknown;
|
TextureInternalFormat m_cacheInternalFormat = TextureInternalFormat::Unknown;
|
||||||
Int m_cacheWidth = 0;
|
Int m_cacheWidth = 0;
|
||||||
Int m_cacheHeight = 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;
|
g_backendRenderbufferObjects;
|
||||||
} // namespace RenderbufferImpl
|
} // namespace RenderbufferImpl
|
||||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "DirectGLES.h"
|
#include "DirectGLES.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "Managers.h"
|
#include "Managers.h"
|
||||||
|
#include "MG_Backend/BackendObjects.h"
|
||||||
#include "MG_Util/Converters/GLToMG/FramebufferEnumConverter.h"
|
#include "MG_Util/Converters/GLToMG/FramebufferEnumConverter.h"
|
||||||
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
||||||
|
|
||||||
@@ -17,28 +18,137 @@
|
|||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.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 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 {
|
namespace TextureImpl {
|
||||||
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||||
GLenum* outFormat, GLenum* outType) {
|
GLenum* outFormat, GLenum* outType, TextureTarget target) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
using namespace MobileGL::MG_Util::TextureFormatProcessor;
|
const SizeT targetIndex =
|
||||||
auto options = (g_GLESCapabilities.SupportsNorm16Texture) ? PixelFormatNormalizeOptionBit::None
|
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||||
: PixelFormatNormalizeOptionBit::NoNorm16;
|
GenerateFormatInfo(internalFormat, targetIndex, outInternalFormat, outFormat, outType);
|
||||||
NormalizePixelFormat(MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat), options,
|
}
|
||||||
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 TextureImpl
|
||||||
|
|
||||||
namespace FramebufferImpl {} // namespace FramebufferImpl
|
|
||||||
|
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
String ProcessOutColorLocations(const String& glslCode) {
|
String ProcessOutColorLocations(const String& glslCode) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
@@ -108,14 +218,163 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return result;
|
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) {
|
String RemoveLayoutBinding(const String& glslCode) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#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*)");
|
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*,)");
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String RemoveClipDistanceRedeclaration(const String& glslCode) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
// Adreno rejects any redeclaration of gl_ClipDistance/gl_CullDistance ("reserved
|
||||||
|
// built-in name") even with GL_EXT_clip_cull_distance required, but accepts plain
|
||||||
|
// usage of the builtin. Drop the desktop-style redeclaration line SPIRV-Cross
|
||||||
|
// prints; the "#extension GL_EXT_clip_cull_distance : require" line stays.
|
||||||
|
static const std::regex redeclarationRegex(
|
||||||
|
R"(^\s*(?:out|in)\s+(?:(?:high|medium|low)p\s+)?float\s+gl_(?:Clip|Cull)Distance\[[0-9]+\];\s*$)");
|
||||||
|
|
||||||
|
String result;
|
||||||
|
result.reserve(glslCode.size());
|
||||||
|
SizeT lineStart = 0;
|
||||||
|
Bool firstLine = true;
|
||||||
|
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_match(line, redeclarationRegex)) {
|
||||||
|
if (!firstLine) {
|
||||||
|
result += '\n';
|
||||||
|
}
|
||||||
|
result += line;
|
||||||
|
firstLine = false;
|
||||||
|
}
|
||||||
|
if (lastLine) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lineStart = lineEnd + 1;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
@@ -125,7 +384,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#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());
|
MGLOG_E("-> GLES Error: %s", MG_Util::ConvertGLEnumToString(err).c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +425,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return GL_UNIFORM_BUFFER_BINDING;
|
return GL_UNIFORM_BUFFER_BINDING;
|
||||||
|
|
||||||
case GL_FRAMEBUFFER:
|
case GL_FRAMEBUFFER:
|
||||||
return GL_FRAMEBUFFER_BINDING;
|
|
||||||
case GL_DRAW_FRAMEBUFFER:
|
case GL_DRAW_FRAMEBUFFER:
|
||||||
return GL_DRAW_FRAMEBUFFER_BINDING;
|
return GL_DRAW_FRAMEBUFFER_BINDING;
|
||||||
case GL_READ_FRAMEBUFFER:
|
case GL_READ_FRAMEBUFFER:
|
||||||
@@ -176,7 +434,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return GL_RENDERBUFFER_BINDING;
|
return GL_RENDERBUFFER_BINDING;
|
||||||
|
|
||||||
case GL_VERTEX_ARRAY:
|
case GL_VERTEX_ARRAY:
|
||||||
return GL_VERTEX_ARRAY_BINDING;
|
|
||||||
case GL_VERTEX_ARRAY_BINDING:
|
case GL_VERTEX_ARRAY_BINDING:
|
||||||
return GL_VERTEX_ARRAY_BINDING;
|
return GL_VERTEX_ARRAY_BINDING;
|
||||||
|
|
||||||
@@ -230,4 +487,407 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace Utils
|
} // 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SizeT AlignReadbackRow(SizeT rowBytes, Int alignment) {
|
||||||
|
const SizeT align = alignment > 0 ? static_cast<SizeT>(alignment) : 1;
|
||||||
|
return (rowBytes + align - 1) / align * align;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repacks wide RGBA(_INTEGER) rows into the client's (format, type) layout, honoring the
|
||||||
|
// client-side PACK parameters and the bound pixel-pack buffer. `wide` holds
|
||||||
|
// `sliceHeight * sliceCount` rows of `width` texels (slice-major, tightly stacked),
|
||||||
|
// 4 components x GetReadbackComponentSize(wideType) bytes each.
|
||||||
|
// applyPackImageParams: GL_PACK_IMAGE_HEIGHT / GL_PACK_SKIP_IMAGES apply only to GetTexImage
|
||||||
|
// of 3D/array images; ReadPixels and 2D GetTexImage ignore them (GL 3.3 sections 4.3.1, 6.1.4).
|
||||||
|
// Per the GL addressing rules, slice k row j lands at
|
||||||
|
// SKIP_IMAGES*imageStride + SKIP_ROWS*rowStride + SKIP_PIXELS*pixelBytes
|
||||||
|
// + k*imageStride + j*rowStride, with imageStride = max(IMAGE_HEIGHT, sliceHeight)*rowStride.
|
||||||
|
Bool StoreWideRowsToClient(const Uint8* wide, GLenum wideType, GLsizei width, GLsizei sliceHeight,
|
||||||
|
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type,
|
||||||
|
void* pixels, Bool applyPackImageParams) {
|
||||||
|
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
|
||||||
|
if (dstPixelBytes == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PackedReadbackLayout packedLayout{};
|
||||||
|
const Bool isPackedType = GetPackedReadbackLayout(type, packedLayout);
|
||||||
|
const SizeT dstComponentSize = GetReadbackComponentSize(type);
|
||||||
|
|
||||||
|
const auto& pixelPackBufferObject =
|
||||||
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
||||||
|
|
||||||
|
// Destination layout is computed from the client-side PACK parameters; only the actual pixel
|
||||||
|
// rows are written so skip regions of the destination stay untouched.
|
||||||
|
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
|
||||||
|
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
|
||||||
|
const SizeT dstRowStride = AlignReadbackRow(rowPixels * dstPixelBytes, packParams.Alignment);
|
||||||
|
const SizeT imageRows =
|
||||||
|
applyPackImageParams && packParams.ImageHeight > 0
|
||||||
|
? static_cast<SizeT>(packParams.ImageHeight)
|
||||||
|
: static_cast<SizeT>(sliceHeight);
|
||||||
|
const SizeT dstImageStride = imageRows * dstRowStride;
|
||||||
|
const SizeT skipImages =
|
||||||
|
applyPackImageParams ? static_cast<SizeT>(std::max(packParams.SkipImages, 0)) : SizeT{0};
|
||||||
|
const SizeT dstSkipOffset = skipImages * dstImageStride +
|
||||||
|
static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
|
||||||
|
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
|
||||||
|
const SizeT dstRowBytes = static_cast<SizeT>(width) * dstPixelBytes;
|
||||||
|
|
||||||
|
const SizeT pboBaseOffset = reinterpret_cast<SizeT>(pixels); // with a PBO, `pixels` is an offset
|
||||||
|
if (pixelPackBufferObject) {
|
||||||
|
const SizeT requiredSize = pboBaseOffset + dstSkipOffset +
|
||||||
|
static_cast<SizeT>(sliceCount - 1) * dstImageStride +
|
||||||
|
static_cast<SizeT>(sliceHeight - 1) * dstRowStride + dstRowBytes;
|
||||||
|
if (requiredSize > pixelPackBufferObject->GetSize()) {
|
||||||
|
MGLOG_E("Readback conversion: pixel pack buffer is too small");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SizeT srcComponentSize = GetReadbackComponentSize(wideType);
|
||||||
|
const SizeT srcPixelBytes = 4 * srcComponentSize;
|
||||||
|
Vector<Uint8> convertedRow(dstRowBytes);
|
||||||
|
|
||||||
|
for (GLsizei slice = 0; slice < sliceCount; ++slice) {
|
||||||
|
for (GLsizei row = 0; row < sliceHeight; ++row) {
|
||||||
|
const SizeT flatRow = static_cast<SizeT>(slice) * static_cast<SizeT>(sliceHeight) +
|
||||||
|
static_cast<SizeT>(row);
|
||||||
|
const Uint8* srcRow = wide + flatRow * static_cast<SizeT>(width) * srcPixelBytes;
|
||||||
|
ConvertWideReadbackRow(srcRow, convertedRow.data(), static_cast<SizeT>(width), wideType,
|
||||||
|
mapping, type);
|
||||||
|
|
||||||
|
if (packParams.SwapBytes) {
|
||||||
|
const SizeT groupSize = isPackedType ? packedLayout.byteSize : dstComponentSize;
|
||||||
|
if (groupSize > 1) {
|
||||||
|
for (SizeT offset = 0; offset + groupSize <= dstRowBytes; offset += groupSize) {
|
||||||
|
std::reverse(convertedRow.data() + offset, convertedRow.data() + offset + groupSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SizeT dstOffset = dstSkipOffset + static_cast<SizeT>(slice) * dstImageStride +
|
||||||
|
static_cast<SizeT>(row) * dstRowStride;
|
||||||
|
if (pixelPackBufferObject) {
|
||||||
|
pixelPackBufferObject->WritebackFromBackend({convertedRow.data(), dstRowBytes},
|
||||||
|
pboBaseOffset + dstOffset);
|
||||||
|
} else {
|
||||||
|
Memcpy(static_cast<Uint8*>(pixels) + dstOffset, convertedRow.data(), dstRowBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace ReadbackImpl
|
||||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||||
|
|||||||
@@ -14,15 +14,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
namespace DebugImpl {
|
namespace DebugImpl {
|
||||||
class ErrorLopper {
|
class ErrorLopper {
|
||||||
public:
|
public:
|
||||||
void Loop(std::function<void(GLenum)>);
|
static void Loop(const std::function<void(GLenum)>&);
|
||||||
void Clear();
|
static void Clear();
|
||||||
ErrorLopper();
|
ErrorLopper();
|
||||||
~ErrorLopper();
|
~ErrorLopper();
|
||||||
};
|
};
|
||||||
|
|
||||||
class OpenGLScopeMarker {
|
class OpenGLScopeMarker {
|
||||||
public:
|
public:
|
||||||
explicit OpenGLScopeMarker(String scopeName);
|
explicit OpenGLScopeMarker(const String& scopeName);
|
||||||
~OpenGLScopeMarker();
|
~OpenGLScopeMarker();
|
||||||
};
|
};
|
||||||
} // namespace DebugImpl
|
} // namespace DebugImpl
|
||||||
@@ -35,15 +35,77 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
namespace TextureImpl {
|
namespace TextureImpl {
|
||||||
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
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 TextureImpl
|
||||||
|
|
||||||
namespace FramebufferImpl {} // namespace FramebufferImpl
|
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);
|
||||||
|
|
||||||
|
// Stores wide RGBA(_INTEGER) rows into the client pointer or the bound PACK pixel buffer,
|
||||||
|
// honoring the client-side PACK pixel-store parameters (row length, alignment, skips,
|
||||||
|
// swap-bytes, and - when applyPackImageParams - image height/skip images). Shared by the
|
||||||
|
// DirectGLES and DirectVulkan readback conversion paths.
|
||||||
|
Bool StoreWideRowsToClient(const Uint8* wide, GLenum wideType, GLsizei width, GLsizei sliceHeight,
|
||||||
|
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type,
|
||||||
|
void* pixels, Bool applyPackImageParams);
|
||||||
|
} // namespace ReadbackImpl
|
||||||
|
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
String ProcessOutColorLocations(const String& glslCode);
|
String ProcessOutColorLocations(const String& glslCode);
|
||||||
String ForceSupporterOutput(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);
|
String RemoveLayoutBinding(const String& glslCode);
|
||||||
|
String RemoveClipDistanceRedeclaration(const String& glslCode);
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|||||||
@@ -9,16 +9,334 @@
|
|||||||
#include "BackendObject_DirectVulkan.h"
|
#include "BackendObject_DirectVulkan.h"
|
||||||
#include "MG_Backend/BackendObject.h"
|
#include "MG_Backend/BackendObject.h"
|
||||||
#include "DirectVulkan.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 MobileGL::MG_Backend::DirectVulkan {
|
||||||
namespace {
|
namespace {
|
||||||
|
Bool IsR11G11B10FFallbackEnabled() {
|
||||||
|
return MG_Config::Features.MagmaR11G11B10FFallback;
|
||||||
|
}
|
||||||
|
|
||||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
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;
|
||||||
|
// Legacy low-bit-depth formats with no (or rarely supported) native Vulkan
|
||||||
|
// encoding; a wider normalized fallback keeps at least the required precision.
|
||||||
|
case TextureInternalFormat::R3G3B2:
|
||||||
|
case TextureInternalFormat::RGB4:
|
||||||
|
case TextureInternalFormat::RGB5:
|
||||||
|
case TextureInternalFormat::RGBA2:
|
||||||
|
case TextureInternalFormat::RGBA4:
|
||||||
|
case TextureInternalFormat::RGB5A1:
|
||||||
|
return TextureInternalFormat::RGBA8;
|
||||||
|
case TextureInternalFormat::RGB10:
|
||||||
|
return TextureInternalFormat::RGB10A2;
|
||||||
|
case TextureInternalFormat::RGB12:
|
||||||
|
case TextureInternalFormat::RGBA12:
|
||||||
|
return TextureInternalFormat::RGBA16;
|
||||||
|
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
|
} // 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() = default;
|
||||||
|
|
||||||
|
BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {}
|
||||||
|
|
||||||
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
||||||
if (!m_windowHandle.Handle) {
|
if (!m_windowHandle.Handle) {
|
||||||
MGLOG_E("Cannot initialize DirectVulkan window surface: native window handle is null");
|
MGLOG_E("Cannot initialize DirectVulkan window surface: native window handle is null");
|
||||||
@@ -27,7 +345,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
auto nativeWindow = reinterpret_cast<NativeWindowType>(m_windowHandle.Handle);
|
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);
|
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();
|
pVulkanRenderer->Initialize();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -46,8 +381,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
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();
|
UpdateDynamicBackendParameters();
|
||||||
|
UpdateAdvertisedExtensions();
|
||||||
|
PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
|
||||||
|
MutableFormatCapabilities());
|
||||||
|
PrintFormatCapabilities(GetFormatCapabilities());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,40 +403,49 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return BackendObject::InitializeEGLDisplay(dpy, major, minor);
|
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);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
if (!m_initialized) {
|
if (!m_initialized) {
|
||||||
MGLOG_E("DirectVulkan backend not initialized");
|
MGLOG_E("DirectVulkan backend not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (handle.Backend != WindowBackend::Android || !handle.Handle) {
|
if (!handle.Handle || (handle.Backend != WindowBackend::Android &&
|
||||||
MGLOG_E("DirectVulkan backend only supports Android native windows");
|
handle.Backend != WindowBackend::X11 &&
|
||||||
|
handle.Backend != WindowBackend::MetalLayer &&
|
||||||
|
handle.Backend != WindowBackend::Win32)) {
|
||||||
|
MGLOG_E("DirectVulkan backend only supports Android, X11, CAMetalLayer, and Win32 native windows");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bool sameHandle =
|
return RegisterEGLWindowSurface(surface, handle);
|
||||||
m_eglWindowSurfaceInitialized && m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle;
|
}
|
||||||
if (sameHandle) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_eglWindowSurfaceInitialized || pVulkanRenderer) {
|
Bool BackendObject_DirectVulkan::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||||
pVulkanRenderer.reset();
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
ResetEGLRuntimeState();
|
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) {
|
Bool BackendObject_DirectVulkan::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
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);
|
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,33 +458,95 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return BackendObject::SwapEGLBuffers(dpy, draw);
|
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 {
|
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
||||||
static RendererInfo RendererInfo = {
|
return m_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String BackendObject_DirectVulkan::GetBackendAPIVersionString() const {
|
String BackendObject_DirectVulkan::GetBackendAPIVersionString() const {
|
||||||
if (!m_initialized) {
|
if (!m_initialized) {
|
||||||
return "<uninitialized DirectVulkan backend>";
|
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:
|
// Format:
|
||||||
// <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version>
|
// <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version>
|
||||||
String str = m_vulkanCaps.DeviceName + ", Vulkan " + m_vulkanCaps.VulkanAPIVersion.toString() + ", Driver " +
|
return deviceName + ", Vulkan " + vulkanApiVersionString + ", Driver " + driverVersionString;
|
||||||
m_vulkanCaps.DriverVersionString;
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BackendType BackendObject_DirectVulkan::GetBackendType() const {
|
BackendType BackendObject_DirectVulkan::GetBackendType() const {
|
||||||
@@ -146,10 +561,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.DrawArrays = DrawArrays;
|
funcsTable.GL.DrawArrays = DrawArrays;
|
||||||
funcsTable.GL.DrawElements = DrawElements;
|
funcsTable.GL.DrawElements = DrawElements;
|
||||||
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
||||||
|
funcsTable.GL.MultiDrawArrays = MultiDrawArrays;
|
||||||
funcsTable.GL.MultiDrawElements = MultiDrawElements;
|
funcsTable.GL.MultiDrawElements = MultiDrawElements;
|
||||||
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
||||||
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
||||||
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
||||||
|
funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
|
||||||
|
funcsTable.GL.MultiDrawArraysIndirectCount = MultiDrawArraysIndirectCount;
|
||||||
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
|
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
|
||||||
funcsTable.GL.DrawRangeElements = DrawRangeElements;
|
funcsTable.GL.DrawRangeElements = DrawRangeElements;
|
||||||
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
|
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
|
||||||
@@ -165,12 +583,51 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.ClearBufferfv = ClearBufferfv;
|
funcsTable.GL.ClearBufferfv = ClearBufferfv;
|
||||||
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
||||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||||
|
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||||
|
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||||
|
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||||
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
||||||
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
||||||
|
funcsTable.GL.CopyImageSubData = CopyImageSubData;
|
||||||
funcsTable.GL.GenerateMipmap = GenerateMipmap;
|
funcsTable.GL.GenerateMipmap = GenerateMipmap;
|
||||||
funcsTable.GL.ReadPixels = ReadPixels;
|
funcsTable.GL.ReadPixels = ReadPixels;
|
||||||
funcsTable.GL.GetTexImage = GetTexImage;
|
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;
|
funcsTableInitialized = true;
|
||||||
}
|
}
|
||||||
return funcsTable;
|
return funcsTable;
|
||||||
@@ -180,7 +637,204 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return m_dynamicParameters;
|
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() {
|
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.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);
|
||||||
|
}
|
||||||
|
switch (m_vulkanCaps.VendorId) {
|
||||||
|
case 0x5143u: // VK_VENDOR_ID: Qualcomm
|
||||||
|
m_dynamicParameters.GpuVendor = GpuVendorKind::Qualcomm;
|
||||||
|
break;
|
||||||
|
case 0x13B5u: // ARM
|
||||||
|
m_dynamicParameters.GpuVendor = GpuVendorKind::Arm;
|
||||||
|
break;
|
||||||
|
case 0x10DEu: // NVIDIA
|
||||||
|
m_dynamicParameters.GpuVendor = GpuVendorKind::Nvidia;
|
||||||
|
break;
|
||||||
|
case 0x1002u: // AMD
|
||||||
|
m_dynamicParameters.GpuVendor = GpuVendorKind::Amd;
|
||||||
|
break;
|
||||||
|
case 0x8086u: // Intel
|
||||||
|
m_dynamicParameters.GpuVendor = GpuVendorKind::Intel;
|
||||||
|
break;
|
||||||
|
case 0x1010u: // Imagination
|
||||||
|
m_dynamicParameters.GpuVendor = GpuVendorKind::ImgTec;
|
||||||
|
break;
|
||||||
|
case 0x10005u: // Mesa software (lavapipe)
|
||||||
|
case 0x1AE0u: // Google (SwiftShader)
|
||||||
|
m_dynamicParameters.GpuVendor = GpuVendorKind::Software;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
m_dynamicParameters.GpuVendor = GpuVendorKind::Unknown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -12,29 +12,73 @@
|
|||||||
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
|
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
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 {
|
class BackendObject_DirectVulkan : public BackendObject {
|
||||||
public:
|
public:
|
||||||
|
BackendObject_DirectVulkan();
|
||||||
~BackendObject_DirectVulkan() override;
|
~BackendObject_DirectVulkan() override;
|
||||||
|
|
||||||
void Initialize() override;
|
void Initialize() override;
|
||||||
Bool InitWindowSurface() override;
|
Bool InitWindowSurface() override;
|
||||||
Bool InitCapabilities() override;
|
Bool InitCapabilities() override;
|
||||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) 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 MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||||
|
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||||
|
void ReleaseEGLResources() override;
|
||||||
|
|
||||||
const RendererInfo& GetRendererInfo() const override;
|
const RendererInfo& GetRendererInfo() const override;
|
||||||
String GetBackendAPIVersionString() const override;
|
String GetBackendAPIVersionString() const override;
|
||||||
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
|
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
|
||||||
const DynamicBackendParameters& GetDynamicParameters() const override;
|
const DynamicBackendParameters& GetDynamicParameters() const override;
|
||||||
BackendType GetBackendType() const override;
|
BackendType GetBackendType() const override;
|
||||||
|
void ApplyVulkanCapabilitiesForTesting(const MG_External::VulkanCapabilities& capabilities);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||||
|
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||||
|
void UpdateAdvertisedExtensions();
|
||||||
void UpdateDynamicBackendParameters();
|
void UpdateDynamicBackendParameters();
|
||||||
|
|
||||||
Bool m_initialized = false;
|
Bool m_initialized = false;
|
||||||
DynamicBackendParameters m_dynamicParameters;
|
DynamicBackendParameters m_dynamicParameters;
|
||||||
MG_External::VulkanCapabilities m_vulkanCaps;
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,25 +8,44 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <MG_Backend/BackendObject.h>
|
||||||
#include "Renderer/VulkanRenderer.h"
|
#include "Renderer/VulkanRenderer.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
extern UniquePtr<VulkanRenderer> pVulkanRenderer;
|
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 ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* 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 Clear(GLbitfield mask);
|
||||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
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,
|
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount);
|
GLsizei drawcount);
|
||||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount, const GLint* basevertex);
|
GLsizei drawcount, const GLint* basevertex);
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
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 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,
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex);
|
const void* indices, GLint basevertex);
|
||||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
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 DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
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,
|
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height, GLint border);
|
GLsizei height, GLint border);
|
||||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height);
|
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 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 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 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();
|
void Present();
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // 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);
|
Destroy(device, commandPool);
|
||||||
m_frames.assign(frameCount, {});
|
m_frames.assign(frameCount, {});
|
||||||
currentFrameIndex = 0;
|
currentFrameIndex = 0;
|
||||||
|
m_device = device;
|
||||||
|
m_commandPool = commandPool;
|
||||||
|
|
||||||
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
||||||
VkCommandBufferAllocateInfo allocInfo{};
|
VkCommandBufferAllocateInfo allocInfo{};
|
||||||
@@ -55,10 +57,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
DestroySwapchainSemaphores(device);
|
DestroySwapchainSemaphores(device);
|
||||||
if (device != VK_NULL_HANDLE && commandPool != VK_NULL_HANDLE && !m_frames.empty()) {
|
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());
|
vkFreeCommandBuffers(device, commandPool, frameCount, commandBuffers.data());
|
||||||
}
|
}
|
||||||
m_frames.clear();
|
m_frames.clear();
|
||||||
currentFrameIndex = 0;
|
currentFrameIndex = 0;
|
||||||
|
m_device = VK_NULL_HANDLE;
|
||||||
|
m_commandPool = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameContext::FrameData& FrameContext::GetCurrent() {
|
FrameContext::FrameData& FrameContext::GetCurrent() {
|
||||||
@@ -97,6 +104,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VK_VERIFY(vkBeginCommandBuffer(frame.commandBuffer, &beginInfo), "BeginCommandRecording, vkBeginCommandBuffer");
|
VK_VERIFY(vkBeginCommandBuffer(frame.commandBuffer, &beginInfo), "BeginCommandRecording, vkBeginCommandBuffer");
|
||||||
|
|
||||||
frame.isCommandRecording = true;
|
frame.isCommandRecording = true;
|
||||||
|
if (m_recordingObserver != nullptr) {
|
||||||
|
m_recordingObserver->OnFrameCommandRecordingBegan(frame.commandBuffer);
|
||||||
|
}
|
||||||
return frame.commandBuffer;
|
return frame.commandBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,9 +188,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
||||||
packet.commandBuffer = frame.commandBuffer;
|
packet.commandBuffer = frame.commandBuffer;
|
||||||
|
|
||||||
packet.submitInfo.waitSemaphoreCount = 1;
|
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
|
||||||
packet.submitInfo.pWaitSemaphores = &packet.waitSemaphore;
|
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
|
||||||
packet.submitInfo.pWaitDstStageMask = &packet.waitDstStageMask;
|
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
|
||||||
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
|
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
|
||||||
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
|
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
|
||||||
packet.submitInfo.signalSemaphoreCount = 1;
|
packet.submitInfo.signalSemaphoreCount = 1;
|
||||||
@@ -188,18 +198,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameContext::PresentInfoPacket FrameContext::GetPresentInfo(VkSwapchainKHR swapchain, const Uint32& imageIndex) const {
|
FrameContext::PresentInfoPacket FrameContext::GetPresentInfo(VkSwapchainKHR swapchain, Uint32 imageIndex) const {
|
||||||
AssertValidSwapchainImageIndex(imageIndex);
|
AssertValidSwapchainImageIndex(imageIndex);
|
||||||
PresentInfoPacket packet{};
|
PresentInfoPacket packet{};
|
||||||
packet.waitSemaphore = m_swapchainImageRenderFinishedSemaphores[imageIndex];
|
packet.waitSemaphore = m_swapchainImageRenderFinishedSemaphores[imageIndex];
|
||||||
packet.swapchain = swapchain;
|
packet.swapchain = swapchain;
|
||||||
packet.imageIndex = &imageIndex;
|
packet.imageIndex = imageIndex;
|
||||||
|
|
||||||
packet.presentInfo.waitSemaphoreCount = 1;
|
packet.presentInfo.waitSemaphoreCount = 1;
|
||||||
packet.presentInfo.pWaitSemaphores = &packet.waitSemaphore;
|
packet.presentInfo.pWaitSemaphores = &packet.waitSemaphore;
|
||||||
packet.presentInfo.swapchainCount = 1;
|
packet.presentInfo.swapchainCount = 1;
|
||||||
packet.presentInfo.pSwapchains = &packet.swapchain;
|
packet.presentInfo.pSwapchains = &packet.swapchain;
|
||||||
packet.presentInfo.pImageIndices = packet.imageIndex;
|
packet.presentInfo.pImageIndices = &packet.imageIndex;
|
||||||
packet.presentInfo.pResults = nullptr;
|
packet.presentInfo.pResults = nullptr;
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
@@ -211,14 +221,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
return result;
|
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) {
|
if (result != VK_SUCCESS) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
frame.imageAvailableSemaphoreConsumed = false;
|
||||||
&outImageIndex);
|
return vkResetFences(device, 1, &frame.imageInFlightFence);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
||||||
@@ -229,6 +243,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return static_cast<Uint32>(m_frames.size());
|
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 {
|
void FrameContext::AssertValidFrameIndex(Uint32 frameIndex) const {
|
||||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "FrameContext index out of range");
|
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "FrameContext index out of range");
|
||||||
}
|
}
|
||||||
@@ -260,6 +311,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
frame.hasCommandBufferRecorded = false;
|
frame.hasCommandBufferRecorded = false;
|
||||||
frame.isCommandRecording = false;
|
frame.isCommandRecording = false;
|
||||||
|
frame.imageAvailableSemaphoreConsumed = false;
|
||||||
return VK_SUCCESS;
|
return VK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,5 +329,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frame.imageAvailableSemaphore = VK_NULL_HANDLE;
|
frame.imageAvailableSemaphore = VK_NULL_HANDLE;
|
||||||
frame.isCommandRecording = false;
|
frame.isCommandRecording = false;
|
||||||
frame.hasCommandBufferRecorded = false;
|
frame.hasCommandBufferRecorded = false;
|
||||||
|
frame.imageAvailableSemaphoreConsumed = false;
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -14,6 +14,17 @@
|
|||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
class FrameContext {
|
class FrameContext {
|
||||||
public:
|
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 {
|
struct SubmitInfoPacket {
|
||||||
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||||
@@ -25,7 +36,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
struct PresentInfoPacket {
|
struct PresentInfoPacket {
|
||||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||||
const Uint32* imageIndex = nullptr;
|
Uint32 imageIndex = 0;
|
||||||
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,6 +46,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
||||||
Bool isCommandRecording = false;
|
Bool isCommandRecording = false;
|
||||||
Bool hasCommandBufferRecorded = 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);
|
VkResult Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount);
|
||||||
@@ -53,13 +72,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
||||||
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||||
SubmitInfoPacket GetSubmitInfo(Bool shouldSubmitCommandBuffer, Uint32 swapchainImageIndex) const;
|
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,
|
VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
||||||
Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE);
|
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 GetCurrentFrameIndex() const;
|
||||||
Uint32 GetFrameCount() const;
|
Uint32 GetFrameCount() const;
|
||||||
|
|
||||||
|
// Observer may be null (no notifications). Not owned.
|
||||||
|
void SetRecordingObserver(IRecordingObserver* observer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AssertValidFrameIndex(Uint32 frameIndex) const;
|
void AssertValidFrameIndex(Uint32 frameIndex) const;
|
||||||
void AssertValidSwapchainImageIndex(Uint32 imageIndex) const;
|
void AssertValidSwapchainImageIndex(Uint32 imageIndex) const;
|
||||||
@@ -68,9 +96,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const VkSemaphoreCreateInfo& semaphoreInfo,
|
const VkSemaphoreCreateInfo& semaphoreInfo,
|
||||||
const VkFenceCreateInfo& fenceInfo);
|
const VkFenceCreateInfo& fenceInfo);
|
||||||
void DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex);
|
void DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex);
|
||||||
|
void FreeRetiredCommandBuffers(FrameData& frame);
|
||||||
|
|
||||||
Vector<FrameData> m_frames;
|
Vector<FrameData> m_frames;
|
||||||
Vector<VkSemaphore> m_swapchainImageRenderFinishedSemaphores;
|
Vector<VkSemaphore> m_swapchainImageRenderFinishedSemaphores;
|
||||||
Uint32 currentFrameIndex = 0;
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -8,9 +8,188 @@
|
|||||||
|
|
||||||
#include "PipelineFactory.h"
|
#include "PipelineFactory.h"
|
||||||
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called once, before any pipeline is created: the flag is not part of the
|
||||||
|
// pipeline hash, so flipping it mid-life would serve cached pipelines built under the
|
||||||
|
// old value.
|
||||||
|
void PipelineFactory::SetSuppressBlendedDepthWrite(Bool enabled) {
|
||||||
|
s_suppressBlendedDepthWrite = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(MG_Config::QuirkOverride quirkOverride,
|
||||||
|
Uint32 vendorId) {
|
||||||
|
static constexpr Uint32 kVendorIdQualcomm = 0x5143;
|
||||||
|
switch (quirkOverride) {
|
||||||
|
case MG_Config::QuirkOverride::ForceOn:
|
||||||
|
return true;
|
||||||
|
case MG_Config::QuirkOverride::ForceOff:
|
||||||
|
return false;
|
||||||
|
case MG_Config::QuirkOverride::Auto:
|
||||||
|
default:
|
||||||
|
return vendorId == kVendorIdQualcomm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// MIN/MAX extremum blending: the signature of a depth-bounds accumulation pass
|
||||||
|
// (MC 26.3 OIT writes vec4(-linD, linD, deviceZ, 0) under GL_MAX while writing
|
||||||
|
// depth for its equality chain). MIN/MAX ignore blend factors per the Vulkan spec.
|
||||||
|
//
|
||||||
|
// Deliberately the ONLY shape stripped. A quirk should touch as little unrelated
|
||||||
|
// content as possible, and a trace sweep of every fixture showed the wider
|
||||||
|
// alternatives all cost more than they fix:
|
||||||
|
// - additive ONE+ONE with a depth write matched zero draws of the 26.3 chain
|
||||||
|
// (its transmittance/accumulate passes disable depth writes themselves) - the
|
||||||
|
// only real content it caught was harmless additive glow effects (Create);
|
||||||
|
// - sorted-transparency "over" blends (SRC_ALPHA-style) are order-dependent,
|
||||||
|
// drawn once per surface, and rely on their depth writes for occlusion;
|
||||||
|
// - separate-alpha accumulation over an over-blending color channel has no
|
||||||
|
// known pairing with a depth-equality chain (color channel only, see tests).
|
||||||
|
// If a future workload pairs another blend shape with an equality chain, widen
|
||||||
|
// this with that evidence in hand rather than pre-emptively.
|
||||||
|
Bool IsAccumulationBlend(const VkPipelineColorBlendAttachmentState& attachment) {
|
||||||
|
return attachment.colorBlendOp == VK_BLEND_OP_MIN ||
|
||||||
|
attachment.colorBlendOp == VK_BLEND_OP_MAX;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Bool PipelineFactory::ShouldSuppressDepthWrite(const PipelineCreatePayload& payload) {
|
||||||
|
if (!payload.depthWriteEnable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// A shader that assigns gl_FragDepth supplies depth itself rather than taking the
|
||||||
|
// pipeline's interpolated Z, so a driver that varies the vertex position math
|
||||||
|
// between pipelines cannot desynchronize it. (A gl_FragDepth = gl_FragCoord.z
|
||||||
|
// passthrough is the exception that stays exposed; no known content pairs one with
|
||||||
|
// an equality chain, and 26.3's composite is a genuine computed-depth writer.)
|
||||||
|
if (payload.fragmentReplacesDepth) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
||||||
|
const VkPipelineColorBlendAttachmentState& attachment = payload.colorBlendAttachments[i];
|
||||||
|
if (attachment.blendEnable != VK_TRUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// All color writes masked: blending is moot (depth-prepass pattern that left
|
||||||
|
// GL_BLEND enabled); stripping the depth write would delete the whole prepass.
|
||||||
|
if (attachment.colorWriteMask == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Any attachment qualifies, not just attachment 0: the 26.3 transmittance pass
|
||||||
|
// accumulates into a 2-target MRT and must stay stripped.
|
||||||
|
if (IsAccumulationBlend(attachment)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
PipelineFactory::~PipelineFactory() {
|
PipelineFactory::~PipelineFactory() {
|
||||||
DestroyAll();
|
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 {
|
PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const {
|
||||||
@@ -19,17 +198,44 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
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.pipelineLayout, sizeof(payload.pipelineLayout)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
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.subpass, sizeof(payload.subpass)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
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.depthTestEnable, sizeof(payload.depthTestEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
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.depthCompareOp, sizeof(payload.depthCompareOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.blendEnable, sizeof(payload.blendEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOp, sizeof(payload.logicOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.srcColorBlendFactor, sizeof(payload.srcColorBlendFactor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.dstColorBlendFactor, sizeof(payload.dstColorBlendFactor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.srcAlphaBlendFactor, sizeof(payload.srcAlphaBlendFactor)));
|
XXHASH_VERIFY(
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.dstAlphaBlendFactor, sizeof(payload.dstAlphaBlendFactor)));
|
XXH64_update(m_hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorWriteMask, sizeof(payload.colorWriteMask)));
|
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)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth)));
|
||||||
|
if (payload.colorAttachmentCount > 0) {
|
||||||
|
XXHASH_VERIFY(XXH64_update(
|
||||||
|
m_hashState,
|
||||||
|
payload.colorBlendAttachments.data(),
|
||||||
|
sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount));
|
||||||
|
}
|
||||||
return XXH64_digest(m_hashState);
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +265,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
||||||
MOBILEGL_ASSERT(payload.pipelineLayout != VK_NULL_HANDLE, "PipelineFactory: pipelineLayout 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.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[] = {
|
static constexpr VkDynamicState kDynamicStates[] = {
|
||||||
VK_DYNAMIC_STATE_VIEWPORT,
|
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{};
|
VkPipelineDynamicStateCreateInfo dynamicState{};
|
||||||
@@ -72,39 +292,66 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||||
ia.topology = payload.topology;
|
ia.topology = payload.topology;
|
||||||
|
ia.primitiveRestartEnable = payload.primitiveRestartEnable ? VK_TRUE : VK_FALSE;
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
||||||
vpci.viewportCount = 1;
|
vpci.viewportCount = 1;
|
||||||
vpci.scissorCount = 1;
|
vpci.scissorCount = 1;
|
||||||
|
|
||||||
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
||||||
raster.polygonMode = VK_POLYGON_MODE_FILL;
|
raster.polygonMode = payload.polygonMode;
|
||||||
raster.cullMode = VK_CULL_MODE_NONE;
|
raster.cullMode = payload.cullMode;
|
||||||
raster.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
raster.frontFace = payload.frontFace;
|
||||||
|
raster.depthBiasEnable = payload.depthBiasEnable ? VK_TRUE : VK_FALSE;
|
||||||
|
raster.rasterizerDiscardEnable = payload.rasterizerDiscardEnable ? VK_TRUE : VK_FALSE;
|
||||||
raster.lineWidth = 1.0f;
|
raster.lineWidth = 1.0f;
|
||||||
|
|
||||||
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
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};
|
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||||
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
||||||
depthStencil.depthWriteEnable = payload.depthWriteEnable ? VK_TRUE : VK_FALSE;
|
depthStencil.depthWriteEnable = payload.depthWriteEnable ? VK_TRUE : VK_FALSE;
|
||||||
depthStencil.depthCompareOp = payload.depthCompareOp;
|
depthStencil.depthCompareOp = payload.depthCompareOp;
|
||||||
depthStencil.depthBoundsTestEnable = VK_FALSE;
|
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{};
|
Vector<VkPipelineColorBlendAttachmentState> colorAttachments(payload.colorAttachmentCount);
|
||||||
colorAttach.colorWriteMask = payload.colorWriteMask;
|
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
||||||
colorAttach.blendEnable = payload.blendEnable ? VK_TRUE : VK_FALSE;
|
colorAttachments[i] = payload.colorBlendAttachments[i];
|
||||||
colorAttach.srcColorBlendFactor = payload.srcColorBlendFactor;
|
}
|
||||||
colorAttach.dstColorBlendFactor = payload.dstColorBlendFactor;
|
// Suppress depth writes on accumulation-blended pipelines when the active driver
|
||||||
colorAttach.colorBlendOp = VK_BLEND_OP_ADD;
|
// cannot keep vertex positions invariant across the pipelines of a multi-pass
|
||||||
colorAttach.srcAlphaBlendFactor = payload.srcAlphaBlendFactor;
|
// depth-equality chain (see SetSuppressBlendedDepthWrite). The decision is narrowed
|
||||||
colorAttach.dstAlphaBlendFactor = payload.dstAlphaBlendFactor;
|
// in ShouldSuppressDepthWrite: sorted-transparency "over" blends (vanilla MC water),
|
||||||
colorAttach.alphaBlendOp = VK_BLEND_OP_ADD;
|
// gl_FragDepth writers, and masked-out attachments keep their depth writes.
|
||||||
|
// This bakes the decision into the pipeline, which only works because depth write is
|
||||||
|
// static state here - adding VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE to kDynamicStates
|
||||||
|
// would let the record-time value override it and silently disable the quirk.
|
||||||
|
if (s_suppressBlendedDepthWrite && ShouldSuppressDepthWrite(payload)) {
|
||||||
|
depthStencil.depthWriteEnable = VK_FALSE;
|
||||||
|
}
|
||||||
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
||||||
blend.attachmentCount = 1;
|
blend.logicOpEnable = payload.logicOpEnable ? VK_TRUE : VK_FALSE;
|
||||||
blend.pAttachments = &colorAttach;
|
blend.logicOp = payload.logicOp;
|
||||||
|
blend.attachmentCount = payload.colorAttachmentCount;
|
||||||
|
blend.pAttachments = colorAttachments.empty() ? nullptr : colorAttachments.data();
|
||||||
|
|
||||||
VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||||
gpi.stageCount = static_cast<Uint32>(payload.stages->size());
|
gpi.stageCount = static_cast<Uint32>(payload.stages->size());
|
||||||
@@ -122,8 +369,52 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
gpi.subpass = payload.subpass;
|
gpi.subpass = payload.subpass;
|
||||||
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &gpi, nullptr, &pipeline),
|
const VkResult result = vkCreateGraphicsPipelines(m_device, m_pipelineCache, 1, &gpi, nullptr, &pipeline);
|
||||||
"vkCreateGraphicsPipelines");
|
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;
|
return pipeline;
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "../VkIncludes.h"
|
#include "../VkIncludes.h"
|
||||||
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
@@ -18,29 +19,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|
||||||
struct PipelineCreatePayload {
|
struct PipelineCreatePayload {
|
||||||
|
static constexpr Uint32 kMaxColorAttachments = MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS;
|
||||||
|
|
||||||
HashType programHash = 0;
|
HashType programHash = 0;
|
||||||
HashType vertexInputHash = 0;
|
HashType vertexInputHash = 0;
|
||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||||
|
Uint32 colorAttachmentCount = 1;
|
||||||
|
VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
Uint32 subpass = 0;
|
Uint32 subpass = 0;
|
||||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
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 depthTestEnable = false;
|
||||||
Bool depthWriteEnable = false;
|
Bool depthWriteEnable = false;
|
||||||
|
Bool depthBiasEnable = false;
|
||||||
|
Bool rasterizerDiscardEnable = false;
|
||||||
|
Bool logicOpEnable = false;
|
||||||
|
Bool stencilTestEnable = false;
|
||||||
VkCompareOp depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
VkCompareOp depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||||
Bool blendEnable = false;
|
VkLogicOp logicOp = VK_LOGIC_OP_COPY;
|
||||||
VkBlendFactor srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
VkStencilOp frontStencilFailOp = VK_STENCIL_OP_KEEP;
|
||||||
VkBlendFactor dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
|
VkStencilOp frontStencilPassOp = VK_STENCIL_OP_KEEP;
|
||||||
VkBlendFactor srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
VkStencilOp frontStencilDepthFailOp = VK_STENCIL_OP_KEEP;
|
||||||
VkBlendFactor dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
VkCompareOp frontStencilCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||||
VkColorComponentFlags colorWriteMask =
|
VkStencilOp backStencilFailOp = VK_STENCIL_OP_KEEP;
|
||||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
VkStencilOp backStencilPassOp = VK_STENCIL_OP_KEEP;
|
||||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
VkStencilOp backStencilDepthFailOp = VK_STENCIL_OP_KEEP;
|
||||||
|
VkCompareOp backStencilCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||||
|
// The fragment module writes gl_FragDepth (SPIR-V DepthReplacing); exempts the
|
||||||
|
// pipeline from the blended depth-write quirk (see ShouldSuppressDepthWrite).
|
||||||
|
Bool fragmentReplacesDepth = false;
|
||||||
|
Array<VkPipelineColorBlendAttachmentState, kMaxColorAttachments> colorBlendAttachments{};
|
||||||
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
||||||
const VkPipelineVertexInputStateCreateInfo* vertexInputState = nullptr;
|
const VkPipelineVertexInputStateCreateInfo* vertexInputState = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit PipelineFactory(VkDevice device, const VulkanRendererConfig& config):
|
explicit PipelineFactory(VkDevice device, const VulkanRendererConfig& config);
|
||||||
m_device(device), m_config(config) {}
|
|
||||||
~PipelineFactory();
|
~PipelineFactory();
|
||||||
PipelineFactory(const PipelineFactory&) = delete;
|
PipelineFactory(const PipelineFactory&) = delete;
|
||||||
|
|
||||||
@@ -48,12 +65,35 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPipeline GetOrCreatePipeline(const PipelineCreatePayload& payload);
|
VkPipeline GetOrCreatePipeline(const PipelineCreatePayload& payload);
|
||||||
void DestroyAll();
|
void DestroyAll();
|
||||||
|
|
||||||
|
// Driver quirk: suppress depth writes on accumulation-blended pipelines. Multi-pass
|
||||||
|
// depth-equality rendering (a blended prepass writes depth that later passes re-test
|
||||||
|
// with an equality-inclusive compare on the re-rasterized geometry) requires
|
||||||
|
// cross-pipeline position invariance that some mobile compilers do not provide, even
|
||||||
|
// with the SPIR-V Invariant decoration; whole primitives then drop out of the later
|
||||||
|
// passes. Only MIN/MAX extremum blends are stripped - the signature of such a
|
||||||
|
// chain's depth-bounds pass (MC 26.3 OIT), and per a fixture-wide trace sweep the
|
||||||
|
// only depth-writing shape the chain actually uses - so every other blend
|
||||||
|
// (sorted-transparency "over" like vanilla MC water, additive glows, ...) keeps
|
||||||
|
// its depth writes. Set at renderer initialization based on the active driver.
|
||||||
|
static void SetSuppressBlendedDepthWrite(Bool enabled);
|
||||||
|
static Bool IsSuppressBlendedDepthWriteEnabled() { return s_suppressBlendedDepthWrite; }
|
||||||
|
// Device gate for the quirk: ForceOn/ForceOff bypass detection, Auto enables it on
|
||||||
|
// the known-affected vendor (Qualcomm).
|
||||||
|
static Bool ShouldSuppressBlendedDepthWriteForDevice(MG_Config::QuirkOverride quirkOverride,
|
||||||
|
Uint32 vendorId);
|
||||||
|
// Pure per-pipeline strip decision (exempts gl_FragDepth writers, masked-out and
|
||||||
|
// non-accumulation blends); combined with the device flag in CreatePipeline. Static
|
||||||
|
// and payload-only so tests can pin the contract without a VkDevice.
|
||||||
|
static Bool ShouldSuppressDepthWrite(const PipelineCreatePayload& payload);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkPipeline CreatePipeline(const PipelineCreatePayload& payload) const;
|
VkPipeline CreatePipeline(const PipelineCreatePayload& payload) const;
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
const VulkanRendererConfig& m_config;
|
const VulkanRendererConfig& m_config;
|
||||||
|
VkPipelineCache m_pipelineCache = VK_NULL_HANDLE;
|
||||||
UnorderedMap<HashType, VkPipeline> m_cache;
|
UnorderedMap<HashType, VkPipeline> m_cache;
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
|
static inline Bool s_suppressBlendedDepthWrite = false;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,11 +11,30 @@
|
|||||||
#include "../VkIncludes.h"
|
#include "../VkIncludes.h"
|
||||||
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||||
#include "MG_State/GLState/ProgramState/ShaderObject.h"
|
#include "MG_State/GLState/ProgramState/ShaderObject.h"
|
||||||
|
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <spirv_reflect.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
enum class SamplerNumericDomain : Uint8 {
|
||||||
|
Unknown = 0,
|
||||||
|
Float,
|
||||||
|
SignedInteger,
|
||||||
|
UnsignedInteger,
|
||||||
|
};
|
||||||
|
|
||||||
class ProgramFactory {
|
class ProgramFactory {
|
||||||
public:
|
public:
|
||||||
|
enum class DescriptorBindingKind : Uint8 {
|
||||||
|
None = 0,
|
||||||
|
UniformBufferDynamic,
|
||||||
|
CombinedImageSampler,
|
||||||
|
UniformTexelBuffer,
|
||||||
|
StorageBuffer,
|
||||||
|
StorageImage
|
||||||
|
};
|
||||||
|
|
||||||
enum class CompileOptionBit : Uint {
|
enum class CompileOptionBit : Uint {
|
||||||
None = 0,
|
None = 0,
|
||||||
PositionYFlip = 1 << 0,
|
PositionYFlip = 1 << 0,
|
||||||
@@ -26,69 +45,227 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
};
|
};
|
||||||
using CompileOptionFlags = Flags<CompileOptionBit>;
|
using CompileOptionFlags = Flags<CompileOptionBit>;
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
struct BackendProgramObject {
|
|
||||||
|
struct VkProgramObject {
|
||||||
|
static constexpr Uint32 kMaxVertexInputLocations = 32;
|
||||||
|
|
||||||
HashType hash = 0;
|
HashType hash = 0;
|
||||||
VkDevice device = VK_NULL_HANDLE;
|
|
||||||
Vector<VkPipelineShaderStageCreateInfo> stages;
|
Vector<VkPipelineShaderStageCreateInfo> stages;
|
||||||
Vector<VkShaderModule> modules;
|
Vector<VkShaderModule> modules;
|
||||||
|
|
||||||
BackendProgramObject() = default;
|
// Layout data (previously in separate VkProgramLayout)
|
||||||
BackendProgramObject(const BackendProgramObject&) = delete;
|
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
BackendProgramObject& operator=(const BackendProgramObject&) = delete;
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
BackendProgramObject(BackendProgramObject&& other) noexcept {
|
Vector<DescriptorBindingKind> bindingKinds;
|
||||||
|
Vector<Uint32> dynamicBindings;
|
||||||
|
Vector<Int> uniformBlockIndexByBinding;
|
||||||
|
// Descriptor count per binding (1 except for UBO instance arrays, which occupy one
|
||||||
|
// binding with descriptorCount = N).
|
||||||
|
Vector<Uint16> bindingDescriptorCounts;
|
||||||
|
// Per-element GL uniform block indices for arrayed UBO bindings (count > 1);
|
||||||
|
// element 0 of a non-arrayed binding stays in uniformBlockIndexByBinding.
|
||||||
|
UnorderedMap<Uint32, Vector<Int>> arrayedUniformBlockIndicesByBinding;
|
||||||
|
Vector<String> samplerNameByBinding;
|
||||||
|
Vector<Int> samplerUniformLocationByBinding;
|
||||||
|
Vector<TextureTarget> samplerTextureTargetByBinding;
|
||||||
|
Vector<SamplerNumericDomain> samplerNumericDomainByBinding;
|
||||||
|
Vector<VkFormat> storageImageFormatByBinding;
|
||||||
|
Vector<Bool> storageImageUsesBindingFormatByBinding;
|
||||||
|
Vector<String> storageBlockNameByBinding;
|
||||||
|
Vector<Int> storageBlockIndexByBinding;
|
||||||
|
// Set once during ReflectLayout so the per-draw path can skip the whole
|
||||||
|
// storage-image preparation for the overwhelming majority of programs.
|
||||||
|
Bool hasStorageImages = false;
|
||||||
|
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;
|
||||||
|
// The fragment module declares the DepthReplacing execution mode (writes
|
||||||
|
// gl_FragDepth); shader-computed depth is immune to the cross-pipeline
|
||||||
|
// position-invariance quirk (see PipelineFactory::ShouldSuppressDepthWrite).
|
||||||
|
Bool fragmentReplacesDepth = false;
|
||||||
|
|
||||||
|
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;
|
hash = other.hash;
|
||||||
device = other.device;
|
|
||||||
stages = std::move(other.stages);
|
stages = std::move(other.stages);
|
||||||
modules = std::move(other.modules);
|
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);
|
||||||
|
bindingDescriptorCounts = std::move(other.bindingDescriptorCounts);
|
||||||
|
arrayedUniformBlockIndicesByBinding = std::move(other.arrayedUniformBlockIndicesByBinding);
|
||||||
|
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);
|
||||||
|
hasStorageImages = other.hasStorageImages;
|
||||||
|
globalUboBinding = other.globalUboBinding;
|
||||||
|
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||||
|
vertexInputTypes = other.vertexInputTypes;
|
||||||
|
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
|
||||||
|
fragmentOutputTypes = other.fragmentOutputTypes;
|
||||||
|
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||||
|
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||||
|
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||||
|
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.device = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
other.hasStorageImages = false;
|
||||||
|
other.globalUboBinding = -1;
|
||||||
|
other.activeVertexInputLocationMask = 0;
|
||||||
|
other.activeFragmentOutputLocationMask = 0;
|
||||||
|
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||||
|
other.producerOutputComponentCount = 0;
|
||||||
|
other.fragmentInputComponentCount = 0;
|
||||||
|
other.fragmentReplacesDepth = false;
|
||||||
}
|
}
|
||||||
BackendProgramObject& operator=(BackendProgramObject&& other) noexcept {
|
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
||||||
if (this == &other) {
|
if (this == &other) {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
DestroyModules();
|
Destroy();
|
||||||
stages.clear();
|
|
||||||
hash = other.hash;
|
hash = other.hash;
|
||||||
device = other.device;
|
|
||||||
stages = std::move(other.stages);
|
stages = std::move(other.stages);
|
||||||
modules = std::move(other.modules);
|
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);
|
||||||
|
bindingDescriptorCounts = std::move(other.bindingDescriptorCounts);
|
||||||
|
arrayedUniformBlockIndicesByBinding = std::move(other.arrayedUniformBlockIndicesByBinding);
|
||||||
|
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);
|
||||||
|
hasStorageImages = other.hasStorageImages;
|
||||||
|
globalUboBinding = other.globalUboBinding;
|
||||||
|
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||||
|
vertexInputTypes = other.vertexInputTypes;
|
||||||
|
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
|
||||||
|
fragmentOutputTypes = other.fragmentOutputTypes;
|
||||||
|
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||||
|
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||||
|
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||||
|
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.device = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
other.hasStorageImages = false;
|
||||||
|
other.globalUboBinding = -1;
|
||||||
|
other.activeVertexInputLocationMask = 0;
|
||||||
|
other.activeFragmentOutputLocationMask = 0;
|
||||||
|
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||||
|
other.producerOutputComponentCount = 0;
|
||||||
|
other.fragmentInputComponentCount = 0;
|
||||||
|
other.fragmentReplacesDepth = false;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
~BackendProgramObject() {
|
~VkProgramObject() {
|
||||||
DestroyModules();
|
Destroy();
|
||||||
stages.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void DestroyModules() {
|
void Destroy() {
|
||||||
for (auto module : modules) {
|
if (s_device != VK_NULL_HANDLE) {
|
||||||
if (module != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
if (pipelineLayout != VK_NULL_HANDLE) {
|
||||||
vkDestroyShaderModule(device, module, nullptr);
|
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();
|
modules.clear();
|
||||||
|
stages.clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config)
|
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16,
|
||||||
: m_device(device), m_config(config) {}
|
Bool shaderDrawParametersEnabled = false,
|
||||||
~ProgramFactory();
|
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;
|
ProgramFactory(const ProgramFactory&) = delete;
|
||||||
|
|
||||||
HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const;
|
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);
|
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
||||||
|
|
||||||
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
||||||
|
static VkFormat ConvertSpirvImageFormatToVkFormat(SpvImageFormat format);
|
||||||
|
static SamplerNumericDomain UniformTypeToSamplerNumericDomain(GLenum glType);
|
||||||
|
// True when any entry point declares the DepthReplacing execution mode, i.e. the
|
||||||
|
// shader assigns gl_FragDepth. Exposed so the blended depth-write quirk's exemption
|
||||||
|
// can be pinned by tests. A false negative loses the exemption, so such a shader is
|
||||||
|
// stripped conservatively and forfeits its depth write.
|
||||||
|
static Bool ReflectedFragmentReplacesDepth(const SpvReflectShaderModule& reflectModule);
|
||||||
|
// True when an entry point reads the InstanceIndex builtin. Only gates a diagnostic:
|
||||||
|
// without shaderDrawParameters such a shader cannot have gl_InstanceID rebased.
|
||||||
|
static Bool ReflectedReadsInstanceIndexBuiltin(const SpvReflectShaderModule& reflectModule);
|
||||||
|
|
||||||
private:
|
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;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
UnorderedMap<HashType, BackendProgramObject> m_cache;
|
Uint32 m_maxBindings = 0;
|
||||||
|
UnorderedMap<HashType, VkProgramObject> m_cache;
|
||||||
const VulkanRendererConfig& m_config;
|
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();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
#include "SwapchainObject.h"
|
#include "SwapchainObject.h"
|
||||||
|
|
||||||
|
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
|
||||||
|
#include "MG_State/GLState/TextureState/TextureObject2D.h"
|
||||||
|
|
||||||
#if defined(__has_include)
|
#if defined(__has_include)
|
||||||
#if __has_include(<vulkan/vk_enum_string_helper.h>)
|
#if __has_include(<vulkan/vk_enum_string_helper.h>)
|
||||||
#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)";
|
return "VkColorSpaceKHR(unknown)";
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* string_VkPresentModeKHR(VkPresentModeKHR) {
|
static const char* string_VkPresentModeKHR(VkPresentModeKHR presentMode) {
|
||||||
return "VkPresentModeKHR(unknown)";
|
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) {
|
static const char* string_VkSurfaceTransformFlagBitsKHR(VkSurfaceTransformFlagBitsKHR) {
|
||||||
@@ -38,6 +52,40 @@ static const char* string_VkSurfaceTransformFlagBitsKHR(VkSurfaceTransformFlagBi
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
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,
|
SwapchainObject::SwapchainCapabilities SwapchainObject::GetSwapchainCapabilities(VkPhysicalDevice physicalDevice,
|
||||||
VkSurfaceKHR surface) {
|
VkSurfaceKHR surface) {
|
||||||
SwapchainCapabilities swapchainCapabilities{};
|
SwapchainCapabilities swapchainCapabilities{};
|
||||||
@@ -68,7 +116,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkSurfaceFormatKHR SwapchainObject::ChooseSwapchainSurfaceFormat(
|
VkSurfaceFormatKHR SwapchainObject::ChooseSwapchainSurfaceFormat(
|
||||||
const Vector<VkSurfaceFormatKHR>& availableFormats) {
|
const Vector<VkSurfaceFormatKHR>& availableFormats) {
|
||||||
for (const auto& availableFormat : 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) {
|
availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
||||||
return availableFormat;
|
return availableFormat;
|
||||||
}
|
}
|
||||||
@@ -93,7 +149,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SwapchainObject::Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
|
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);
|
const auto swapchainCapabilities = GetSwapchainCapabilities(physicalDevice, surface);
|
||||||
MOBILEGL_ASSERT(swapchainCapabilities.IsComplete(),
|
MOBILEGL_ASSERT(swapchainCapabilities.IsComplete(),
|
||||||
"SwapchainObject::Create failed: incomplete swapchain capabilities");
|
"SwapchainObject::Create failed: incomplete swapchain capabilities");
|
||||||
@@ -116,7 +173,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MGLOG_I("Picked present mode: %s", string_VkPresentModeKHR(presentMode));
|
MGLOG_I("Picked present mode: %s", string_VkPresentModeKHR(presentMode));
|
||||||
|
|
||||||
const auto& swapchainCaps = swapchainCapabilities.capabilities;
|
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("Set minImageCount = %u", targetImageCount);
|
||||||
MGLOG_I("Swapchain currentTransform = %s",
|
MGLOG_I("Swapchain currentTransform = %s",
|
||||||
string_VkSurfaceTransformFlagBitsKHR(swapchainCaps.currentTransform));
|
string_VkSurfaceTransformFlagBitsKHR(swapchainCaps.currentTransform));
|
||||||
@@ -127,6 +187,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
createInfo.imageFormat = pickedSurfaceFormat.format;
|
createInfo.imageFormat = pickedSurfaceFormat.format;
|
||||||
createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace;
|
createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace;
|
||||||
createInfo.imageExtent = swapchainCaps.currentExtent;
|
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;
|
createInfo.imageArrayLayers = 1;
|
||||||
const VkImageUsageFlags requiredImageUsage =
|
const VkImageUsageFlags requiredImageUsage =
|
||||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
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);
|
m_imageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||||
|
|
||||||
CreateImageViews(device);
|
CreateImageViews(device);
|
||||||
|
CreateDepthStencilResources(device, physicalDevice);
|
||||||
|
|
||||||
MGLOG_I("Swapchain created, extent = %dx%d, swapchain imageCount = %d", m_extent.width, m_extent.height,
|
MGLOG_I("Swapchain created, extent = %dx%d, swapchain imageCount = %d", m_extent.width, m_extent.height,
|
||||||
imageCount);
|
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) {
|
void SwapchainObject::Shutdown(VkDevice device) {
|
||||||
|
DestroyDepthStencilResources(device);
|
||||||
|
|
||||||
for (auto imageView : m_imageViews) {
|
for (auto imageView : m_imageViews) {
|
||||||
vkDestroyImageView(device, imageView, nullptr);
|
vkDestroyImageView(device, imageView, nullptr);
|
||||||
}
|
}
|
||||||
@@ -221,6 +446,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_imageLayouts[index] = layout;
|
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) {
|
void SwapchainObject::CreateImageViews(VkDevice device) {
|
||||||
m_imageViews.resize(m_images.size(), VK_NULL_HANDLE);
|
m_imageViews.resize(m_images.size(), VK_NULL_HANDLE);
|
||||||
for (SizeT i = 0; i < m_imageViews.size(); i++) {
|
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);
|
static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector<VkPresentModeKHR>& availablePresentModes);
|
||||||
|
|
||||||
void Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, Uint32 graphicsQueueFamily,
|
void Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, Uint32 graphicsQueueFamily,
|
||||||
Uint32 presentQueueFamily, Uint32 minImageCountHint);
|
Uint32 presentQueueFamily, Uint32 minImageCountHint, VkExtent2D desiredExtent);
|
||||||
void Shutdown(VkDevice device);
|
void Shutdown(VkDevice device);
|
||||||
|
|
||||||
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
||||||
@@ -38,6 +38,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkSurfaceTransformFlagBitsKHR GetPreTransform() const { return m_preTransform; }
|
VkSurfaceTransformFlagBitsKHR GetPreTransform() const { return m_preTransform; }
|
||||||
const Vector<VkImage>& GetImages() const { return m_images; }
|
const Vector<VkImage>& GetImages() const { return m_images; }
|
||||||
const Vector<VkImageView>& GetImageViews() const { return m_imageViews; }
|
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;
|
VkImage GetImage(Uint32 index) const;
|
||||||
VkImageLayout GetImageLayout(Uint32 index) const;
|
VkImageLayout GetImageLayout(Uint32 index) const;
|
||||||
void SetImageLayout(Uint32 index, VkImageLayout layout);
|
void SetImageLayout(Uint32 index, VkImageLayout layout);
|
||||||
@@ -45,6 +51,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateImageViews(VkDevice device);
|
void CreateImageViews(VkDevice device);
|
||||||
|
void CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice);
|
||||||
|
void DestroyDepthStencilResources(VkDevice device);
|
||||||
static constexpr VkPresentModeKHR s_desiredPresentModes[] {
|
static constexpr VkPresentModeKHR s_desiredPresentModes[] {
|
||||||
VK_PRESENT_MODE_MAILBOX_KHR,
|
VK_PRESENT_MODE_MAILBOX_KHR,
|
||||||
VK_PRESENT_MODE_IMMEDIATE_KHR,
|
VK_PRESENT_MODE_IMMEDIATE_KHR,
|
||||||
@@ -59,5 +67,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<VkImage> m_images;
|
Vector<VkImage> m_images;
|
||||||
Vector<VkImageView> m_imageViews;
|
Vector<VkImageView> m_imageViews;
|
||||||
Vector<VkImageLayout> m_imageLayouts;
|
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
|
} // 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,189 @@
|
|||||||
|
// 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,
|
||||||
|
Uint32 arrayElement, 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;
|
||||||
|
// ResolveSampledImageViewFormat is pure in (image format, numeric domain), but a
|
||||||
|
// domain mismatch walks a ~184-entry format table. Memo the resolution per binding
|
||||||
|
// so a reinterpreted sampler pays that scan once, not once per draw.
|
||||||
|
VkFormat viewFormatSource = VK_FORMAT_UNDEFINED;
|
||||||
|
SamplerNumericDomain viewFormatDomain = SamplerNumericDomain::Unknown;
|
||||||
|
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
|
||||||
|
Bool viewFormatValid = 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.Stride, sizeof(attr.Stride)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
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.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)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||||
|
|
||||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||||
@@ -38,70 +39,127 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return XXH64_digest(m_hashState);
|
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 VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao) {
|
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);
|
auto it = m_cache.find(hash);
|
||||||
if (it != m_cache.end()) {
|
if (it != m_cache.end()) {
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexInputStateBuilder builder;
|
VertexInputStateBuilder builder;
|
||||||
UnorderedMap<SizeT, Uint32> bindingByBufferKey;
|
|
||||||
UnorderedMap<SizeT, Uint32> strideByBufferKey;
|
|
||||||
UnorderedMap<SizeT, VkVertexInputRate> inputRateByBufferKey;
|
|
||||||
Vector<SizeT> bindingBufferKeys;
|
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) {
|
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
|
||||||
const auto& attr = vao.GetAttribute(location);
|
const auto& attr = vao.GetAttribute(location);
|
||||||
if (!attr.Enabled || !attr.Buffer) {
|
if (!attr.Enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto vkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger);
|
const VkFormat sourceVkFormat =
|
||||||
if (vkFormat == VK_FORMAT_UNDEFINED) {
|
ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra);
|
||||||
MGLOG_D("Skipping unsupported vertex attribute layout (location=%u, type=%s, size=%d)",
|
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);
|
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
|
||||||
|
unsupportedAttribMask |= (1u << location);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SizeT componentSize = GetComponentSize(attr.Type);
|
VkFormat vkFormat = sourceVkFormat;
|
||||||
if (componentSize == 0) {
|
VertexStreamConversion conversion = VertexStreamConversion::None;
|
||||||
MGLOG_D("Skipping vertex attribute with unknown component size (location=%u, type=%s)",
|
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());
|
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str());
|
||||||
|
unsupportedAttribMask |= (1u << location);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Uint32 stride = attr.Stride > 0
|
const Uint32 sourceStride =
|
||||||
? static_cast<Uint32>(attr.Stride)
|
attr.Stride > 0 ? static_cast<Uint32>(attr.Stride) : static_cast<Uint32>(attribByteSize);
|
||||||
: static_cast<Uint32>(componentSize * static_cast<SizeT>(attr.Size));
|
const Bool packedAttribute = attr.Type == DataType::Int2101010Rev ||
|
||||||
|
attr.Type == DataType::Uint2101010Rev;
|
||||||
|
const SizeT requiredAlignment = packedAttribute ? attribByteSize : GetComponentSize(attr.Type);
|
||||||
|
// For a client-memory array attr.Offset holds the raw client pointer, and the
|
||||||
|
// draw path re-uploads the data to a 16-aligned transient slice with attribute
|
||||||
|
// offset 0, so only the stride can violate Vulkan's fetch alignment there.
|
||||||
|
const Bool clientMemoryAttribute = attr.Buffer == nullptr;
|
||||||
|
if (conversion == VertexStreamConversion::None && requiredAlignment > 1 &&
|
||||||
|
((sourceStride % requiredAlignment) != 0 ||
|
||||||
|
(!clientMemoryAttribute && (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 =
|
const VkVertexInputRate inputRate =
|
||||||
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
||||||
|
|
||||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||||
Uint32 binding = 0;
|
const Uint32 binding = static_cast<Uint32>(bindingBufferKeys.size());
|
||||||
auto itBinding = bindingByBufferKey.find(bufferKey);
|
bindingBufferKeys.push_back(bufferKey);
|
||||||
if (itBinding == bindingByBufferKey.end()) {
|
bindingBaseOffsets.push_back(attr.Buffer ? attr.Offset : 0);
|
||||||
binding = static_cast<Uint32>(bindingByBufferKey.size());
|
bindingAttributeLocations.push_back(location);
|
||||||
bindingByBufferKey.emplace(bufferKey, binding);
|
bindingUsesClientMemory.push_back(attr.Buffer == nullptr);
|
||||||
strideByBufferKey.emplace(bufferKey, stride);
|
bindingConversions.push_back(conversion);
|
||||||
inputRateByBufferKey.emplace(bufferKey, inputRate);
|
builder.AddBinding(binding, stride, inputRate);
|
||||||
bindingBufferKeys.push_back(bufferKey);
|
builder.AddAttribute(location, binding, vkFormat, 0);
|
||||||
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 auto& state = builder.Build();
|
const auto& state = builder.Build();
|
||||||
@@ -111,14 +169,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
entry.bindings = builder.GetBindings();
|
entry.bindings = builder.GetBindings();
|
||||||
entry.attributes = builder.GetAttributes();
|
entry.attributes = builder.GetAttributes();
|
||||||
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
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 = state;
|
||||||
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
|
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
|
||||||
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
|
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
|
||||||
return entry;
|
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) {
|
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:
|
case DataType::Float32:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||||
@@ -127,6 +214,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
|
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||||
default: return VK_FORMAT_UNDEFINED;
|
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:
|
case DataType::Int32:
|
||||||
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
||||||
switch (size) {
|
switch (size) {
|
||||||
@@ -148,8 +246,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Int16:
|
case DataType::Int16:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1:
|
case 1:
|
||||||
return isInteger ? VK_FORMAT_R16_SINT
|
return isInteger ? VK_FORMAT_R16_SINT : (normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SSCALED);
|
||||||
: (normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SSCALED);
|
|
||||||
case 2:
|
case 2:
|
||||||
return isInteger ? VK_FORMAT_R16G16_SINT
|
return isInteger ? VK_FORMAT_R16G16_SINT
|
||||||
: (normalized ? VK_FORMAT_R16G16_SNORM : VK_FORMAT_R16G16_SSCALED);
|
: (normalized ? VK_FORMAT_R16G16_SNORM : VK_FORMAT_R16G16_SSCALED);
|
||||||
@@ -164,8 +261,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Uint16:
|
case DataType::Uint16:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1:
|
case 1:
|
||||||
return isInteger ? VK_FORMAT_R16_UINT
|
return isInteger ? VK_FORMAT_R16_UINT : (normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_USCALED);
|
||||||
: (normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_USCALED);
|
|
||||||
case 2:
|
case 2:
|
||||||
return isInteger ? VK_FORMAT_R16G16_UINT
|
return isInteger ? VK_FORMAT_R16G16_UINT
|
||||||
: (normalized ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16G16_USCALED);
|
: (normalized ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16G16_USCALED);
|
||||||
@@ -180,8 +276,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Int8:
|
case DataType::Int8:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1:
|
case 1:
|
||||||
return isInteger ? VK_FORMAT_R8_SINT
|
return isInteger ? VK_FORMAT_R8_SINT : (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED);
|
||||||
: (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED);
|
|
||||||
case 2:
|
case 2:
|
||||||
return isInteger ? VK_FORMAT_R8G8_SINT
|
return isInteger ? VK_FORMAT_R8G8_SINT
|
||||||
: (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED);
|
: (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED);
|
||||||
@@ -196,8 +291,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Uint8:
|
case DataType::Uint8:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1:
|
case 1:
|
||||||
return isInteger ? VK_FORMAT_R8_UINT
|
return isInteger ? VK_FORMAT_R8_UINT : (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED);
|
||||||
: (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED);
|
|
||||||
case 2:
|
case 2:
|
||||||
return isInteger ? VK_FORMAT_R8G8_UINT
|
return isInteger ? VK_FORMAT_R8G8_UINT
|
||||||
: (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED);
|
: (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED);
|
||||||
@@ -234,4 +328,57 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return 0;
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -19,29 +19,56 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
public:
|
public:
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|
||||||
|
enum class VertexStreamConversion : Uint8 {
|
||||||
|
None = 0,
|
||||||
|
Repack,
|
||||||
|
ScaledIntegerToFloat32,
|
||||||
|
};
|
||||||
|
|
||||||
struct BackendVertexInputState {
|
struct BackendVertexInputState {
|
||||||
HashType hash = 0;
|
HashType hash = 0;
|
||||||
Vector<VkVertexInputBindingDescription> bindings;
|
Vector<VkVertexInputBindingDescription> bindings;
|
||||||
Vector<VkVertexInputAttributeDescription> attributes;
|
Vector<VkVertexInputAttributeDescription> attributes;
|
||||||
Vector<SizeT> bindingBufferKeys;
|
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{
|
VkPipelineVertexInputStateCreateInfo state{
|
||||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit VertexInputStateFactory(const VulkanRendererConfig& config):
|
VertexInputStateFactory(const VulkanRendererConfig& config, VkPhysicalDevice physicalDevice):
|
||||||
m_config(config) {}
|
m_config(config), m_physicalDevice(physicalDevice) {}
|
||||||
~VertexInputStateFactory() = default;
|
~VertexInputStateFactory() = default;
|
||||||
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
||||||
|
|
||||||
HashType ComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
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);
|
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:
|
private:
|
||||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger);
|
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false);
|
||||||
static SizeT GetComponentSize(DataType type);
|
static Bool IsScaledIntegerVertexFormat(VkFormat format);
|
||||||
|
static VkFormat ToFloat32VertexFormat(Int componentCount);
|
||||||
|
Bool SupportsVertexBufferFormat(VkFormat format) const;
|
||||||
|
|
||||||
const VulkanRendererConfig& m_config;
|
const VulkanRendererConfig& m_config;
|
||||||
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||||
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
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_allocator = other.m_allocator;
|
||||||
m_buffer = other.m_buffer;
|
m_buffer = other.m_buffer;
|
||||||
m_allocation = other.m_allocation;
|
m_allocation = other.m_allocation;
|
||||||
|
m_mappedData = other.m_mappedData;
|
||||||
m_size = other.m_size;
|
m_size = other.m_size;
|
||||||
|
|
||||||
other.m_allocator = nullptr;
|
other.m_allocator = nullptr;
|
||||||
other.m_buffer = VK_NULL_HANDLE;
|
other.m_buffer = VK_NULL_HANDLE;
|
||||||
other.m_allocation = nullptr;
|
other.m_allocation = nullptr;
|
||||||
|
other.m_mappedData = nullptr;
|
||||||
other.m_size = 0;
|
other.m_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,11 +33,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_allocator = other.m_allocator;
|
m_allocator = other.m_allocator;
|
||||||
m_buffer = other.m_buffer;
|
m_buffer = other.m_buffer;
|
||||||
m_allocation = other.m_allocation;
|
m_allocation = other.m_allocation;
|
||||||
|
m_mappedData = other.m_mappedData;
|
||||||
m_size = other.m_size;
|
m_size = other.m_size;
|
||||||
|
|
||||||
other.m_allocator = nullptr;
|
other.m_allocator = nullptr;
|
||||||
other.m_buffer = VK_NULL_HANDLE;
|
other.m_buffer = VK_NULL_HANDLE;
|
||||||
other.m_allocation = nullptr;
|
other.m_allocation = nullptr;
|
||||||
|
other.m_mappedData = nullptr;
|
||||||
other.m_size = 0;
|
other.m_size = 0;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -44,8 +48,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Destroy();
|
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,
|
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(allocator != nullptr, "VkBufferObject::Create requires valid VMA allocator");
|
||||||
MOBILEGL_ASSERT(size > 0, "VkBufferObject::Create requires non-zero size");
|
MOBILEGL_ASSERT(size > 0, "VkBufferObject::Create requires non-zero size");
|
||||||
|
|
||||||
@@ -61,6 +71,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VmaAllocationCreateInfo allocationInfo{};
|
VmaAllocationCreateInfo allocationInfo{};
|
||||||
allocationInfo.usage = memoryUsage;
|
allocationInfo.usage = memoryUsage;
|
||||||
allocationInfo.flags = allocationFlags;
|
allocationInfo.flags = allocationFlags;
|
||||||
|
allocationInfo.requiredFlags = requiredFlags;
|
||||||
|
|
||||||
const VkResult result =
|
const VkResult result =
|
||||||
vmaCreateBuffer(m_allocator, &bufferInfo, &allocationInfo, &m_buffer, &m_allocation, nullptr);
|
vmaCreateBuffer(m_allocator, &bufferInfo, &allocationInfo, &m_buffer, &m_allocation, nullptr);
|
||||||
@@ -78,6 +89,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferObject::Destroy() {
|
void VkBufferObject::Destroy() {
|
||||||
|
Unmap();
|
||||||
if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) {
|
if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) {
|
||||||
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
|
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
|
||||||
}
|
}
|
||||||
@@ -87,6 +99,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_size = 0;
|
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) {
|
Bool VkBufferObject::Upload(const void* data, VkDeviceSize size, VkDeviceSize offset) {
|
||||||
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Upload called on invalid buffer");
|
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Upload called on invalid buffer");
|
||||||
MOBILEGL_ASSERT(data != nullptr || size == 0, "VkBufferObject::Upload data pointer is null");
|
MOBILEGL_ASSERT(data != nullptr || size == 0, "VkBufferObject::Upload data pointer is null");
|
||||||
@@ -96,15 +135,57 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* mapped = nullptr;
|
const Bool wasMapped = IsMapped();
|
||||||
const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &mapped);
|
void* mapped = wasMapped ? m_mappedData : Map();
|
||||||
if (mapResult != VK_SUCCESS || mapped == nullptr) {
|
if (mapped == nullptr) {
|
||||||
MGLOG_E("VkBufferObject::Upload failed: vmaMapMemory returned %d", mapResult);
|
MGLOG_E("VkBufferObject::Upload failed: unable to map buffer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size));
|
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;
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -8,11 +8,23 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "BufferSlice.h"
|
||||||
#include "../VkIncludes.h"
|
#include "../VkIncludes.h"
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
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 {
|
class VkBufferObject {
|
||||||
public:
|
public:
|
||||||
VkBufferObject() = default;
|
VkBufferObject() = default;
|
||||||
@@ -23,20 +35,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkBufferObject(VkBufferObject&& other) noexcept;
|
VkBufferObject(VkBufferObject&& other) noexcept;
|
||||||
VkBufferObject& operator=(VkBufferObject&& other) noexcept;
|
VkBufferObject& operator=(VkBufferObject&& other) noexcept;
|
||||||
|
|
||||||
|
Bool Create(const VkBufferObjectDesc& desc);
|
||||||
Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
|
Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||||
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0);
|
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0,
|
||||||
|
VkMemoryPropertyFlags requiredFlags = 0);
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
|
||||||
|
void* Map();
|
||||||
|
void Unmap();
|
||||||
Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0);
|
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; }
|
VkBuffer GetHandle() const { return m_buffer; }
|
||||||
VkDeviceSize GetSize() const { return m_size; }
|
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; }
|
Bool IsValid() const { return m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VmaAllocator m_allocator = nullptr;
|
VmaAllocator m_allocator = nullptr;
|
||||||
VkBuffer m_buffer = VK_NULL_HANDLE;
|
VkBuffer m_buffer = VK_NULL_HANDLE;
|
||||||
VmaAllocation m_allocation = nullptr;
|
VmaAllocation m_allocation = nullptr;
|
||||||
|
void* m_mappedData = nullptr;
|
||||||
VkDeviceSize m_size = 0;
|
VkDeviceSize m_size = 0;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // 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,252 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "SwapchainObject.h"
|
||||||
|
#include "VkClearManager.h"
|
||||||
|
#include "VkTextureManager.h"
|
||||||
#include "../VkIncludes.h"
|
#include "../VkIncludes.h"
|
||||||
|
#include "../VulkanRendererConfig.h"
|
||||||
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
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 {
|
class VkRenderPassManager {
|
||||||
public:
|
public:
|
||||||
struct InitInfo {
|
using HashType = Uint64;
|
||||||
VkDevice device = VK_NULL_HANDLE;
|
VkRenderPassManager(VkDevice device,
|
||||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config,
|
||||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject);
|
||||||
};
|
~VkRenderPassManager();
|
||||||
|
|
||||||
struct OffscreenRenderTargetInfo {
|
Bool Initialize();
|
||||||
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);
|
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
Bool RecreateDefaultFramebuffers(const Vector<VkImageView>& colorViews,
|
HashType ComputeHash(
|
||||||
const Vector<VkImageView>& depthStencilViews, VkExtent2D extent);
|
const MG_State::GLState::FramebufferObject& fbo,
|
||||||
Bool GetDefaultRenderTarget(Uint32 imageIndex, VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
Uint32 swapchainImageIndex,
|
||||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const;
|
Bool includePendingClear = true);
|
||||||
|
RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex);
|
||||||
Bool EnsureOffscreenRenderTarget(const OffscreenRenderTargetInfo& targetInfo);
|
void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||||
Bool GetOffscreenRenderTarget(Uint targetExternalIndex, VkRenderPass& outRenderPass,
|
const MG_State::GLState::FramebufferObject& drawFbo);
|
||||||
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
|
void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload,
|
||||||
VkFormat& outDepthStencilFormat) const;
|
const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||||
void RemoveOffscreenRenderTarget(Uint targetExternalIndex);
|
void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer);
|
||||||
|
// Frame boundary hook: ages the render-pass cache and evicts long-unused
|
||||||
void BeginRenderPass(VkCommandBuffer commandBuffer, VkRenderPass renderPass, VkFramebuffer framebuffer,
|
// entries (their command buffers retired many frames ago).
|
||||||
VkExtent2D extent) const;
|
void OnPresent();
|
||||||
void EndRenderPass(VkCommandBuffer commandBuffer) const;
|
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
|
||||||
void RecordColorClear(VkCommandBuffer commandBuffer, VkExtent2D extent,
|
static Bool EndRenderPass(VkCommandBuffer commandBuffer);
|
||||||
const VkClearColorValue& clearColor) const;
|
static ActiveRenderPassInfo* GetActiveRenderPass();
|
||||||
void RecordDepthStencilClear(VkCommandBuffer commandBuffer, VkExtent2D extent, GLbitfield mask, Float depth,
|
|
||||||
Uint32 stencil, VkFormat depthStencilFormat) const;
|
|
||||||
|
|
||||||
VkRenderPass GetLoadRenderPass() const;
|
|
||||||
VkRenderPass GetClearRenderPass() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct OffscreenRenderTarget {
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
Uint16 targetVersion = 0;
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||||
VkImageView colorView = VK_NULL_HANDLE;
|
VmaAllocator m_allocator = nullptr;
|
||||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
const VulkanRendererConfig& m_config;
|
||||||
VkImageView depthStencilView = VK_NULL_HANDLE;
|
VkClearManager& m_clearManager;
|
||||||
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
|
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;
|
||||||
|
|
||||||
|
public:
|
||||||
|
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};
|
VkExtent2D extent = {0, 0};
|
||||||
VkRenderPass renderPassLoad = VK_NULL_HANDLE;
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||||
|
Int samples = 0;
|
||||||
|
|
||||||
|
void Destroy(VkDevice device, VmaAllocator allocator);
|
||||||
};
|
};
|
||||||
|
|
||||||
VkRenderPass CreateDefaultRenderPass(VkAttachmentLoadOp colorLoadOp) const;
|
// Public so the renderer's blit/copy/readback bindings can source renderbuffer
|
||||||
VkRenderPass CreateRenderPass(VkFormat colorFormat, VkFormat depthStencilFormat, VkAttachmentLoadOp colorLoadOp,
|
// attachments the same way texture attachments go through the texture manager.
|
||||||
VkImageLayout colorFinalLayout) const;
|
RenderbufferResource* GetOrCreateRenderbufferResource(
|
||||||
void DestroyDefaultFramebuffers();
|
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||||
void DestroyOffscreenRenderTarget(OffscreenRenderTarget& target);
|
Bool GetPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer,
|
||||||
static Bool HasStencilComponent(VkFormat format);
|
ClearAttachmentPayload& outPayload) const;
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
private:
|
||||||
VkFormat m_colorFormat = VK_FORMAT_UNDEFINED;
|
struct PendingRenderbufferClear {
|
||||||
VkFormat m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
VkRenderPass m_renderPassLoad = VK_NULL_HANDLE;
|
ClearAttachmentPayload payload{};
|
||||||
VkRenderPass m_renderPassClear = VK_NULL_HANDLE;
|
};
|
||||||
Vector<VkFramebuffer> m_defaultFramebuffers;
|
|
||||||
VkExtent2D m_defaultExtent = {0, 0};
|
UnorderedMap<MG_State::GLState::RenderbufferObject*, RenderbufferResource> m_renderbufferResources;
|
||||||
UnorderedMap<Uint, OffscreenRenderTarget> m_offscreenRenderTargets;
|
UnorderedMap<MG_State::GLState::RenderbufferObject*, PendingRenderbufferClear> m_pendingRenderbufferClears;
|
||||||
|
|
||||||
|
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
|
} // 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,396 @@
|
|||||||
|
// 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>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
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);
|
||||||
|
// Non-mutating probe for the per-draw storage-image fast path: true when preparing this
|
||||||
|
// texture as a storage image may need work that is illegal inside a render pass (resource
|
||||||
|
// creation, dirty-content upload, or a layout transition to GENERAL). Unknown state reports
|
||||||
|
// true - a false positive merely ends the render pass, a false negative would skip a barrier.
|
||||||
|
Bool NeedsStorageImagePreparation(MG_State::GLState::ITextureObject& texture) const;
|
||||||
|
|
||||||
|
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;
|
||||||
|
// Formats whose mutable-image probe failed on this device; their images are created
|
||||||
|
// without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch.
|
||||||
|
std::unordered_set<VkFormat> m_mutableFormatUnsupported;
|
||||||
|
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 "PipelineFactory.h"
|
||||||
#include "ProgramFactory.h"
|
#include "ProgramFactory.h"
|
||||||
#include "SwapchainObject.h"
|
#include "SwapchainObject.h"
|
||||||
#include "UniformDescriptorBinder.h"
|
#include "UniformManager.h"
|
||||||
#include "VertexInputStateFactory.h"
|
#include "VertexInputStateFactory.h"
|
||||||
#include "VkBufferObject.h"
|
#include "VkBufferObject.h"
|
||||||
#include "VkFramebufferManager.h"
|
#include "VkBufferManager.h"
|
||||||
|
#include "VkClearManager.h"
|
||||||
#include "VkRenderPassManager.h"
|
#include "VkRenderPassManager.h"
|
||||||
#include "VkTextureSamplerManager.h"
|
#include "VkSamplerManager.h"
|
||||||
|
#include "VkTextureManager.h"
|
||||||
|
#include "VkTimerQueryManager.h"
|
||||||
#include "MG_Util/Math/VectorTypes.h"
|
#include "MG_Util/Math/VectorTypes.h"
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
@@ -27,23 +30,73 @@
|
|||||||
namespace MobileGL::MG_State::GLState {
|
namespace MobileGL::MG_State::GLState {
|
||||||
class FramebufferObject;
|
class FramebufferObject;
|
||||||
class ProgramObject;
|
class ProgramObject;
|
||||||
|
class SamplerObject;
|
||||||
class VertexArrayObject;
|
class VertexArrayObject;
|
||||||
} // namespace MobileGL::MG_State::GLState
|
} // namespace MobileGL::MG_State::GLState
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
struct DrawArrayPayload {
|
enum class DrawSetupAspect: Uint8 {
|
||||||
GLenum mode = GL_TRIANGLES;
|
FramebufferObject = 1 << 0,
|
||||||
GLint first = 0;
|
VertexArrayObject = 1 << 1,
|
||||||
GLsizei count = 0;
|
UniformBuffer = 1 << 2,
|
||||||
const MG_State::GLState::ProgramObject* program = nullptr;
|
VertexBuffer = 1 << 3,
|
||||||
const MG_State::GLState::VertexArrayObject* vertexArray = nullptr;
|
IndexBuffer = 1 << 4,
|
||||||
|
IndirectDrawBuffer = 1 << 5,
|
||||||
|
Viewport = 1 << 6,
|
||||||
|
Scissor = 1 << 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawElementPayload {
|
struct DrawCmdParam {
|
||||||
DrawArrayPayload drawArray;
|
Uint32 vertexCount = 0;
|
||||||
|
Uint32 instanceCount = 1;
|
||||||
|
Uint32 firstVertex = 0;
|
||||||
|
Uint32 firstInstance = 0;
|
||||||
|
// Indexed-draw metadata for bounding vertex-stream conversion. baseVertex is the
|
||||||
|
// draw's base-vertex offset; indexRangeIsExactView is true only when the draw
|
||||||
|
// fetches exactly the indices its IndexBufferView describes (direct DrawElements;
|
||||||
|
// multi/indirect forms leave it false because the CPU cannot bound their ranges).
|
||||||
|
Int32 baseVertex = 0;
|
||||||
|
Bool indexRangeIsExactView = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
GLenum indexType = GL_UNSIGNED_SHORT;
|
||||||
SizeT indexByteOffset = 0;
|
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 {
|
struct QueueFamilyIndices {
|
||||||
@@ -61,7 +114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class VulkanRenderer {
|
class VulkanRenderer : public IBufferCopyCommandProvider, public FrameContext::IRecordingObserver {
|
||||||
public:
|
public:
|
||||||
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
||||||
~VulkanRenderer();
|
~VulkanRenderer();
|
||||||
@@ -69,36 +122,245 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void Initialize();
|
void Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil,
|
// IBufferCopyCommandProvider: recording command buffer, outside any
|
||||||
Uint drawFboExternalIndex, Bool isDefaultFramebufferTarget);
|
// render pass, for immediate staged buffer copies.
|
||||||
Bool ConsumePendingColorClear(VkClearColorValue& outClearColor);
|
VkCommandBuffer AcquireBufferCopyCommandBuffer() override;
|
||||||
void EnsureFrameRecordingStarted();
|
|
||||||
void DrawArrays(const DrawArrayPayload& payload);
|
// FrameContext::IRecordingObserver: prepares the frame's timer-query
|
||||||
void DrawElements(const DrawElementPayload& payload);
|
// pool (harvest + reset) right after the frame command buffer begins
|
||||||
void MultiDrawElements(const Vector<DrawElementPayload>& payloads);
|
// recording, before any render pass.
|
||||||
Bool BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) override;
|
||||||
GLint dstY1, GLbitfield mask, GLenum filter, Uint readFboExternalIndex,
|
|
||||||
Uint drawFboExternalIndex, Bool readIsDefaultFramebuffer, Bool drawIsDefaultFramebuffer);
|
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||||
void Render();
|
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();
|
void Present();
|
||||||
|
|
||||||
const PhysicalDevice& GetPhysicalDevice() const;
|
const PhysicalDevice& GetPhysicalDevice() const;
|
||||||
|
VkInstance GetInstance() const;
|
||||||
Bool IsDrawIndirectCountExtensionEnabled() const;
|
Bool IsDrawIndirectCountExtensionEnabled() const;
|
||||||
|
|
||||||
void RecreateSwapchain();
|
// 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);
|
||||||
|
// Returns false when the surface is zero-area (minimized/hidden window):
|
||||||
|
// no new swapchain is installed and presentation must stay suspended.
|
||||||
|
Bool RecreateSwapchain();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PendingClearState {
|
struct BlitUniformData {
|
||||||
GLbitfield mask = 0;
|
float srcRect[4] = {0.f, 0.f, 1.f, 1.f};
|
||||||
VkClearColorValue color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
float dstRect[4] = {0.f, 0.f, 1.f, 1.f};
|
||||||
Float depth = 1.0f;
|
Int surfaceTransform = 0;
|
||||||
Uint32 stencil = 0;
|
Int padding[3] = {0, 0, 0};
|
||||||
Uint drawFboExternalIndex = 0;
|
|
||||||
Bool targetsDefaultFramebuffer = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
NativeWindowType m_window = 0;
|
||||||
|
void* m_platformDisplay = nullptr;
|
||||||
|
void* m_platformLibrary = nullptr;
|
||||||
|
void* m_platformCloseDisplay = nullptr;
|
||||||
VulkanRendererConfig m_config;
|
VulkanRendererConfig m_config;
|
||||||
|
Bool m_swapchainResizeRequested = false;
|
||||||
|
// Presentation is suspended while the window is zero-area (minimized): the
|
||||||
|
// swapchain is unusable/out of date, so Present drops frames instead of
|
||||||
|
// submitting on a signaled fence / presenting never-acquired images.
|
||||||
|
Bool m_presentSuspended = false;
|
||||||
|
|
||||||
// Vulkan objects
|
// Vulkan objects
|
||||||
Bool m_validationLayersEnabled = false;
|
Bool m_validationLayersEnabled = false;
|
||||||
@@ -106,56 +368,151 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkInstance m_instance = VK_NULL_HANDLE;
|
VkInstance m_instance = VK_NULL_HANDLE;
|
||||||
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
||||||
PhysicalDevice m_physicalDevice;
|
PhysicalDevice m_physicalDevice;
|
||||||
// VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
VmaAllocator m_allocator = nullptr;
|
VmaAllocator m_allocator = nullptr;
|
||||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||||
SwapchainObject m_swapchainObject;
|
SwapchainObject m_swapchainObject;
|
||||||
|
|
||||||
// Vector<VkQueueFamilyProperties> m_queueFamilies;
|
|
||||||
// QueueFamilyIndices m_queueFamilyIndices;
|
|
||||||
|
|
||||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||||
VkQueue m_presentQueue = VK_NULL_HANDLE;
|
VkQueue m_presentQueue = VK_NULL_HANDLE;
|
||||||
Bool m_drawIndirectCountExtensionEnabled = false;
|
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,
|
using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer,
|
||||||
VkDeviceSize offset, VkBuffer countBuffer,
|
VkDeviceSize offset, VkBuffer countBuffer,
|
||||||
VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
|
VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
|
||||||
Uint32 stride);
|
Uint32 stride);
|
||||||
PFNDrawIndexedIndirectCountFunc m_cmdDrawIndexedIndirectCount = nullptr;
|
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
||||||
|
|
||||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkFormat m_depthStencilFormat = VK_FORMAT_UNDEFINED;
|
VkBufferManager m_bufferManager;
|
||||||
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;
|
|
||||||
|
|
||||||
Uint m_imageIndexAcquired = 0;
|
Uint m_imageIndexAcquired = 0;
|
||||||
FrameContext m_frameContext;
|
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;
|
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<ProgramFactory> m_programFactory;
|
||||||
UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder;
|
UniquePtr<UniformManager> m_uniformManager;
|
||||||
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
|
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
|
||||||
UniquePtr<VkFramebufferManager> m_framebufferManager;
|
UniquePtr<VkClearManager> m_clearManager;
|
||||||
UniquePtr<VkRenderPassManager> m_renderPassManager;
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConvertedVertexStream {
|
||||||
|
BufferSlice slice;
|
||||||
|
// Number of source elements the cached slice covers. A draw needing a prefix of
|
||||||
|
// this range reuses the slice (converted streams are tightly packed); a draw
|
||||||
|
// needing more reconverts and replaces the entry, so per (buffer, layout) a
|
||||||
|
// frame converts at most the largest range any draw asked for.
|
||||||
|
SizeT elementCount = 0;
|
||||||
|
// Pins the source buffer for the frame so its heap address cannot be reused by
|
||||||
|
// a new BufferObject while this pointer-keyed entry is alive.
|
||||||
|
SharedPtr<const MG_State::GLState::BufferObject> sourcePin;
|
||||||
|
};
|
||||||
|
UnorderedMap<ConvertedVertexStreamKey, ConvertedVertexStream, ConvertedVertexStreamKeyHash>
|
||||||
|
m_convertedVertexStreams;
|
||||||
|
|
||||||
void CreateInstance();
|
void CreateInstance();
|
||||||
VkResult SetupDebugMessenger();
|
VkResult SetupDebugMessenger();
|
||||||
@@ -168,31 +525,59 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void DestroyAllocator();
|
void DestroyAllocator();
|
||||||
void CreateSwapchain();
|
void CreateSwapchain();
|
||||||
void CreateCommandPool();
|
void CreateCommandPool();
|
||||||
void CreateFrameContexts();
|
|
||||||
void CreateDepthStencilResources();
|
VkPipeline GetOrCreatePipeline(
|
||||||
void DestroyDepthStencilResources();
|
GLenum mode,
|
||||||
VkRenderPass GetDefaultLoadRenderPass() const;
|
const MG_State::GLState::ProgramObject& program,
|
||||||
Bool GetDefaultRenderTargetForCurrentImage(VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const;
|
ProgramFactory::CompileOptionFlags transformFlags,
|
||||||
Bool EnsureOffscreenRenderTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo,
|
const MG_State::GLState::VertexArrayObject& vao,
|
||||||
VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
|
const RenderPassEntry& renderPassEntry);
|
||||||
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat);
|
VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj);
|
||||||
void PrepareDemoPipeline();
|
void DestroyComputePipelines();
|
||||||
VkPipeline GetOrCreatePipeline(const MG_State::GLState::ProgramObject& program, VkPipelineLayout pipelineLayout,
|
Bool PrepareStorageImageTextures(
|
||||||
Uint64 vertexInputHash,
|
VkCommandBuffer commandBuffer,
|
||||||
const VkPipelineVertexInputStateCreateInfo& vertexInputState);
|
const MG_State::GLState::ProgramObject& program,
|
||||||
void TransitionSwapchainImageToColorAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex);
|
const ProgramFactory::VkProgramObject& programObj);
|
||||||
void TransitionDepthStencilImageToAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex);
|
|
||||||
void EndFrameRecordingIfNeeded();
|
Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao,
|
||||||
void DeferDestroyBuffer(VkBufferObject& buffer);
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
void CollectDeferredBufferReleases(Uint32 frameIndex);
|
const DrawCmdParam& drawParams,
|
||||||
Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset,
|
const IndexBufferView* pIndexBufferView);
|
||||||
VkDeviceSize minCapacity, VkBufferUsageFlags usage);
|
Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame,
|
||||||
Bool UploadAndBindVertexStreams(const VertexInputStateFactory::BackendVertexInputState& vertexInputState,
|
const MG_State::GLState::VertexArrayObject& vao,
|
||||||
const DrawArrayPayload& payload, VkCommandBuffer commandBuffer);
|
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);
|
||||||
|
Bool MaterializePendingClearForRenderbuffer(
|
||||||
|
VkCommandBuffer commandBuffer,
|
||||||
|
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||||
|
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();
|
void ShutdownSwapchain();
|
||||||
|
|
||||||
|
// Static functions
|
||||||
static Int GetPresentQueueFamilyIndex(const PhysicalDevice& physicalDevice, VkSurfaceKHR surface,
|
static Int GetPresentQueueFamilyIndex(const PhysicalDevice& physicalDevice, VkSurfaceKHR surface,
|
||||||
const Vector<VkQueueFamilyProperties>& queueFamilies,
|
const Vector<VkQueueFamilyProperties>& queueFamilies,
|
||||||
Int preferredFamilyIndex = -1);
|
Int preferredFamilyIndex = -1);
|
||||||
@@ -212,11 +597,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface,
|
static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface,
|
||||||
const PhysicalDevice& compareWithDevice,
|
const PhysicalDevice& compareWithDevice,
|
||||||
PhysicalDevice& outBetterDevice);
|
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_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"};
|
||||||
static constexpr const char* s_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
static constexpr const char* s_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||||
static Bool CheckValidationLayerSupport();
|
static Bool CheckValidationLayerSupport();
|
||||||
|
|||||||
@@ -10,16 +10,61 @@
|
|||||||
|
|
||||||
#include "VulkanRendererConfig.h"
|
#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, ...) \
|
#define VK_VERIFY(expr, ...) \
|
||||||
do { \
|
do { \
|
||||||
VkResult _vk_verify_result = (expr); \
|
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)
|
} while (0)
|
||||||
|
|
||||||
#define ENUM_STR_CASE(c) case c: return #c;
|
|
||||||
|
|
||||||
#define XXHASH_VERIFY(expr, ...) \
|
#define XXHASH_VERIFY(expr, ...) \
|
||||||
do { \
|
do { \
|
||||||
XXH_errorcode _xxh_verify_result = (expr); \
|
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__); \
|
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 {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
struct VulkanRendererConfig {
|
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";
|
String AppName = "MobileGL-VulkanRenderer";
|
||||||
Version Version = MG_Config::CoreVersion;
|
MobileGL::Version Version = MG_Config::CoreVersion;
|
||||||
Uint64 CacheVersion = MG_Config::CacheVersion;
|
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;
|
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}
|
${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)
|
add_test(NAME SanityBench COMMAND SanityBench --benchmark_counters_tabular=true)
|
||||||
|
set_tests_properties(SanityBench PROPERTIES LABELS benchmark)
|
||||||
|
|
||||||
add_subdirectory(Program)
|
add_subdirectory(Program)
|
||||||
add_subdirectory(Buffer)
|
add_subdirectory(Buffer)
|
||||||
@@ -16,4 +16,5 @@ target_link_libraries(
|
|||||||
${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
|
||||||
@@ -8,8 +8,11 @@
|
|||||||
|
|
||||||
#include "EGLImpl.h"
|
#include "EGLImpl.h"
|
||||||
#include "../GetProcAddress.h"
|
#include "../GetProcAddress.h"
|
||||||
|
#include <Init.h>
|
||||||
#include <MG_Backend/BackendObjects.h>
|
#include <MG_Backend/BackendObjects.h>
|
||||||
#include <MG_State/EGLState/Core.h>
|
#include <MG_State/EGLState/Core.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sstream>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::EGLImpl {
|
namespace MobileGL::MG_Impl::EGLImpl {
|
||||||
@@ -20,7 +23,18 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!MG_State::pEGLContext) {
|
if (!MG_State::pEGLContext) {
|
||||||
MGLOG_E("pEGLContext is null. MG_State may not be initialized.");
|
MGLOG_E("pEGLContext is null. MG_State may not be initialized.");
|
||||||
}
|
}
|
||||||
return MG_State::pEGLContext;
|
return MG_State::pEGLContext.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry points that can legitimately be an application's FIRST EGL
|
||||||
|
// call (display/proc-address/string queries) lazily bring MobileGL
|
||||||
|
// up here, so the library needs no static constructor and can
|
||||||
|
// re-initialize after the last eglTerminate tore everything down.
|
||||||
|
// Teardown-ish entry points keep using GetState() and fail benignly
|
||||||
|
// when MobileGL is not initialized.
|
||||||
|
EGLStateContext* GetStateEnsureInitialized() {
|
||||||
|
MobileGL::EnsureInitialized();
|
||||||
|
return GetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
MG_Backend::BackendObject* GetBackendObject(EGLStateContext* state) {
|
MG_Backend::BackendObject* GetBackendObject(EGLStateContext* state) {
|
||||||
@@ -31,14 +45,55 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return backendObject;
|
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() {
|
MG_Backend::WindowBackend DetectWindowBackend() {
|
||||||
#if defined(ANDROID) || defined(__ANDROID__)
|
#if defined(ANDROID) || defined(__ANDROID__)
|
||||||
return MG_Backend::WindowBackend::Android;
|
return MG_Backend::WindowBackend::Android;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
return MG_Backend::WindowBackend::MetalLayer;
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
return MG_Backend::WindowBackend::Win32;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
return MG_Backend::WindowBackend::X11;
|
||||||
#else
|
#else
|
||||||
return MG_Backend::WindowBackend::Unknown;
|
return MG_Backend::WindowBackend::Unknown;
|
||||||
#endif
|
#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>
|
template <typename NativeType>
|
||||||
Bool IsNullNativeHandle(NativeType nativeHandle) {
|
Bool IsNullNativeHandle(NativeType nativeHandle) {
|
||||||
if constexpr (std::is_pointer_v<NativeType>) {
|
if constexpr (std::is_pointer_v<NativeType>) {
|
||||||
@@ -77,25 +132,35 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return EGL_NO_SURFACE;
|
return EGL_NO_SURFACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* backendObject = GetBackendObject(state);
|
|
||||||
if (!backendObject) {
|
|
||||||
MGLOG_E("activeBackendObject not initialized!");
|
|
||||||
return EGL_NO_SURFACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MG_Backend::WindowHandle windowHandle = {
|
const MG_Backend::WindowHandle windowHandle = {
|
||||||
.Backend = DetectWindowBackend(),
|
.Backend = DetectWindowBackend(),
|
||||||
.Handle = ToVoidHandle(window),
|
.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);
|
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||||
return EGL_NO_SURFACE;
|
return EGL_NO_SURFACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state->CreateWindowSurface(dpy, config, window, attrib_list);
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||||
auto* state = GetState();
|
auto* state = GetState();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
@@ -111,6 +176,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
if (!backendObject->SwapEGLBuffers(dpy, draw)) {
|
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);
|
state->SetError(EGL_BAD_SURFACE);
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
@@ -135,7 +201,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean Initialize(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
EGLBoolean Initialize(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||||
auto* state = GetState();
|
auto* state = GetStateEnsureInitialized();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
@@ -156,7 +222,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EGLDisplay GetDisplay(NativeDisplayType display) {
|
EGLDisplay GetDisplay(NativeDisplayType display) {
|
||||||
auto* state = GetState();
|
auto* state = GetStateEnsureInitialized();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_NO_DISPLAY;
|
return EGL_NO_DISPLAY;
|
||||||
}
|
}
|
||||||
@@ -172,6 +238,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
EGLBoolean MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||||
auto* state = GetState();
|
auto* state = GetState();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
@@ -181,17 +248,30 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
const auto oldDraw = state->GetCurrentSurface(EGL_DRAW);
|
const auto oldDraw = state->GetCurrentSurface(EGL_DRAW);
|
||||||
const auto oldRead = state->GetCurrentSurface(EGL_READ);
|
const auto oldRead = state->GetCurrentSurface(EGL_READ);
|
||||||
const auto oldContext = state->GetCurrentContext();
|
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)) {
|
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;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bool releaseCurrentRequest =
|
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 (releaseCurrentRequest) {
|
||||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
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;
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,10 +282,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
|
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->SetError(EGL_BAD_ACCESS);
|
||||||
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
||||||
return EGL_FALSE;
|
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;
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,11 +302,18 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean DestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
EGLBoolean DestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||||
auto* state = GetState();
|
auto* state = GetState();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
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) {
|
EGLBoolean Terminate(EGLDisplay dpy) {
|
||||||
@@ -230,7 +321,21 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
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();
|
||||||
|
}
|
||||||
|
// The last initialized display is gone and nothing is current on any
|
||||||
|
// thread: tear the whole library down deterministically inside the
|
||||||
|
// EGL lifecycle (backend, GL/EGL state, glslang). A later EGL call
|
||||||
|
// re-initializes lazily via GetStateEnsureInitialized(); process exit
|
||||||
|
// then has nothing left to destroy.
|
||||||
|
if (!state->HasAnyInitializedDisplay() && !state->HasAnyCurrentContext()) {
|
||||||
|
MobileGL::Destroy();
|
||||||
|
}
|
||||||
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean ReleaseThread() {
|
EGLBoolean ReleaseThread() {
|
||||||
@@ -238,6 +343,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
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();
|
state->ReleaseThread();
|
||||||
return EGL_TRUE;
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
@@ -259,7 +367,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean BindAPI(EGLenum api) {
|
EGLBoolean BindAPI(EGLenum api) {
|
||||||
auto* state = GetState();
|
auto* state = GetStateEnsureInitialized();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
@@ -292,7 +400,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char const* QueryString(EGLDisplay display, EGLint name) {
|
char const* QueryString(EGLDisplay display, EGLint name) {
|
||||||
auto* state = GetState();
|
auto* state = GetStateEnsureInitialized();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -310,7 +418,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
case EGL_CLIENT_APIS:
|
case EGL_CLIENT_APIS:
|
||||||
return "OpenGL OpenGL_ES";
|
return "OpenGL OpenGL_ES";
|
||||||
case EGL_EXTENSIONS:
|
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:
|
default:
|
||||||
state->SetError(EGL_BAD_PARAMETER);
|
state->SetError(EGL_BAD_PARAMETER);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -322,7 +437,17 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
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) {
|
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
||||||
@@ -330,7 +455,24 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_NO_SURFACE;
|
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) {
|
EGLBoolean BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||||
@@ -521,7 +663,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list) {
|
EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list) {
|
||||||
(void)attrib_list;
|
(void)attrib_list;
|
||||||
|
|
||||||
auto* state = GetState();
|
auto* state = GetStateEnsureInitialized();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_NO_DISPLAY;
|
return EGL_NO_DISPLAY;
|
||||||
}
|
}
|
||||||
@@ -547,22 +689,53 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return EGL_NO_SURFACE;
|
return EGL_NO_SURFACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* backendObject = GetBackendObject(state);
|
|
||||||
if (!backendObject) {
|
|
||||||
MGLOG_E("activeBackendObject not initialized!");
|
|
||||||
return EGL_NO_SURFACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MG_Backend::WindowHandle windowHandle = {
|
const MG_Backend::WindowHandle windowHandle = {
|
||||||
.Backend = DetectWindowBackend(),
|
.Backend = DetectWindowBackend(),
|
||||||
.Handle = native_window,
|
.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);
|
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||||
return EGL_NO_SURFACE;
|
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,
|
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
||||||
@@ -586,6 +759,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!name) {
|
if (!name) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
MobileGL::EnsureInitialized();
|
||||||
|
|
||||||
MGLOG_D("eglGetProcAddress(%s)", name);
|
MGLOG_D("eglGetProcAddress(%s)", name);
|
||||||
void* proc = MG_Impl::GetProcAddress(name);
|
void* proc = MG_Impl::GetProcAddress(name);
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list);
|
EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list);
|
||||||
EGLSurface CreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
EGLSurface CreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
||||||
const EGLAttrib* attrib_list);
|
const EGLAttrib* attrib_list);
|
||||||
|
EGLBoolean ResizePlatformWindowSurface(EGLDisplay dpy, EGLSurface surface, EGLint width, EGLint height);
|
||||||
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
||||||
const EGLAttrib* attrib_list);
|
const EGLAttrib* attrib_list);
|
||||||
EGLBoolean WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags);
|
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);
|
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,
|
MOBILEGL_EGL_API EGLSurface eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
||||||
const EGLAttrib* attrib_list) {
|
const EGLAttrib* attrib_list) {
|
||||||
MGLOG_D("eglCreatePlatformWindowSurface(dpy=%p, config=%p, native_window=%p, attrib_list=%p)", dpy, config,
|
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
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
namespace MG_Impl::GLImpl {
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||||
void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
void GetBufferParameteri64v(GLenum target, GLenum pname, GLint64* params);
|
||||||
GLboolean IsBuffer(GLuint buffer);
|
void GetBufferPointerv(GLenum target, GLenum pname, void** params);
|
||||||
void DeleteBuffers(GLsizei n, const GLuint* buffers);
|
GLboolean IsBuffer(GLuint buffer);
|
||||||
void FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length);
|
void DeleteBuffers(GLsizei n, const GLuint* buffers);
|
||||||
GLboolean UnmapBuffer(GLenum target);
|
void FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length);
|
||||||
void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
GLboolean UnmapBuffer(GLenum target);
|
||||||
void* MapBuffer(GLenum target, GLenum access);
|
void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||||
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset,
|
void* MapBuffer(GLenum target, GLenum access);
|
||||||
GLsizeiptr size);
|
void BufferStorage(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags);
|
||||||
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
void CreateBuffers(GLsizei n, GLuint* buffers);
|
||||||
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
void NamedBufferStorage(GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags);
|
||||||
void BindBuffer(GLenum target, GLuint buffer);
|
void NamedBufferData(GLuint buffer, GLsizeiptr size, const void* data, GLenum usage);
|
||||||
void GenBuffers(GLsizei n, GLuint* buffers);
|
void NamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data);
|
||||||
void BindBufferBase(GLenum target, GLuint index, GLuint buffer);
|
void CopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset,
|
||||||
void BindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
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::MG_Impl::GLImpl
|
||||||
} // namespace MobileGL
|
|
||||||
|
|||||||
@@ -7,111 +7,127 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "Validators.h"
|
#include "Validators.h"
|
||||||
|
#include <MG_Backend/BackendObjects.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/GLState/ErrorState/Error.h>
|
#include <MG_State/GLState/ErrorState/Error.h>
|
||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/BufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/BufferEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToStr/BufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToStr/BufferEnumConverter.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
||||||
namespace BufferImpl {
|
Bool ValidateBufferTarget(BufferTarget target) {
|
||||||
Bool ValidateBufferTarget(BufferTarget target) {
|
if (target == BufferTarget::Unknown) {
|
||||||
if (target == BufferTarget::Unknown) {
|
using namespace MG_Util;
|
||||||
using namespace MG_Util;
|
String bufferTargetStr = ConvertBufferTargetToString(target);
|
||||||
String bufferTargetStr = ConvertBufferTargetToString(target);
|
String glTargetStr = ConvertGLEnumToString(ConvertBufferTargetToGLEnum(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;
|
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>(
|
||||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferName",
|
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferTarget",
|
||||||
std::format("Buffer name {} is not valid.", index)));
|
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateBufferUsage(BufferUsage usage) {
|
if (target == BufferTarget::Index && MG_State::pGLContext->GetBoundVertexArray() == nullptr) {
|
||||||
if (usage != BufferUsage::Unknown) {
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation,
|
||||||
return true;
|
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;
|
using namespace MG_Util;
|
||||||
String bufferUsageStr = ConvertBufferUsageToString(usage);
|
String bufferTargetStr = ConvertBufferTargetToString(target);
|
||||||
String glUsageStr = ConvertGLEnumToString(ConvertBufferUsageToGLEnum(usage));
|
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(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidEnum,
|
ErrorCode::InvalidEnum,
|
||||||
MakeShared<GenericErrorInfo>(
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferMappingAccess",
|
||||||
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferUsage",
|
"Access bits cannot contain invalid flags."));
|
||||||
std::format("Usage {} ({}) is not one of the allowable values.", bufferUsageStr, glUsageStr)));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
return true;
|
||||||
if (accessBits == BufferMappingAccessBit::Null) {
|
}
|
||||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
} // namespace MobileGL::MG_Impl::GLImpl::BufferImpl
|
||||||
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
|
|
||||||
|
|||||||
@@ -10,12 +10,11 @@
|
|||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <MG_State/GLState/BufferState/BufferObject.h>
|
#include <MG_State/GLState/BufferState/BufferObject.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
||||||
namespace BufferImpl {
|
Bool ValidateBufferTarget(BufferTarget target);
|
||||||
Bool ValidateBufferTarget(BufferTarget target);
|
Bool ValidateBufferName(Uint index, Bool allowZero = false);
|
||||||
Bool ValidateBufferName(Uint index, Bool allowZero = false);
|
Bool ValidateBufferUsage(BufferUsage usage);
|
||||||
Bool ValidateBufferUsage(BufferUsage usage);
|
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits);
|
||||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits);
|
Bool ValidateBufferBindingPointTarget(BufferTarget target);
|
||||||
Bool ValidateBufferBindingPointTarget(BufferTarget target);
|
Bool ValidateBufferBindingPointIndex(BufferTarget target, Uint index);
|
||||||
} // namespace BufferImpl
|
} // namespace MobileGL::MG_Impl::GLImpl::BufferImpl
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
|
||||||
|
|||||||
@@ -9,9 +9,75 @@
|
|||||||
#include "GL_Drawing.h"
|
#include "GL_Drawing.h"
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_State/EGLState/Core.h>
|
||||||
#include <MG_Backend/BackendObjects.h>
|
#include <MG_Backend/BackendObjects.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
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) {
|
void Clear_Backend(GLbitfield mask) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
@@ -50,6 +116,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MG_Backend::gBackendFunctionsTable.GL.DrawArrays(mode, first, count);
|
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,
|
void DrawElementsBaseVertex_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLint basevertex) {
|
GLint basevertex) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
@@ -73,6 +146,24 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirect(mode, indirect, drawcount, stride);
|
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,
|
void DrawRangeElementsBaseVertex_Backend(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex) {
|
const void* indices, GLint basevertex) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
@@ -156,75 +247,193 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
/* @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) {
|
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);
|
MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei 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);
|
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,
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex) {
|
const void* indices, GLint basevertex) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawRangeElementsBaseVertex_Backend(mode, start, end, count, type, indices, basevertex);
|
DrawRangeElementsBaseVertex_Backend(mode, start, end, count, type, indices, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {
|
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);
|
DrawRangeElements_Backend(mode, start, end, count, type, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstancedBaseVertexBaseInstance_Backend(mode, count, type, indices, instancecount, basevertex,
|
DrawElementsInstancedBaseVertexBaseInstance_Backend(mode, count, type, indices, instancecount, basevertex,
|
||||||
baseinstance);
|
baseinstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLint basevertex) {
|
GLsizei instancecount, GLint basevertex) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstancedBaseVertex_Backend(mode, count, type, indices, instancecount, basevertex);
|
DrawElementsInstancedBaseVertex_Backend(mode, count, type, indices, instancecount, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLuint baseinstance) {
|
GLsizei instancecount, GLuint baseinstance) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstancedBaseInstance_Backend(mode, count, type, indices, instancecount, baseinstance);
|
DrawElementsInstancedBaseInstance_Backend(mode, count, type, indices, instancecount, baseinstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) {
|
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);
|
DrawElementsInstanced_Backend(mode, count, type, indices, instancecount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsIndirect_Backend(mode, type, indirect);
|
DrawElementsIndirect_Backend(mode, type, indirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||||
GLuint baseinstance) {
|
GLuint baseinstance) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArraysInstancedBaseInstance_Backend(mode, first, count, instancecount, baseinstance);
|
DrawArraysInstancedBaseInstance_Backend(mode, first, count, instancecount, baseinstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
|
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);
|
DrawArraysInstanced_Backend(mode, first, count, instancecount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArraysIndirect_Backend(mode, indirect);
|
DrawArraysIndirect_Backend(mode, indirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
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);
|
DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArrays_Backend(mode, first, count);
|
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,
|
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||||
GLsizei drawcount) {
|
GLsizei drawcount) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawElements_Backend(mode, count, type, indices, drawcount);
|
MultiDrawElements_Backend(mode, count, type, indices, drawcount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||||
GLsizei drawcount, const GLint* basevertex) {
|
GLsizei drawcount, const GLint* basevertex) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawElementsBaseVertex_Backend(mode, count, type, indices, drawcount, basevertex);
|
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) {
|
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);
|
DrawElements_Backend(mode, count, type, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,34 +9,40 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
namespace MG_Impl::GLImpl {
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
GLsizei stride);
|
void MemoryBarrier(GLbitfield barriers);
|
||||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
const void* indices, GLint basevertex);
|
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance);
|
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
GLsizei instancecount, GLint basevertex);
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
const void* indices, GLint basevertex);
|
||||||
GLsizei instancecount, GLuint baseinstance);
|
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
||||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount);
|
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect);
|
GLsizei instancecount, GLint basevertex, GLuint baseinstance);
|
||||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLuint baseinstance);
|
GLsizei instancecount, GLint basevertex);
|
||||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
GLsizei instancecount, GLuint baseinstance);
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex);
|
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount);
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect);
|
||||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||||
GLsizei drawcount);
|
GLuint baseinstance);
|
||||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||||
GLsizei drawcount, const GLint* basevertex);
|
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||||
void Clear(GLbitfield mask);
|
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex);
|
||||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||||
} // namespace MG_Impl::GLImpl
|
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||||
} // namespace MobileGL
|
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 <Includes.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
namespace MG_Impl::GLImpl {
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||||
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 ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
||||||
void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
GLsizei height);
|
||||||
GLsizei height);
|
void RenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||||
void RenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
GLboolean IsRenderbuffer(GLuint renderbuffer);
|
||||||
GLboolean IsRenderbuffer(GLuint renderbuffer);
|
void GetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||||
void GetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
void GenRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||||
void GenRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
void CreateRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||||
void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
void NamedRenderbufferStorage(GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height);
|
||||||
void DeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers);
|
void NamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat,
|
||||||
void BindRenderbuffer(GLenum target, GLuint renderbuffer);
|
GLsizei width, GLsizei height);
|
||||||
void SampleMaski(GLuint maskNumber, GLbitfield mask);
|
void GetNamedRenderbufferParameteriv(GLuint renderbuffer, GLenum pname, GLint* params);
|
||||||
GLboolean IsFramebuffer(GLuint framebuffer);
|
void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
||||||
void GetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint* params);
|
void NamedFramebufferRenderbuffer(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget,
|
||||||
void GenFramebuffers(GLsizei n, GLuint* framebuffers);
|
GLuint renderbuffer);
|
||||||
void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
void DeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers);
|
||||||
void FramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
void BindRenderbuffer(GLenum target, GLuint renderbuffer);
|
||||||
GLint zoffset);
|
void SampleMaski(GLuint maskNumber, GLbitfield mask);
|
||||||
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
GLboolean IsFramebuffer(GLuint framebuffer);
|
||||||
void FramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
void GetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint* params);
|
||||||
void FramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
|
void GenFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||||
void DrawBuffer(GLenum buf);
|
void CreateFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||||
void DrawBuffers(GLsizei n, const GLenum* bufs);
|
void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
||||||
void ReadBuffer(GLenum src);
|
void FramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
||||||
void DeleteFramebuffers(GLsizei n, const GLuint* framebuffers);
|
GLint zoffset);
|
||||||
GLenum CheckFramebufferStatus(GLenum target);
|
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
void FramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
void FramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
|
||||||
void BindFramebuffer(GLenum target, GLuint framebuffer);
|
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 {
|
namespace FramebufferImpl {
|
||||||
struct DefaultFramebufferInfo {
|
struct DefaultFramebufferInfo {
|
||||||
SharedPtr<MG_State::GLState::FramebufferObject> defaultFBO;
|
SharedPtr<MG_State::GLState::FramebufferObject> defaultFBO;
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> colorAttachment;
|
SharedPtr<MG_State::GLState::ITextureObject> colorAttachment;
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> depthAttachment;
|
SharedPtr<MG_State::GLState::ITextureObject> depthAttachment;
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> stencilAttachment;
|
SharedPtr<MG_State::GLState::ITextureObject> stencilAttachment;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern DefaultFramebufferInfo* pDefaultFramebufferInfo;
|
extern UniquePtr<DefaultFramebufferInfo>& pDefaultFramebufferInfo;
|
||||||
} // namespace FramebufferImpl
|
} // namespace FramebufferImpl
|
||||||
} // namespace MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
} // namespace MobileGL
|
|
||||||
|
|||||||
@@ -13,85 +13,88 @@
|
|||||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToStr/FramebufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToStr/FramebufferEnumConverter.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
||||||
namespace FramebufferImpl {
|
Bool ValidateFramebufferTarget(FramebufferTarget target) {
|
||||||
Bool ValidateFramebufferTarget(FramebufferTarget target) {
|
if (target == FramebufferTarget::Unknown) {
|
||||||
if (target == FramebufferTarget::Unknown) {
|
using namespace MG_Util;
|
||||||
using namespace MG_Util;
|
String bufferTargetStr = ConvertFramebufferTargetToString(target);
|
||||||
String bufferTargetStr = ConvertFramebufferTargetToString(target);
|
String glTargetStr = ConvertGLEnumToString(ConvertFramebufferTargetToGLEnum(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;
|
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>(
|
||||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferName",
|
"MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferTarget",
|
||||||
std::format("Framebuffer name {} is not valid.", index)));
|
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment) {
|
Bool ValidateFramebufferName(Uint index, Bool allowZero) {
|
||||||
if (attachment == FramebufferAttachmentType::Unknown) {
|
if (index == 0 && !allowZero) {
|
||||||
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;
|
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidValue,
|
||||||
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateFramebufferName",
|
||||||
std::format("Renderbuffer name {} is not valid.", index)));
|
"Framebuffer name 0 is not valid in this situation."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} // namespace FramebufferImpl
|
Bool isValid = MG_State::pGLContext->ValidateFramebufferName(index);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
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 <Includes.h>
|
||||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
||||||
namespace FramebufferImpl {
|
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
||||||
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
||||||
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
|
||||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
|
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
||||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
||||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||||
} // namespace FramebufferImpl
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
const GLubyte* GetString(GLenum name);
|
const GLubyte* GetString(GLenum name);
|
||||||
const GLubyte* GetStringi(GLenum name, GLuint index);
|
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 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();
|
GLenum GetError();
|
||||||
|
GLenum GetGraphicsResetStatus();
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user