mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
Compare commits
704
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3d52faad4 | ||
|
|
ba81ee114e | ||
|
|
c8c7b19579 | ||
|
|
0e7692251d | ||
|
|
34f09291da | ||
|
|
3b65e646e1 | ||
|
|
25b9370815 | ||
|
|
4ce808b9f2 | ||
|
|
5545d31c37 | ||
|
|
588ddba722 | ||
|
|
62301b1061 | ||
|
|
f3a846d336 | ||
|
|
9cdc82fbdd | ||
|
|
4a9d20c49f | ||
|
|
394d1ce748 | ||
|
|
300b458132 | ||
|
|
1f1a331a44 | ||
|
|
817091641c | ||
|
|
e64c7c7e65 | ||
|
|
dd60ff39ce | ||
|
|
96ad7ca0cc | ||
|
|
e80a23eae6 | ||
|
|
088f263495 | ||
|
|
3b3b6e5b8b | ||
|
|
9eda2147b1 | ||
|
|
a63699cde6 | ||
|
|
765aaec6dc | ||
|
|
bcd669bd25 | ||
|
|
f3405d1d53 | ||
|
|
81604d5596 | ||
|
|
31ea6aa5a3 | ||
|
|
7d6f6603c1 | ||
|
|
39c17c0b1b | ||
|
|
88138b48ec | ||
|
|
c114ce750b | ||
|
|
1ca2d3c0fe | ||
|
|
58c17f85a5 | ||
|
|
4873da6844 | ||
|
|
efeb24ff9b | ||
|
|
18c1a4d586 | ||
|
|
66ac3486e1 | ||
|
|
764a44f589 | ||
|
|
d96acb7972 | ||
|
|
bd710078fc | ||
|
|
95a7b17d45 | ||
|
|
19932f9e49 | ||
|
|
1011d9fea1 | ||
|
|
b5565ae503 | ||
|
|
b06ad3f877 | ||
|
|
3311e6034a | ||
|
|
027c1bd4ab | ||
|
|
da52cc3906 | ||
|
|
ebe4fe133f | ||
|
|
534ec65dda | ||
|
|
35ad1ae7fc | ||
|
|
6152ee933f | ||
|
|
dac02ca044 | ||
|
|
42fd02d82f | ||
|
|
6359b0002b | ||
|
|
c186f5f255 | ||
|
|
3d97f6fa8f | ||
|
|
bb582203d9 | ||
|
|
f39e6eb82d | ||
|
|
cb2ba71feb | ||
|
|
6ea7ccdf64 | ||
|
|
14605723f0 | ||
|
|
a680611c9f | ||
|
|
93224ca406 | ||
|
|
fbed4485b7 | ||
|
|
90ae0f048c | ||
|
|
cff959b2e8 | ||
|
|
a50b2c422b | ||
|
|
9dcda82d71 | ||
|
|
28d0af6f04 | ||
|
|
44ee6b66b3 | ||
|
|
28c3cfc1d6 | ||
|
|
38e04eefae | ||
|
|
fd29cb914e | ||
|
|
38497174c8 | ||
|
|
b95fcb7bca | ||
|
|
5fce287de5 | ||
|
|
ae6949e459 | ||
|
|
5437947240 | ||
|
|
f38dbf018d | ||
|
|
2dcc15bb0e | ||
|
|
ff76af9df7 | ||
|
|
76f37a18e6 | ||
|
|
8d1a734c22 | ||
|
|
7d215028fb | ||
|
|
00534d8bbc | ||
|
|
d81a6a0998 | ||
|
|
9bf23d7ffd | ||
|
|
41e45f7d48 | ||
|
|
3dc6a1b6db | ||
|
|
598c5497b0 | ||
|
|
86c00bdf18 | ||
|
|
2f2f95498f | ||
|
|
7105c2ebdc | ||
|
|
13bab780f2 | ||
|
|
027310f993 | ||
|
|
c741a938bc | ||
|
|
01d0f01d13 | ||
|
|
e45f7ae5d4 | ||
|
|
a687873d32 | ||
|
|
43a43c1180 | ||
|
|
65dbfa6f26 | ||
|
|
6de38c666c | ||
|
|
9dff24f3e1 | ||
|
|
5a400e0297 | ||
|
|
b6a2bf08d4 | ||
|
|
6b2a2b5e00 | ||
|
|
512c857f18 | ||
|
|
9f3cac6691 | ||
|
|
4c7332d5e6 | ||
|
|
1c5f6c0986 | ||
|
|
8b75628dec | ||
|
|
8269a1786f | ||
|
|
3019c68945 | ||
|
|
800142c104 | ||
|
|
e9382f5329 | ||
|
|
df7d1edeca | ||
|
|
8025745fa3 | ||
|
|
4a533a215a | ||
|
|
0a5d7ceb6c | ||
|
|
ac81185968 | ||
|
|
9e861f3f7a | ||
|
|
da6f75dbd1 | ||
|
|
951da362f7 | ||
|
|
4c929b9b3f | ||
|
|
8d5072543a | ||
|
|
5de2b9e3e9 | ||
|
|
9192d156d1 | ||
|
|
27cdfbc0ca | ||
|
|
4567c3b468 | ||
|
|
d18c6a1bae | ||
|
|
dd745d7547 | ||
|
|
4407be89cd | ||
|
|
0c1a433af6 | ||
|
|
a2f3efe22c | ||
|
|
b9a15aed61 | ||
|
|
f748a06632 | ||
|
|
4532cae175 | ||
|
|
107b56d603 | ||
|
|
22b749dd37 | ||
|
|
f0c0211767 | ||
|
|
9ebbb76df1 | ||
|
|
95547ab9ce | ||
|
|
8eaf2d0069 | ||
|
|
54a8609c64 | ||
|
|
1fb0eb0737 | ||
|
|
282dd69230 | ||
|
|
e6ebe7078d | ||
|
|
30a91023f6 | ||
|
|
47dd8cdc05 | ||
|
|
8b36a15fb3 | ||
|
|
07fa84fb8d | ||
|
|
a03817b4ee | ||
|
|
641bc0cdd9 | ||
|
|
c069890ac7 | ||
|
|
48dd1c5956 | ||
|
|
a389477f78 | ||
|
|
94a8f1e3f3 | ||
|
|
45d506545e | ||
|
|
d9d63c9496 | ||
|
|
92140405c1 | ||
|
|
b9ecfef0b6 | ||
|
|
fba26ea169 | ||
|
|
93a3b55907 | ||
|
|
d1487bedf0 | ||
|
|
1bb736c57e | ||
|
|
eb76686c1e | ||
|
|
1a04fb8c0c | ||
|
|
306790ee7c | ||
|
|
170ccda3e7 | ||
|
|
e86a9bbec5 | ||
|
|
c5569e71b3 | ||
|
|
7e8c32a063 | ||
|
|
77bd03d962 | ||
|
|
37111ae992 | ||
|
|
6b0c2a15ab | ||
|
|
e9ffd99313 | ||
|
|
7c01ddea0c | ||
|
|
2d4d6e9cfb | ||
|
|
76b8957b99 | ||
|
|
ec685b9fa7 | ||
|
|
a12068df52 | ||
|
|
0b344792cc | ||
|
|
9fa32bdad0 | ||
|
|
a4980f2b56 | ||
|
|
8ca20e28ca | ||
|
|
c353a2055f | ||
|
|
421c20984e | ||
|
|
fc4cd980f2 | ||
|
|
992d16267c | ||
|
|
0ea9e6de5f | ||
|
|
241ed377b4 | ||
|
|
bf312a4b67 | ||
|
|
56b31a9587 | ||
|
|
8a0a8a0274 | ||
|
|
d076c29146 | ||
|
|
930a607bdf | ||
|
|
34685b4bb0 | ||
|
|
c540fb88ee | ||
|
|
6ae3245a0d | ||
|
|
7e048fc2bf | ||
|
|
83cdfd6bdd | ||
|
|
1c76f886cf | ||
|
|
a2e109beff | ||
|
|
63f0756644 | ||
|
|
450215d12c | ||
|
|
3a9e520170 | ||
|
|
d2996ba1cf | ||
|
|
c8632dfefe | ||
|
|
b8a8a660e1 | ||
|
|
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 | ||
|
|
727939af5b |
@@ -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,246 @@
|
|||||||
|
#!/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
|
||||||
|
}
|
||||||
|
|
||||||
|
# Files no mirror could serve, even after retrying every mirror. Only these fall
|
||||||
|
# back to Git LFS, so a mirror that served the rest of the case still spares
|
||||||
|
# GitHub the bandwidth for those files.
|
||||||
|
mirror_failures=()
|
||||||
|
|
||||||
|
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
|
||||||
|
mirror_failures+=("${file}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ "${#mirror_failures[@]}" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
if fetch_from_mirror; then
|
||||||
|
echo "Fetched trace fixture files for ${case_name} from mirror: ${include}"
|
||||||
|
else
|
||||||
|
fallback_include="$(IFS=,; echo "${mirror_failures[*]}")"
|
||||||
|
echo "All mirrors failed for ${#mirror_failures[@]} of ${#files[@]} file(s) of ${case_name}; falling back to Git LFS: ${fallback_include}"
|
||||||
|
git lfs install --local
|
||||||
|
git lfs pull --include="${fallback_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
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
name: MobileGL Plugin APK
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- Feat/Backend-Direct-GLES
|
|
||||||
- Feat/Backend-Direct-Vulkan
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Set artifact metadata
|
|
||||||
run: |
|
|
||||||
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
|
||||||
echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Set up JDK
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: zulu
|
|
||||||
java-version: '17'
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
with:
|
|
||||||
gradle-version: 8.10.2
|
|
||||||
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
|
|
||||||
- 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:assembleRelease
|
|
||||||
env:
|
|
||||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
|
||||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
|
||||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Verify signed APK
|
|
||||||
run: |
|
|
||||||
APKSIGNER="$(find "$ANDROID_HOME/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
|
|
||||||
mapfile -t APKS < <(find android-plugin/app/build/outputs/apk -type f -path '*/release/*.apk' | sort)
|
|
||||||
if [ "${#APKS[@]}" -eq 0 ]; then
|
|
||||||
echo "::error::No release APKs found under android-plugin/app/build/outputs/apk"
|
|
||||||
find android-plugin/app/build/outputs/apk -type f -print || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
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@v4
|
|
||||||
with:
|
|
||||||
name: MobileGL-plugin-${{ env.date_today }}-${{ env.short_sha }}
|
|
||||||
path: |
|
|
||||||
android-plugin/app/build/outputs/apk/release/*.apk
|
|
||||||
android-plugin/app/build/outputs/apk/**/release/*.apk
|
|
||||||
+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)."
|
||||||
|
|||||||
@@ -22,3 +22,8 @@ MobileGL/MG*/cmake-build*
|
|||||||
/android-plugin/.gradle
|
/android-plugin/.gradle
|
||||||
/android-plugin/build
|
/android-plugin/build
|
||||||
/android-plugin/app/build
|
/android-plugin/app/build
|
||||||
|
/android-plugin/app/src/trace/jniLibs
|
||||||
|
/android-plugin/local.properties
|
||||||
|
tools/trace_replay/work/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|||||||
@@ -28,3 +28,6 @@
|
|||||||
[submodule "3rdparty/SPIRV-Reflect"]
|
[submodule "3rdparty/SPIRV-Reflect"]
|
||||||
path = 3rdparty/SPIRV-Reflect
|
path = 3rdparty/SPIRV-Reflect
|
||||||
url = https://github.com/KhronosGroup/SPIRV-Reflect.git
|
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/apitrace added at 10935bb5e4
Vendored
+1
-1
Submodule 3rdparty/glslang updated: 26fe5ceb45...900b29d449
+149
-2
@@ -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)
|
||||||
@@ -181,10 +186,22 @@ set(SOURCE_FILES
|
|||||||
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/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/PackDoubleVertexInputsPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.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/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
|
||||||
|
|
||||||
@@ -202,6 +219,7 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp
|
MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp
|
MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp
|
MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp
|
||||||
|
MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp
|
MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp
|
MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp
|
||||||
MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp
|
MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp
|
||||||
@@ -212,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
|
||||||
@@ -239,6 +258,7 @@ set(SOURCE_FILES
|
|||||||
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/VkTextureManager.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/VkSamplerManager.cpp
|
||||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp
|
MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp
|
||||||
MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp
|
MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp
|
||||||
@@ -271,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
|
||||||
@@ -299,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
|
||||||
@@ -329,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}
|
||||||
@@ -362,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()
|
||||||
|
|
||||||
@@ -380,7 +457,62 @@ if (ANDROID)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT ANDROID)
|
if (APPLE AND NOT MOBILEGL_IOS)
|
||||||
|
# MobileGL statically embeds glslang, SPIRV-Tools, and SPIRV-Cross. When
|
||||||
|
# this dylib is injected with DYLD_INSERT_LIBRARIES, exporting those C++
|
||||||
|
# symbols interposes incompatible copies embedded by host libraries such
|
||||||
|
# as shaderc. Keep only the public GL/EGL/CGL loader surface globally
|
||||||
|
# visible; GetProcAddress can still return pointers to hidden internals.
|
||||||
|
set(MOBILEGL_MACOS_EXPORTED_SYMBOLS
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/MobileGL/MG_Impl/DyldInterpose/ExportedSymbols.txt")
|
||||||
|
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
|
||||||
|
"LINKER:-exported_symbols_list,${MOBILEGL_MACOS_EXPORTED_SYMBOLS}")
|
||||||
|
set_property(TARGET ${CMAKE_PROJECT_NAME} APPEND PROPERTY
|
||||||
|
LINK_DEPENDS "${MOBILEGL_MACOS_EXPORTED_SYMBOLS}")
|
||||||
|
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||||
|
"-framework Cocoa"
|
||||||
|
"-framework CoreVideo"
|
||||||
|
"-framework QuartzCore"
|
||||||
|
"-framework Foundation"
|
||||||
|
"-framework OpenGL"
|
||||||
|
objc)
|
||||||
|
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||||
|
"-framework Cocoa"
|
||||||
|
"-framework CoreVideo"
|
||||||
|
"-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)
|
||||||
@@ -388,6 +520,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)
|
||||||
@@ -396,4 +539,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()
|
||||||
|
|||||||
+75
-1
@@ -14,9 +14,83 @@ 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, 6, 0, "-dev", VersionType::Development};
|
inline const Version CoreVersion = {26, 8, 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_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,10 +8,19 @@
|
|||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
extern char** environ;
|
extern char** environ;
|
||||||
#endif
|
#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;
|
||||||
|
|
||||||
@@ -60,6 +69,77 @@ 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.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");
|
||||||
@@ -81,6 +161,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();
|
||||||
|
|||||||
+4
-1
@@ -32,10 +32,13 @@
|
|||||||
#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
|
#ifndef MOBILEGL_LOG_ACTIVE_LEVEL
|
||||||
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_DEBUG
|
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define MOBILEGL_LOG_ENABLE_CONSOLE 0
|
#define MOBILEGL_LOG_ENABLE_CONSOLE 0
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+81
-34
@@ -9,13 +9,62 @@
|
|||||||
#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 <MG_Impl/GLImpl/Sync/GL_Sync.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();
|
||||||
|
// GL syncs die with their contexts, and every context is gone by the
|
||||||
|
// time full teardown runs: drain the live-sync registry while the
|
||||||
|
// backend function table can still release the backend handles (and
|
||||||
|
// before a re-initialized library could pair them with the wrong
|
||||||
|
// backend's DeleteSync).
|
||||||
|
MG_Impl::GLImpl::DestroyAllSyncObjects();
|
||||||
|
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 +77,41 @@ 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;
|
||||||
MG_State::pGLContext.reset();
|
|
||||||
MG_State::pEGLContext.reset();
|
|
||||||
MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.reset();
|
|
||||||
MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.reset();
|
|
||||||
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 backend-initializing 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. macOS has a lightweight
|
||||||
|
// dyld constructor that installs NSOpenGL dispatch hooks only; full backend
|
||||||
|
// initialization still enters here from the first hooked CGL context.
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
@@ -11,6 +11,13 @@
|
|||||||
|
|
||||||
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
|
||||||
|
// full backend initialization never depends on ELF/DLL static constructors,
|
||||||
|
// and so a fresh init can follow a full Destroy() (e.g. after the last
|
||||||
|
// eglTerminate). The macOS dyld bootstrap installs only lightweight
|
||||||
|
// NSOpenGL method hooks.
|
||||||
|
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,57 +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);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
if (!m_eglDisplayInitialized) {
|
return RegisterEGLWindowSurface(surface, handle) && ActivateEGLSurface(surface);
|
||||||
MGLOG_E("CreateEGLWindowSurface failed: EGL display is not initialized");
|
}
|
||||||
|
|
||||||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (handle.Backend == WindowBackend::Unknown || !handle.Handle) {
|
surfaceIt->second.Window.Width = width;
|
||||||
MGLOG_E("CreateEGLWindowSurface failed: invalid native window handle");
|
surfaceIt->second.Window.Height = height;
|
||||||
return false;
|
if (m_eglSurface == surface) {
|
||||||
|
m_windowHandle.Width = width;
|
||||||
|
m_windowHandle.Height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_eglSurfaceInitialized && m_eglSurfaceKind == SurfaceKind::Window &&
|
|
||||||
m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetWindowHandle(handle);
|
|
||||||
if (!InitWindowSurface()) {
|
|
||||||
MGLOG_E("CreateEGLWindowSurface failed: backend InitWindowSurface failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_eglSurfaceInitialized = true;
|
|
||||||
m_eglSurfaceKind = SurfaceKind::Window;
|
|
||||||
m_eglCurrentThreads.clear();
|
|
||||||
m_backendCapabilitiesInitialized = false;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool BackendObject::CreateEGLPbufferSurface(EGLint width, EGLint height) {
|
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("CreateEGLPbufferSurface failed: EGL display is not initialized");
|
MGLOG_E("RegisterEGLWindowSurface failed: EGL display is not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (surface == EGL_NO_SURFACE) {
|
||||||
|
MGLOG_E("RegisterEGLWindowSurface failed: invalid EGLSurface");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (handle.Backend == WindowBackend::Unknown || !handle.Handle) {
|
||||||
|
MGLOG_E("RegisterEGLWindowSurface failed: invalid native window handle");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (width <= 0 || height <= 0) {
|
if (width <= 0 || height <= 0) {
|
||||||
MGLOG_E("CreateEGLPbufferSurface failed: invalid size %dx%d", width, height);
|
MGLOG_E("RegisterEGLPbufferSurface failed: invalid size %dx%d", width, height);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_eglSurfaceInitialized && m_eglSurfaceKind == SurfaceKind::Pbuffer) {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!InitPbufferSurface(width, height)) {
|
if (surfaceState->Kind == SurfaceKind::Window) {
|
||||||
MGLOG_E("CreateEGLPbufferSurface failed: backend InitPbufferSurface failed");
|
SetWindowHandle(surfaceState->Window);
|
||||||
|
if (!InitWindowSurface()) {
|
||||||
|
MGLOG_E("ActivateEGLSurface failed: backend InitWindowSurface failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (surfaceState->Kind == SurfaceKind::Pbuffer) {
|
||||||
|
if (!InitPbufferSurface(surfaceState->Width, surfaceState->Height)) {
|
||||||
|
MGLOG_E("ActivateEGLSurface failed: backend InitPbufferSurface failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MGLOG_E("ActivateEGLSurface failed: unsupported surface kind");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_eglSurface = surface;
|
||||||
m_eglSurfaceInitialized = true;
|
m_eglSurfaceInitialized = true;
|
||||||
m_eglSurfaceKind = SurfaceKind::Pbuffer;
|
m_eglSurfaceKind = surfaceState->Kind;
|
||||||
m_eglCurrentThreads.clear();
|
m_eglCurrentThreads.clear();
|
||||||
m_backendCapabilitiesInitialized = false;
|
m_backendCapabilitiesInitialized = false;
|
||||||
return true;
|
return true;
|
||||||
@@ -102,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +316,21 @@ namespace MobileGL::MG_Backend {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!m_eglSurfaceInitialized) {
|
if (!m_eglSurfaceInitialized) {
|
||||||
MGLOG_E("MakeEGLCurrent failed: EGL 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) {
|
||||||
@@ -127,7 +346,13 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +361,8 @@ namespace MobileGL::MG_Backend {
|
|||||||
m_eglSurfaceInitialized = false;
|
m_eglSurfaceInitialized = false;
|
||||||
m_backendCapabilitiesInitialized = false;
|
m_backendCapabilitiesInitialized = false;
|
||||||
m_eglSurfaceKind = SurfaceKind::None;
|
m_eglSurfaceKind = SurfaceKind::None;
|
||||||
|
m_eglSurface = EGL_NO_SURFACE;
|
||||||
|
m_windowHandle = {};
|
||||||
m_eglCurrentThreads.clear();
|
m_eglCurrentThreads.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,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_eglSurfaceInitialized || 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;
|
||||||
}
|
}
|
||||||
@@ -164,12 +397,77 @@ 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() {
|
void BackendObject::ReleaseEGLResources() {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
ResetEGLRuntimeState();
|
ResetEGLRuntimeState();
|
||||||
|
m_eglSurfaces.clear();
|
||||||
m_eglDisplay = EGL_NO_DISPLAY;
|
m_eglDisplay = EGL_NO_DISPLAY;
|
||||||
m_eglDisplayInitialized = false;
|
m_eglDisplayInitialized = false;
|
||||||
m_windowHandle = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendObject::SetWindowHandle(const WindowHandle& handle) {
|
void BackendObject::SetWindowHandle(const WindowHandle& handle) {
|
||||||
@@ -177,9 +475,21 @@ namespace MobileGL::MG_Backend {
|
|||||||
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) {
|
Bool BackendObject::InitPbufferSurface(EGLint width, EGLint height) {
|
||||||
(void)width;
|
(void)width;
|
||||||
(void)height;
|
(void)height;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BackendObject::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||||
|
(void)surface;
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Backend
|
} // namespace MobileGL::MG_Backend
|
||||||
|
|||||||
@@ -24,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,
|
||||||
@@ -68,6 +145,10 @@ namespace MobileGL {
|
|||||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
|
void (*ClearNamedFramebufferiv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
|
void (*ClearNamedFramebufferuiv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
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,
|
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||||
@@ -79,6 +160,11 @@ namespace MobileGL {
|
|||||||
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);
|
||||||
@@ -104,25 +190,217 @@ namespace MobileGL {
|
|||||||
GLint (*GetProgramResourceLocation)(GLuint program, GLenum programInterface, const GLchar* name);
|
GLint (*GetProgramResourceLocation)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
GLint (*GetProgramResourceLocationIndex)(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 (*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);
|
||||||
|
// GL_SAMPLES_PASSED occlusion queries (optional; null = unsupported,
|
||||||
|
// the frontend then rejects the target). Results/deletion flow through
|
||||||
|
// GetQueryResult64 / DeleteBackendQuery like timer queries.
|
||||||
|
BackendQueryHandle (*BeginOcclusionQuery)();
|
||||||
|
void (*EndOcclusionQuery)(BackendQueryHandle query);
|
||||||
|
// Transform feedback primitive queries backed by real GPU query pools
|
||||||
|
// (optional; null = frontend falls back to CPU accounting).
|
||||||
|
BackendQueryHandle (*BeginXfbPrimitivesQuery)(Bool generated);
|
||||||
|
void (*EndXfbPrimitivesQuery)(BackendQueryHandle query);
|
||||||
|
// Transform feedback capture spans, for backends whose own GL/ES driver
|
||||||
|
// performs the capture (DirectGLES). Both optional; null means the backend
|
||||||
|
// drives capture from its draw recording instead (DirectVulkan). End is
|
||||||
|
// called while the frontend capture state is still active, so the backend
|
||||||
|
// can still see the capture program and buffer bindings.
|
||||||
|
// GL_PATCH_VERTICES; ES 3.2 spells it the same way.
|
||||||
|
void (*PatchParameteri)(GLenum pname, GLint value);
|
||||||
|
void (*BeginTransformFeedback)(GLenum primitiveMode);
|
||||||
|
void (*EndTransformFeedback)();
|
||||||
|
// ARB_transform_feedback2. A backend that leaves these null keeps the single
|
||||||
|
// implicit capture span the frontend has always modelled; the frontend state
|
||||||
|
// (paused flag, per-object bindings) is tracked either way.
|
||||||
|
void (*PauseTransformFeedback)();
|
||||||
|
void (*ResumeTransformFeedback)();
|
||||||
|
void (*BindTransformFeedback)(GLuint name);
|
||||||
|
void (*DeleteTransformFeedback)(GLuint name);
|
||||||
|
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;
|
||||||
|
// Tessellation limits; defaults are the GL 4.0 core minimums.
|
||||||
|
Int MaxPatchVertices = 32;
|
||||||
|
Int MaxTessGenLevel = 64;
|
||||||
|
// GL_MIN/MAX_PROGRAM_TEXTURE_GATHER_OFFSET. Defaults are the GL 4.0 core
|
||||||
|
// minimums, which every ES 3.1 driver also guarantees.
|
||||||
|
Int MinProgramTextureGatherOffset = -8;
|
||||||
|
Int MaxProgramTextureGatherOffset = 7;
|
||||||
|
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;
|
||||||
|
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.
|
||||||
|
Int TextureBufferOffsetAlignment = 1;
|
||||||
|
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;
|
||||||
|
// GL 4.x fragment-interpolation offset limits. These defaults are the
|
||||||
|
// core minimums and are replaced by live GLES/Vulkan device limits.
|
||||||
|
Float MinFragmentInterpolationOffset = -0.5f;
|
||||||
|
// For four fractional bits the greatest required legal offset is
|
||||||
|
// 0.5 - 2^-4 = 0.4375 (GL 4.6 table 23.70).
|
||||||
|
Float MaxFragmentInterpolationOffset = 0.4375f;
|
||||||
|
Int FragmentInterpolationOffsetBits = 4;
|
||||||
|
Bool SupportsWideLines = false;
|
||||||
|
// Whether a framebuffer whose depth and stencil attachments are distinct
|
||||||
|
// images can be rendered to. GL only requires support when both refer to the
|
||||||
|
// same image and lets an implementation answer GL_FRAMEBUFFER_UNSUPPORTED
|
||||||
|
// otherwise, which is what DirectVulkan (one combined attachment) and the
|
||||||
|
// real ES drivers behind DirectGLES both do. Defaults to true so a backend
|
||||||
|
// that never sets it keeps the permissive behaviour.
|
||||||
|
Bool SupportsDistinctDepthStencilAttachments = true;
|
||||||
|
// Whether attaching a single layer of a 3D or array texture to a framebuffer actually
|
||||||
|
// renders to that layer. DirectGLES hands the layer straight to
|
||||||
|
// glFramebufferTextureLayer, so it does; DirectVulkan maps a GL layer onto a Vulkan
|
||||||
|
// array layer with no notion of a 3D depth slice, so it does not yet. Defaults to false
|
||||||
|
// so a backend that never sets it gets the conservative answer.
|
||||||
|
// Which layered texture targets this backend can attach ONE layer of to a framebuffer
|
||||||
|
// and then really clear, render and read back that layer. Bit (1u << TextureTarget) is
|
||||||
|
// set for each supported target. Deliberately per target rather than one flag: the three
|
||||||
|
// ways a GL layer maps onto Vulkan are independent capabilities. A 2D or 2D multisample
|
||||||
|
// array layer IS a VkImage array layer and needs nothing extra; a 3D texture's layer is
|
||||||
|
// a z slice, which needs a 2D-array-compatible image and a per-slice clear that
|
||||||
|
// vkCmdClearColorImage cannot express; a cube map array needs an image shape and the
|
||||||
|
// imageCubeArray feature before it can be attached at any layer at all. Defaults to 0 so
|
||||||
|
// a backend that never sets it gets the conservative answer.
|
||||||
|
Uint32 PerLayerFramebufferAttachmentTargets = 0;
|
||||||
|
|
||||||
|
static constexpr Uint32 PerLayerFramebufferAttachmentBit(TextureTarget target) {
|
||||||
|
return (static_cast<Int>(target) >= 0 &&
|
||||||
|
static_cast<Int>(target) < static_cast<Int>(TextureTarget::TextureTargetCount))
|
||||||
|
? (1u << static_cast<Uint32>(target))
|
||||||
|
: 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool SupportsPerLayerFramebufferAttachment(TextureTarget target) const {
|
||||||
|
const Uint32 bit = PerLayerFramebufferAttachmentBit(target);
|
||||||
|
return bit != 0 && (PerLayerFramebufferAttachmentTargets & bit) != 0;
|
||||||
|
}
|
||||||
|
// Whether glVertexAttribLFormat / glVertexArrayAttribLFormat can be honoured, i.e.
|
||||||
|
// whether a 64-bit vertex attribute can actually reach a shader unconverted. Detected,
|
||||||
|
// never assumed: DirectVulkan needs VkPhysicalDeviceFeatures::shaderFloat64 (the
|
||||||
|
// attribute travels as its 32-bit word pair, so no VK_FORMAT_R64* is required, but the
|
||||||
|
// bitcast result is Float64); DirectGLES can never have it, ESSL having no fp64 type at
|
||||||
|
// all. Defaults to false so a backend that never sets it gets the conservative answer.
|
||||||
|
Bool SupportsFloat64VertexAttributes = false;
|
||||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||||
Uint32 SubgroupSize = 0;
|
Uint32 SubgroupSize = 0;
|
||||||
Uint32 SubgroupSupportedStages = 0;
|
Uint32 SubgroupSupportedStages = 0;
|
||||||
Uint32 SubgroupSupportedFeatures = 0;
|
Uint32 SubgroupSupportedFeatures = 0;
|
||||||
Bool SubgroupQuadOperationsInAllStages = false;
|
Bool SubgroupQuadOperationsInAllStages = false;
|
||||||
|
GpuVendorKind GpuVendor = GpuVendorKind::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class WindowBackend {
|
enum class WindowBackend {
|
||||||
Android,
|
Android,
|
||||||
X11,
|
X11,
|
||||||
// TODO: X11, Wayland, Windows, macOS, etc.
|
MetalLayer,
|
||||||
|
Win32, // Handle is an HWND
|
||||||
|
// TODO: Wayland, etc.
|
||||||
WindowBackendCount,
|
WindowBackendCount,
|
||||||
Unknown = -1
|
Unknown = -1
|
||||||
};
|
};
|
||||||
@@ -130,6 +408,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 {
|
||||||
@@ -141,10 +421,15 @@ 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 CreateEGLPbufferSurface(EGLint width, EGLint height);
|
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();
|
virtual void ReleaseEGLResources();
|
||||||
|
|
||||||
void SetWindowHandle(const WindowHandle& handle);
|
void SetWindowHandle(const WindowHandle& handle);
|
||||||
@@ -153,6 +438,7 @@ 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:
|
||||||
@@ -162,17 +448,46 @@ namespace MobileGL {
|
|||||||
Pbuffer
|
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 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_eglSurfaceInitialized = false;
|
Bool m_eglSurfaceInitialized = false;
|
||||||
Bool m_backendCapabilitiesInitialized = false;
|
Bool m_backendCapabilitiesInitialized = false;
|
||||||
SurfaceKind m_eglSurfaceKind = SurfaceKind::None;
|
SurfaceKind m_eglSurfaceKind = SurfaceKind::None;
|
||||||
UnorderedMap<std::thread::id, Bool> m_eglCurrentThreads;
|
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,11 @@ 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;
|
void ReleaseEGLResources() override;
|
||||||
|
|
||||||
const RendererInfo& GetRendererInfo() const override;
|
const RendererInfo& GetRendererInfo() const override;
|
||||||
@@ -33,10 +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;
|
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;
|
||||||
@@ -44,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,7 @@
|
|||||||
|
|
||||||
#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/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>
|
||||||
@@ -18,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);
|
||||||
@@ -26,6 +31,7 @@ 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,
|
||||||
@@ -53,6 +59,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
|
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
|
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
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,
|
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||||
@@ -64,6 +74,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
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);
|
||||||
@@ -88,12 +103,89 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||||
Bool InitWindowSurface(NativeWindowType window);
|
Bool InitWindowSurface(NativeWindowType window);
|
||||||
Bool InitPbufferSurface(EGLint width, EGLint height);
|
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();
|
||||||
|
// GL_ANY_SAMPLES_PASSED(_CONSERVATIVE) occlusion queries: core ES3, independent of
|
||||||
|
// GL_EXT_disjoint_timer_query and of MOBILEGL_DISABLE_TIMERQUERY. Results/deletion
|
||||||
|
// flow through GetQueryResult64/DeleteBackendQuery like the timer queries above.
|
||||||
|
BackendQueryHandle BeginOcclusionQuery();
|
||||||
|
void EndOcclusionQuery(BackendQueryHandle query);
|
||||||
|
// GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN / GL_PRIMITIVES_GENERATED, also core ES
|
||||||
|
// (GL_PRIMITIVES_GENERATED from ES 3.2 on). Null when the target is unavailable, in
|
||||||
|
// which case the frontend falls back to counting primitives from the draw calls.
|
||||||
|
BackendQueryHandle BeginXfbPrimitivesQuery(Bool generated);
|
||||||
|
void EndXfbPrimitivesQuery(BackendQueryHandle query);
|
||||||
|
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);
|
||||||
void DestroyEGLContext();
|
void DestroyEGLContext();
|
||||||
|
|
||||||
|
// Transform feedback capture spans, performed by the real ES driver. The
|
||||||
|
// capture set is declared on the backend program at link time; the driver-side
|
||||||
|
// begin is deferred to the first draw of the span (ES needs the capturing
|
||||||
|
// program current and the capture buffers bound), and the end also mirrors the
|
||||||
|
// captured bytes back into the frontend buffer shadows.
|
||||||
|
void PatchParameteri(GLenum pname, GLint value);
|
||||||
|
|
||||||
|
namespace XfbImpl {
|
||||||
|
Bool AreTransformFeedbacksSupported();
|
||||||
|
void BeginTransformFeedback(GLenum primitiveMode);
|
||||||
|
void EndTransformFeedback();
|
||||||
|
void PauseTransformFeedback();
|
||||||
|
void ResumeTransformFeedback();
|
||||||
|
void BindTransformFeedback(GLuint name);
|
||||||
|
void DeleteTransformFeedback(GLuint name);
|
||||||
|
void OnBackendContextDestroyed();
|
||||||
|
} // namespace XfbImpl
|
||||||
|
|
||||||
extern MG_External::EGLFunctionsTable g_EGLFuncs;
|
extern MG_External::EGLFunctionsTable g_EGLFuncs;
|
||||||
extern MG_External::GLESFunctionsTable g_GLESFuncs;
|
extern MG_External::GLESFunctionsTable g_GLESFuncs;
|
||||||
extern MG_External::GLESCapabilities g_GLESCapabilities;
|
extern MG_External::GLESCapabilities g_GLESCapabilities;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,14 +8,18 @@
|
|||||||
|
|
||||||
#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 EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||||
|
String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType);
|
||||||
|
|
||||||
template <typename StateObject, typename BackendObject>
|
template <typename StateObject, typename BackendObject>
|
||||||
class StateBackendObjectRegistry {
|
class StateBackendObjectRegistry {
|
||||||
@@ -52,6 +56,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return const_cast<StateBackendObjectRegistry*>(this)->find(stateObj);
|
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(); }
|
iterator end() { return m_backendObjects.end(); }
|
||||||
const_iterator end() const { return m_backendObjects.end(); }
|
const_iterator end() const { return m_backendObjects.end(); }
|
||||||
|
|
||||||
@@ -113,26 +119,129 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
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(const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
|
||||||
Uint GetBackendBufferId() const { return m_backendBufferId; }
|
|
||||||
void Bind(GLenum target = TempBufferTarget);
|
|
||||||
|
|
||||||
private:
|
Uint id = 0;
|
||||||
void SyncToBackend_glBufferData(const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
SizeT storageSize = 0;
|
||||||
void SyncToBackend_glBufferSubData(const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
|
Bool storageInitialized = false;
|
||||||
void SyncToBackend_glMapBufferRange(const 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 StateBackendObjectRegistry<MG_State::GLState::BufferObject, BackendBufferObject> g_backendBufferObjects;
|
// every MakeCurrent (the ES context can be destroyed and recreated, e.g.
|
||||||
|
// by the trace replayer's probe context).
|
||||||
|
void RegisterBufferBackendOps();
|
||||||
|
void UnregisterBufferBackendOps();
|
||||||
|
// The ES context died: unregister ops, invalidate all outstanding GL ids
|
||||||
|
// (they belonged to the dead context) and drop deferred deletes.
|
||||||
|
void OnBackendContextDestroyed();
|
||||||
|
|
||||||
|
// Get-or-create the backend resource and bring its storage up to date
|
||||||
|
// (creates the GL buffer, replays pending ops, pushes persistent-mapped
|
||||||
|
// ranges). Requires the ES context to be current. Returns nullptr only
|
||||||
|
// for null input.
|
||||||
|
GLESBufferResource* EnsureBufferResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||||
|
// Existing resource or nullptr; performs no GL calls.
|
||||||
|
GLESBufferResource* GetBufferResource(MG_State::GLState::BufferObject* bufferObject);
|
||||||
|
|
||||||
|
// Deletes GL buffers whose owning frontend objects died (possibly on a
|
||||||
|
// thread without a current ES context). Called from draw-time sync.
|
||||||
|
void ProcessDeferredBufferReleases();
|
||||||
|
|
||||||
|
// glBindBuffer with a redundant-bind cache for GL_ARRAY_BUFFER.
|
||||||
|
void BindBufferId(GLenum target, Uint id);
|
||||||
|
void InvalidateArrayBufferBindingCache();
|
||||||
|
// Redundant-bind 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 {
|
||||||
@@ -161,13 +270,59 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
namespace TextureImpl {
|
namespace TextureImpl {
|
||||||
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
||||||
if (target == TextureTarget::Texture1D || target == TextureTarget::TextureRectangle ||
|
// Every desktop-only target is stored on an ES one; see MapToBackendTextureTarget.
|
||||||
target == TextureTarget::Texture2DMultisampleArray || target == TextureTarget::Texture1DArray ||
|
(void)target;
|
||||||
target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DArray)
|
|
||||||
return false;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ES has none of the desktop-only targets: 1D textures are stored as 2D (height 1), 1D
|
||||||
|
// arrays as 2D arrays (height 1, layers in depth), and rectangle textures as plain 2D -
|
||||||
|
// they are single-level and already clamp, so only the non-normalized coordinates differ.
|
||||||
|
// Must match the shader-side emulation: SPIRV-Cross handles 1D/1D-array itself, and
|
||||||
|
// ShaderCompiler::LowerRectImages rewrites rectangle images (declining any module
|
||||||
|
// whose lookups are not integer-coordinate, which SPIRV-Cross then still rejects).
|
||||||
|
inline TextureTarget MapToBackendTextureTarget(TextureTarget target) {
|
||||||
|
switch (target) {
|
||||||
|
case TextureTarget::Texture1D:
|
||||||
|
case TextureTarget::TextureRectangle:
|
||||||
|
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:
|
||||||
|
case TextureUploadTarget::TextureRectangle:
|
||||||
|
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) {
|
inline Bool SupportsWrapR(TextureTarget target) {
|
||||||
return target == TextureTarget::Texture3D || target == TextureTarget::TextureCubeMap;
|
return target == TextureTarget::Texture3D || target == TextureTarget::TextureCubeMap;
|
||||||
}
|
}
|
||||||
@@ -179,11 +334,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); }
|
||||||
@@ -193,15 +351,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
class BackendTextureObject {
|
class BackendTextureObject {
|
||||||
public:
|
public:
|
||||||
BackendTextureObject();
|
BackendTextureObject();
|
||||||
|
// Deletes the GL texture (frontend glDeleteTextures used to leak every
|
||||||
|
// backend id for the context lifetime) and scrubs the binding/scratch-FBO
|
||||||
|
// 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 SyncMipmapsToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||||
void SyncBuiltinSamplerToBackend(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 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() const;
|
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};
|
||||||
@@ -216,10 +388,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void UnbindTexture(Uint unit, GLenum target);
|
void UnbindTexture(Uint unit, GLenum target);
|
||||||
extern StateBackendObjectRegistry<MG_State::GLState::ITextureObject, 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 {
|
||||||
@@ -228,6 +406,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
BackendFramebufferObject();
|
BackendFramebufferObject();
|
||||||
void SyncToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
void SyncToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
||||||
FramebufferTarget asTarget);
|
FramebufferTarget asTarget);
|
||||||
|
// Apply only this FBO's read buffer (glReadBuffer) to the backend. Split out so it can
|
||||||
|
// still run when SyncCurrentFBO skips the READ-target sync because the same GL FBO is
|
||||||
|
// bound as both draw and read (otherwise glReadBuffer changes would be silently dropped).
|
||||||
|
void SyncReadBufferToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject);
|
||||||
void InvalidateSyncedState();
|
void InvalidateSyncedState();
|
||||||
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
|
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
|
||||||
void Bind(FramebufferTarget target) const;
|
void Bind(FramebufferTarget target) const;
|
||||||
@@ -251,6 +433,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
this array could be provided as data directly to ES `glDrawBuffers` function
|
this array could be provided as data directly to ES `glDrawBuffers` function
|
||||||
*/
|
*/
|
||||||
GLenum m_backendDrawBuffers[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS] = {GL_NONE};
|
GLenum m_backendDrawBuffers[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS] = {GL_NONE};
|
||||||
|
|
||||||
|
static constexpr Uint MAX_COLOR_ATTACHMENT_SLOTS =
|
||||||
|
static_cast<Uint>(FramebufferAttachmentType::Color31) -
|
||||||
|
static_cast<Uint>(FramebufferAttachmentType::Color0) + 1;
|
||||||
|
/* Where each frontend GL_COLOR_ATTACHMENTn image physically lives in the backend ES
|
||||||
|
framebuffer, as a GL_COLOR_ATTACHMENTm enum. ES only accepts glDrawBuffers bufs[s] ==
|
||||||
|
GL_COLOR_ATTACHMENTs, so a GL draw-buffer slot s naming attachment a forces a's image
|
||||||
|
under backend slot s. This table is the single owner of that decision and is kept a
|
||||||
|
PERMUTATION of the backend colour slots: every other attachment keeps its identity
|
||||||
|
slot when that slot survived, and is parked on the lowest free slot when it did not.
|
||||||
|
Deriving the point per-query from the draw-buffer array instead handed the identity
|
||||||
|
point to any attachment that was not a draw buffer - i.e. exactly the point a
|
||||||
|
relocated draw buffer had just taken over. The permutation is only true of the
|
||||||
|
PHYSICAL framebuffer because the attachment loop detaches a point whose frontend
|
||||||
|
owner is empty; do not remove that detach. */
|
||||||
|
GLenum m_backendColorSlots[MAX_COLOR_ATTACHMENT_SLOTS] = {GL_NONE};
|
||||||
|
/* Rebuild m_backendColorSlots from the frontend draw-buffer array. Returns true when any
|
||||||
|
attachment moved, i.e. when the physical attachments and the memoised read buffer have
|
||||||
|
to be re-applied. */
|
||||||
|
Bool RecomputeBackendColorSlots(
|
||||||
|
const MG_State::GLState::FramebufferObject::FramebufferAttachmentArray& stateDrawBuffers);
|
||||||
|
|
||||||
FramebufferAttachmentType m_frontendReadBuffer = FramebufferAttachmentType::Color0;
|
FramebufferAttachmentType m_frontendReadBuffer = FramebufferAttachmentType::Color0;
|
||||||
GLenum m_backendReadBuffer = GL_COLOR_ATTACHMENT0;
|
GLenum m_backendReadBuffer = GL_COLOR_ATTACHMENT0;
|
||||||
|
|
||||||
@@ -260,27 +464,227 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
||||||
g_backendFramebufferObjects;
|
g_backendFramebufferObjects;
|
||||||
|
// True when the read buffer names a fixed-point (norm/snorm) attachment that the
|
||||||
|
// backend actually stores in a floating-point format. GL clamps a read from a
|
||||||
|
// fixed-point colour buffer to [0,1] (GL_CLAMP_READ_COLOR defaults to
|
||||||
|
// GL_FIXED_ONLY); the substituted float storage would not, so the readback path
|
||||||
|
// has to apply the clamp itself.
|
||||||
|
Bool IsFixedPointFallbackReadAttachment();
|
||||||
|
|
||||||
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;
|
||||||
|
// Location of this sampler's emulated GL_TEXTURE_LOD_BIAS uniform
|
||||||
|
// (PrgramImpl::EmulateTextureLodBias), -1 when the shader has none.
|
||||||
|
// lastAssignedLodBias mirrors the value the program currently holds,
|
||||||
|
// so an unbiased shader issues no per-draw glUniform1f at all.
|
||||||
|
Int lodBiasLocation = -1;
|
||||||
|
Float lastAssignedLodBias = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
BackendProgramObjectImpl();
|
BackendProgramObjectImpl();
|
||||||
~BackendProgramObjectImpl();
|
~BackendProgramObjectImpl();
|
||||||
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||||
void Use() const;
|
void Use() const;
|
||||||
void SetBaseInstance(Uint32 baseInstance) 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; }
|
||||||
|
// False when the last SyncToBackend could not produce a usable program (a
|
||||||
|
// shader failed to transpile or compile, or the link itself failed). Use()
|
||||||
|
// must not leave the previously bound program current in that case.
|
||||||
|
Bool IsBackendProgramUsable() const { return m_backendProgramUsable; }
|
||||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||||
|
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||||
|
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||||
|
Uint GetFragColorBroadcastCount() const { return m_fragColorBroadcastCount; }
|
||||||
|
|
||||||
|
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_baseInstanceUniformLocation = -1;
|
||||||
|
Int m_drawIdUniformLocation = -1;
|
||||||
|
Int m_baseInstanceWordIndexUniformLocation = -1;
|
||||||
|
Int m_indirectParamsBinding = -1;
|
||||||
|
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||||
|
Uint32 m_unormFallbackClampOutputMask = 0;
|
||||||
|
// Draw buffers a legacy gl_FragColor write has to reach (see
|
||||||
|
// PrgramImpl::BroadcastLegacyFragColor); 1 keeps the plain single-output shader.
|
||||||
|
Uint m_fragColorBroadcastCount = 1;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
|
Bool m_backendProgramUsable = 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 Uint32 g_snormFallbackClampOutputMask;
|
||||||
|
extern Uint32 g_unormFallbackClampOutputMask;
|
||||||
|
// Draw buffers the current draw framebuffer enables. Like the clamp masks above it
|
||||||
|
// is framebuffer state that the shader has to be compiled against, so a program
|
||||||
|
// whose snapshot no longer matches is relinked.
|
||||||
|
extern Uint g_fragColorBroadcastCount;
|
||||||
|
// 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>
|
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
||||||
g_backendProgramObjects;
|
g_backendProgramObjects;
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
@@ -322,6 +726,7 @@ 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 StateBackendObjectRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject>
|
extern StateBackendObjectRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject>
|
||||||
|
|||||||
@@ -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,19 +18,180 @@
|
|||||||
#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>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstring>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
namespace MobileGL::MG_Backend::DirectGLES {
|
||||||
|
namespace {
|
||||||
|
Flags<PixelFormatNormalizeOptionBit> GetForcedPixelFormatNormalizeOptions() {
|
||||||
|
Flags<PixelFormatNormalizeOptionBit> options;
|
||||||
|
if (g_GLESCapabilities.IsAngleRenderer) {
|
||||||
|
options |= PixelFormatNormalizeOptionBit::NoRgb16;
|
||||||
|
options |= PixelFormatNormalizeOptionBit::NoSnorm16;
|
||||||
|
options |= PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
Flags<PixelFormatNormalizeOptionBit> extraOptions) {
|
||||||
|
using namespace MG_Util::TextureFormatProcessor;
|
||||||
|
const Flags<PixelFormatNormalizeOptionBit> forcedOptions = GetApplicablePixelFormatNormalizeOptions(
|
||||||
|
requestedInternalFormat, GetForcedPixelFormatNormalizeOptions() | extraOptions);
|
||||||
|
if (forcedOptions) {
|
||||||
|
return forcedOptions;
|
||||||
|
}
|
||||||
|
return GetApplicablePixelFormatNormalizeOptions(
|
||||||
|
requestedInternalFormat, GetDriverPixelFormatNormalizeOptions() | extraOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multisample textures can only ever be rendered into, never uploaded to, so a fallback
|
||||||
|
// format for them has to stay colour-renderable - a three-channel float fallback is a legal
|
||||||
|
// ES texture format but not a legal multisample storage format. Widening to four channels
|
||||||
|
// is safe here precisely because there is no transfer path that would have to expand
|
||||||
|
// three-channel client data, and the alpha the draw writes for a three-channel source is
|
||||||
|
// already the 1.0 the frontend format implies.
|
||||||
|
Bool TargetRequiresRenderableFormat(SizeT targetIndex) {
|
||||||
|
return targetIndex == static_cast<SizeT>(TextureTarget::Texture2DMultisample) ||
|
||||||
|
targetIndex == static_cast<SizeT>(TextureTarget::Texture2DMultisampleArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
Flags<PixelFormatNormalizeOptionBit> GetRenderTargetNormalizeOptions(SizeT targetIndex) {
|
||||||
|
Flags<PixelFormatNormalizeOptionBit> options;
|
||||||
|
if (!TargetRequiresRenderableFormat(targetIndex)) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
options |= PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||||
|
if (!g_GLESCapabilities.SupportsRenderSnorm || !g_GLESCapabilities.SupportsNorm16Texture) {
|
||||||
|
options |= PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
GetRenderTargetNormalizeOptions(targetIndex));
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BackendTextureFormatAddsAlpha(TextureInternalFormat internalFormat, TextureTarget target) {
|
||||||
|
const SizeT targetIndex =
|
||||||
|
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||||
|
if (!TargetRequiresRenderableFormat(targetIndex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pActiveBackendObject && !ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
||||||
|
const Flags<PixelFormatNormalizeOptionBit> options =
|
||||||
|
GetRuntimeFallbackNormalizeOptions(requestedInternalFormat,
|
||||||
|
GetRenderTargetNormalizeOptions(targetIndex));
|
||||||
|
return static_cast<Bool>(options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget);
|
||||||
}
|
}
|
||||||
} // namespace TextureImpl
|
} // namespace TextureImpl
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
@@ -101,6 +263,113 @@ 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 BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
// The name is the marker: ShaderSourceProcessor only emits it when the source
|
||||||
|
// wrote gl_FragColor, and such a shader can have no other output.
|
||||||
|
static const char* const kLoweredName = "mg_FragColor";
|
||||||
|
if (shaderType != GL_FRAGMENT_SHADER || drawBufferCount <= 1) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
static const std::regex declRegex(
|
||||||
|
R"(layout\s*\(\s*location\s*=\s*0\s*\)\s*out\s+((?:lowp|mediump|highp)\s+)?vec4\s+mg_FragColor\s*;)");
|
||||||
|
std::smatch declMatch;
|
||||||
|
if (!std::regex_search(glslCode, declMatch, declRegex)) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
const String precision = declMatch[1].matched ? declMatch[1].str() : String();
|
||||||
|
|
||||||
|
String replicaDecls;
|
||||||
|
String replicaCopies;
|
||||||
|
for (Uint location = 1; location < drawBufferCount; ++location) {
|
||||||
|
const String name = String(kLoweredName) + "_" + std::to_string(location);
|
||||||
|
replicaDecls += "\nlayout(location = " + std::to_string(location) + ") out " + precision + "vec4 " +
|
||||||
|
name + ";";
|
||||||
|
replicaCopies += "\n " + name + " = " + kLoweredName + ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::regex mainRegex(R"(void\s+main\s*\([^)]*\)\s*\{)");
|
||||||
|
std::smatch mainMatch;
|
||||||
|
if (!std::regex_search(glslCode, mainMatch, mainRegex)) {
|
||||||
|
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) {
|
||||||
|
glslCode.insert(pos, replicaCopies + "\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glslCode.insert(static_cast<SizeT>(declMatch.position(0)) + declMatch[0].str().size(), replicaDecls);
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
@@ -136,12 +405,198 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
#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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// How a lookup carries its level of detail, and how many arguments it takes
|
||||||
|
// before the optional bias.
|
||||||
|
struct LodLookupForm {
|
||||||
|
const char* name;
|
||||||
|
Int requiredArgs; // arguments before the optional bias (implicit form)
|
||||||
|
Int explicitLodArg; // index of the explicit LOD argument, -1 for implicit
|
||||||
|
};
|
||||||
|
|
||||||
|
// texelFetch* is deliberately absent: an integer fetch names its level directly
|
||||||
|
// and takes no LOD bias. textureGather has no bias either. textureGrad* derives
|
||||||
|
// the LOD from gradients and offers no argument to fold a bias into, so it is
|
||||||
|
// left alone rather than rewritten incorrectly.
|
||||||
|
constexpr LodLookupForm LOD_LOOKUP_FORMS[] = {
|
||||||
|
{"textureProjLodOffset", 0, 2}, {"textureProjOffset", 4, -1}, {"textureProjLod", 0, 2},
|
||||||
|
{"textureLodOffset", 0, 2}, {"textureOffset", 3, -1}, {"textureProj", 2, -1},
|
||||||
|
{"textureLod", 0, 2}, {"texture", 2, -1},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sampler types with no mip chain, or whose GLSL lookups have no bias overload
|
||||||
|
// at all (the array-shadow forms), so nothing can or should be folded in.
|
||||||
|
Bool IsBiasableSamplerType(const String& samplerType) {
|
||||||
|
if (samplerType.find("MS") != String::npos) return false; // multisample
|
||||||
|
if (samplerType.find("Buffer") != String::npos) return false; // texture buffer
|
||||||
|
if (samplerType.find("Rect") != String::npos) return false; // rectangle: no mips
|
||||||
|
if (samplerType == "sampler2DArrayShadow") return false;
|
||||||
|
if (samplerType == "samplerCubeArrayShadow") return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool IsIdentifierChar(char c) { return std::isalnum(static_cast<unsigned char>(c)) || c == '_'; }
|
||||||
|
|
||||||
|
// Byte offsets of the top-level argument separators and of the closing paren,
|
||||||
|
// starting from the '(' at openParen. Empty when the parentheses do not balance.
|
||||||
|
Vector<SizeT> SplitCallArguments(const String& code, SizeT openParen) {
|
||||||
|
Vector<SizeT> marks;
|
||||||
|
Int depth = 0;
|
||||||
|
for (SizeT i = openParen; i < code.size(); ++i) {
|
||||||
|
const char c = code[i];
|
||||||
|
if (c == '(' || c == '[') {
|
||||||
|
++depth;
|
||||||
|
} else if (c == ']') {
|
||||||
|
--depth;
|
||||||
|
} else if (c == ')') {
|
||||||
|
--depth;
|
||||||
|
if (depth == 0) {
|
||||||
|
marks.push_back(i);
|
||||||
|
return marks;
|
||||||
|
}
|
||||||
|
} else if (c == ',' && depth == 1) {
|
||||||
|
marks.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
String EmulateTextureLodBias(const String& glslCode) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
if (glslCode.find("sampler") == String::npos || glslCode.find("texture") == String::npos) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the mip-capable sampler uniforms this shader declares.
|
||||||
|
static const std::regex samplerDeclRegex(
|
||||||
|
R"(uniform\s+(?:(?:highp|mediump|lowp)\s+)?([iu]?sampler[A-Za-z0-9]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
|
||||||
|
UnorderedMap<String, String> samplerNames; // name -> bias uniform name
|
||||||
|
for (std::sregex_iterator it(glslCode.begin(), glslCode.end(), samplerDeclRegex), end; it != end; ++it) {
|
||||||
|
const String samplerType = (*it)[1].str();
|
||||||
|
if (!IsBiasableSamplerType(samplerType)) continue;
|
||||||
|
const String name = (*it)[2].str();
|
||||||
|
samplerNames.emplace(name, String(LOD_BIAS_UNIFORM_PREFIX) + name);
|
||||||
|
}
|
||||||
|
if (samplerNames.empty()) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite the lookups. Right-to-left so earlier offsets stay valid, and only for
|
||||||
|
// samplers named directly as the first argument (SPIRV-Cross never produces an
|
||||||
|
// expression there for ES output, which has no separate sampler objects).
|
||||||
|
String result = glslCode;
|
||||||
|
Vector<String> usedSamplers;
|
||||||
|
for (SizeT scan = result.size(); scan-- > 0;) {
|
||||||
|
if (result[scan] != 't') continue;
|
||||||
|
if (scan > 0 && IsIdentifierChar(result[scan - 1])) continue;
|
||||||
|
|
||||||
|
const LodLookupForm* form = nullptr;
|
||||||
|
SizeT openParen = 0;
|
||||||
|
for (const auto& candidate : LOD_LOOKUP_FORMS) {
|
||||||
|
const SizeT nameLength = std::strlen(candidate.name);
|
||||||
|
if (result.compare(scan, nameLength, candidate.name) != 0) continue;
|
||||||
|
SizeT after = result.find_first_not_of(" \t", scan + nameLength);
|
||||||
|
if (after == String::npos || result[after] != '(') continue;
|
||||||
|
form = &candidate;
|
||||||
|
openParen = after;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (form == nullptr) continue;
|
||||||
|
|
||||||
|
const Vector<SizeT> marks = SplitCallArguments(result, openParen);
|
||||||
|
if (marks.empty()) continue;
|
||||||
|
const SizeT argCount = marks.size();
|
||||||
|
const SizeT closeParen = marks.back();
|
||||||
|
|
||||||
|
// First argument must be one of our samplers.
|
||||||
|
const SizeT firstArgStart = result.find_first_not_of(" \t", openParen + 1);
|
||||||
|
SizeT firstArgEnd = marks.front();
|
||||||
|
while (firstArgEnd > firstArgStart && (result[firstArgEnd - 1] == ' ' || result[firstArgEnd - 1] == '\t')) {
|
||||||
|
--firstArgEnd;
|
||||||
|
}
|
||||||
|
if (firstArgStart == String::npos || firstArgEnd <= firstArgStart) continue;
|
||||||
|
const String samplerName = result.substr(firstArgStart, firstArgEnd - firstArgStart);
|
||||||
|
const auto samplerIt = samplerNames.find(samplerName);
|
||||||
|
if (samplerIt == samplerNames.end()) continue;
|
||||||
|
|
||||||
|
const String& biasName = samplerIt->second;
|
||||||
|
if (form->explicitLodArg >= 0) {
|
||||||
|
// Explicit LOD: the bias adds to it, as Vulkan does for
|
||||||
|
// OpImageSampleExplicitLod and as the CTS reference expects.
|
||||||
|
const SizeT lodIndex = static_cast<SizeT>(form->explicitLodArg);
|
||||||
|
if (argCount <= lodIndex) continue;
|
||||||
|
const SizeT lodStart = marks[lodIndex - 1] + 1;
|
||||||
|
const SizeT lodEnd = marks[lodIndex];
|
||||||
|
result.insert(lodEnd, String(") + ") + biasName + ")");
|
||||||
|
result.insert(lodStart, "((");
|
||||||
|
} else {
|
||||||
|
const SizeT required = static_cast<SizeT>(form->requiredArgs);
|
||||||
|
if (argCount == required) {
|
||||||
|
result.insert(closeParen, String(", ") + biasName);
|
||||||
|
} else if (argCount == required + 1) {
|
||||||
|
const SizeT biasStart = marks[argCount - 2] + 1;
|
||||||
|
result.insert(closeParen, String(") + ") + biasName + ")");
|
||||||
|
result.insert(biasStart, "((");
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usedSamplers.push_back(samplerName);
|
||||||
|
}
|
||||||
|
if (usedSamplers.empty()) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare the bias uniforms that were actually referenced, right after the
|
||||||
|
// sampler declaration line they belong to.
|
||||||
|
for (const auto& samplerName : usedSamplers) {
|
||||||
|
const String& biasName = samplerNames[samplerName];
|
||||||
|
if (result.find(String("float ") + biasName + ";") != String::npos) continue;
|
||||||
|
const std::regex declRegex(
|
||||||
|
R"(uniform\s+(?:(?:highp|mediump|lowp)\s+)?[iu]?sampler[A-Za-z0-9]*\s+)" + samplerName + R"(\s*;)");
|
||||||
|
std::smatch match;
|
||||||
|
if (!std::regex_search(result, match, declRegex)) continue;
|
||||||
|
const SizeT declEnd = static_cast<SizeT>(match.position(0)) + match[0].str().size();
|
||||||
|
result.insert(declEnd, String("\nuniform highp float ") + biasName + ";");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
@@ -149,7 +604,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,4 +707,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
|
||||||
|
|||||||
@@ -35,16 +35,100 @@ 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);
|
||||||
|
|
||||||
|
// True when the format the texture is actually created with has an alpha channel the
|
||||||
|
// frontend format does not (the three-channel multisample widening). GL reads such a
|
||||||
|
// channel back as 1.0, so any swizzle source of ALPHA has to be answered with ONE.
|
||||||
|
Bool BackendTextureFormatAddsAlpha(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 ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType);
|
||||||
|
// Legacy GLSL's gl_FragColor is broadcast to every enabled draw buffer (GL 4.6
|
||||||
|
// 15.2.3), but ShaderSourceProcessor lowers it to the single output mg_FragColor,
|
||||||
|
// which only ever reaches draw buffer 0. Replicates it across `drawBufferCount`
|
||||||
|
// outputs and copies the value into them at the end of main. A no-op for
|
||||||
|
// drawBufferCount <= 1, i.e. for everything but a framebuffer that actually
|
||||||
|
// enables several draw buffers, so the ordinary single-target shader is untouched.
|
||||||
|
String BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount);
|
||||||
String RemoveLayoutBinding(const String& glslCode);
|
String RemoveLayoutBinding(const String& glslCode);
|
||||||
|
// Prefix of the per-sampler float uniform that carries GL_TEXTURE_LOD_BIAS into
|
||||||
|
// the shader (see EmulateTextureLodBias); the suffix is the sampler's own name.
|
||||||
|
constexpr const char* LOD_BIAS_UNIFORM_PREFIX = "mg_lodBias_";
|
||||||
|
// ES has no per-texture/sampler LOD bias at all (GL_TEXTURE_LOD_BIAS is desktop
|
||||||
|
// only; Vulkan spells it VkSamplerCreateInfo::mipLodBias), so it has to reach the
|
||||||
|
// shader as a uniform and be folded into every lookup's level of detail. Declares
|
||||||
|
// one `uniform highp float mg_lodBias_<sampler>;` per mip-capable sampler and adds
|
||||||
|
// it to the bias / explicit-LOD argument of every lookup that takes one. Draws push
|
||||||
|
// the bound texture's (or sampler object's) value into it; a shader whose samplers
|
||||||
|
// all have a zero bias is therefore unaffected. Returns the source unchanged when
|
||||||
|
// there is nothing to rewrite.
|
||||||
|
String EmulateTextureLodBias(const String& glslCode);
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|||||||
@@ -9,36 +9,326 @@
|
|||||||
#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 <cmath>
|
||||||
|
#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():
|
BackendObject_DirectVulkan::BackendObject_DirectVulkan() : m_rendererInfo{GetRendererIdentity()} {}
|
||||||
m_rendererInfo{
|
|
||||||
.RendererName = "Magma",
|
|
||||||
.BackendName = "Direct (Vulkan)",
|
|
||||||
.ExtraVendor = Nullopt,
|
|
||||||
.RendererGLInfo =
|
|
||||||
{
|
|
||||||
.TargetGLVersion = {3, 3, 0},
|
|
||||||
.TargetGLSLVersion = {4, 6, 0},
|
|
||||||
.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_direct_state_access,
|
|
||||||
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64},
|
|
||||||
.IsCompatibilityProfile = false
|
|
||||||
},
|
|
||||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}} {}
|
|
||||||
|
|
||||||
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
||||||
if (!m_windowHandle.Handle) {
|
if (!m_windowHandle.Handle) {
|
||||||
@@ -48,12 +338,28 @@ 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");
|
MOBILEGL_ASSERT(pVulkanRenderer != nullptr, "InitWindowSurface: VulkanRenderer creation failed");
|
||||||
pVulkanRenderer->Initialize();
|
pVulkanRenderer->Initialize();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool BackendObject_DirectVulkan::InitPbufferSurface(EGLint width, EGLint height) {
|
||||||
|
VulkanRendererConfig config;
|
||||||
|
config.SurfaceWidth = static_cast<Uint32>(std::max<EGLint>(width, 1));
|
||||||
|
config.SurfaceHeight = static_cast<Uint32>(std::max<EGLint>(height, 1));
|
||||||
|
// Any renderer instance this assignment replaces is destroyed here;
|
||||||
|
// fence/timer-query handles stamped with the old generation go stale.
|
||||||
|
BumpRendererGeneration();
|
||||||
|
pVulkanRenderer = MakeUnique<MG_Backend::DirectVulkan::VulkanRenderer>(NativeWindowType{}, config);
|
||||||
|
MOBILEGL_ASSERT(pVulkanRenderer != nullptr, "InitPbufferSurface: VulkanRenderer creation failed");
|
||||||
|
pVulkanRenderer->Initialize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void BackendObject_DirectVulkan::Initialize() {
|
void BackendObject_DirectVulkan::Initialize() {
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
}
|
}
|
||||||
@@ -76,6 +382,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
UpdateDynamicBackendParameters();
|
UpdateDynamicBackendParameters();
|
||||||
UpdateAdvertisedExtensions();
|
UpdateAdvertisedExtensions();
|
||||||
|
PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
|
||||||
|
MutableFormatCapabilities());
|
||||||
|
PrintFormatCapabilities(GetFormatCapabilities());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,40 +396,47 @@ 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.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11)) {
|
if (!handle.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 &&
|
||||||
MGLOG_E("DirectVulkan backend only supports Android and X11 native windows");
|
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 = m_eglSurfaceInitialized && m_eglSurfaceKind == SurfaceKind::Window &&
|
return RegisterEGLWindowSurface(surface, handle);
|
||||||
m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle;
|
}
|
||||||
if (sameHandle) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_eglSurfaceInitialized || 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +449,34 @@ 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() {
|
void BackendObject_DirectVulkan::ReleaseEGLResources() {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
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();
|
pVulkanRenderer.reset();
|
||||||
|
// The reflection cache is file-scope, not renderer-owned; without this the
|
||||||
|
// deleted programs' reflection strings survive full context teardown.
|
||||||
|
ClearProgramResourceCaches();
|
||||||
BackendObject::ReleaseEGLResources();
|
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();
|
||||||
|
// The reflection cache is file-scope, not renderer-owned; without this the
|
||||||
|
// deleted programs' reflection strings survive full context teardown.
|
||||||
|
ClearProgramResourceCaches();
|
||||||
|
}
|
||||||
|
|
||||||
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
||||||
return m_rendererInfo;
|
return m_rendererInfo;
|
||||||
}
|
}
|
||||||
@@ -147,11 +485,65 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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 = {4, 0, 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, V_OpenGL40, 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_texture_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,
|
||||||
|
E_GL_ARB_explicit_attrib_location,
|
||||||
|
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
|
||||||
|
// extension explicitly permits. It is also the only thing that
|
||||||
|
// exposes glProgramParameteri before GL 4.1.
|
||||||
|
E_GL_ARB_get_program_binary};
|
||||||
|
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 {
|
||||||
@@ -166,6 +558,7 @@ 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;
|
||||||
@@ -189,10 +582,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||||
|
funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv;
|
||||||
|
funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv;
|
||||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
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;
|
||||||
@@ -212,6 +608,34 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.GetProgramResourceLocation = GetProgramResourceLocation;
|
funcsTable.GL.GetProgramResourceLocation = GetProgramResourceLocation;
|
||||||
funcsTable.GL.GetProgramResourceLocationIndex = GetProgramResourceLocationIndex;
|
funcsTable.GL.GetProgramResourceLocationIndex = GetProgramResourceLocationIndex;
|
||||||
funcsTable.GL.ShaderStorageBlockBinding = ShaderStorageBlockBinding;
|
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;
|
||||||
|
}
|
||||||
|
// Occlusion queries share the handle-based result/delete entries, which must
|
||||||
|
// exist even when timer queries are disabled.
|
||||||
|
funcsTable.GL.BeginOcclusionQuery = BeginOcclusionQuery;
|
||||||
|
funcsTable.GL.EndOcclusionQuery = EndOcclusionQuery;
|
||||||
|
funcsTable.GL.BeginXfbPrimitivesQuery = BeginXfbPrimitivesQuery;
|
||||||
|
funcsTable.GL.EndXfbPrimitivesQuery = EndXfbPrimitivesQuery;
|
||||||
|
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||||
|
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
||||||
|
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||||
funcsTableInitialized = true;
|
funcsTableInitialized = true;
|
||||||
}
|
}
|
||||||
return funcsTable;
|
return funcsTable;
|
||||||
@@ -226,16 +650,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_vulkanCaps = capabilities;
|
m_vulkanCaps = capabilities;
|
||||||
UpdateDynamicBackendParameters();
|
UpdateDynamicBackendParameters();
|
||||||
UpdateAdvertisedExtensions();
|
UpdateAdvertisedExtensions();
|
||||||
|
MutableFormatCapabilities().Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendObject_DirectVulkan::UpdateAdvertisedExtensions() {
|
void BackendObject_DirectVulkan::UpdateAdvertisedExtensions() {
|
||||||
auto& extensions = m_rendererInfo.RendererGLInfo.Extensions;
|
// GL_ARB_timer_query gates MC's F3 GPU% (LWJGL checks the extension
|
||||||
extensions.erase(std::remove(extensions.begin(), extensions.end(), E_GL_KHR_shader_subgroup),
|
// string). InitCapabilities runs after InitWindowSurface has created
|
||||||
extensions.end());
|
// and initialized the renderer, so the advertisement can be gated on
|
||||||
|
// real device timestamp support. ApplyVulkanCapabilitiesForTesting may
|
||||||
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
// run without a renderer; no timer query is advertised then. Rebuilding
|
||||||
extensions.push_back(E_GL_KHR_shader_subgroup);
|
// 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() {
|
||||||
@@ -283,12 +710,137 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
static constexpr SizeT kMaxAdvertisedShaderStorageBlockSize = 512ull * 1024ull * 1024ull;
|
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.TextureBufferOffsetAlignment = m_vulkanCaps.TextureBufferOffsetAlignment;
|
||||||
|
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.MinFragmentInterpolationOffset =
|
||||||
|
std::isfinite(m_vulkanCaps.MinFragmentInterpolationOffset) &&
|
||||||
|
m_vulkanCaps.MinFragmentInterpolationOffset <= -0.5f
|
||||||
|
? m_vulkanCaps.MinFragmentInterpolationOffset
|
||||||
|
: -0.5f;
|
||||||
|
m_dynamicParameters.MaxFragmentInterpolationOffset = 0.4375f;
|
||||||
|
m_dynamicParameters.FragmentInterpolationOffsetBits = 4;
|
||||||
|
if (m_vulkanCaps.FragmentInterpolationOffsetBits >= 4 &&
|
||||||
|
std::isfinite(m_vulkanCaps.MaxFragmentInterpolationOffset)) {
|
||||||
|
const Float requiredMaxOffset = 0.5f - std::ldexp(1.0f, -m_vulkanCaps.FragmentInterpolationOffsetBits);
|
||||||
|
if (m_vulkanCaps.MaxFragmentInterpolationOffset >= requiredMaxOffset) {
|
||||||
|
m_dynamicParameters.MaxFragmentInterpolationOffset = m_vulkanCaps.MaxFragmentInterpolationOffset;
|
||||||
|
m_dynamicParameters.FragmentInterpolationOffsetBits = m_vulkanCaps.FragmentInterpolationOffsetBits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
||||||
|
// A 2D or 2D multisample array texture is a VK_IMAGE_TYPE_2D image whose GL depth IS its
|
||||||
|
// arrayLayers, so a GL layer is a Vulkan array layer with nothing to translate.
|
||||||
|
// ResolveAttachmentBaseArrayLayer already passes the attachment's layer through. The other
|
||||||
|
// layered targets are declared separately as their own machinery lands.
|
||||||
|
{
|
||||||
|
using DynParams = MG_Backend::DynamicBackendParameters;
|
||||||
|
m_dynamicParameters.PerLayerFramebufferAttachmentTargets |=
|
||||||
|
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture2DArray) |
|
||||||
|
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture2DMultisampleArray);
|
||||||
|
// A cube map array is one 2D image with arrayLayers = 6 * cubeCount, so a GL layer is a
|
||||||
|
// Vulkan array layer here too - but the image cannot be created without imageCubeArray.
|
||||||
|
// A 3D texture's GL layer is a z slice, which only a 2D view over a 2D-array-compatible
|
||||||
|
// image can name. Optimistic: a format that refuses the flag is caught at image creation
|
||||||
|
// and declines the slice view there, which the clear path handles as a soft miss.
|
||||||
|
if (m_vulkanCaps.Supports2DArrayCompatible3DImages) {
|
||||||
|
m_dynamicParameters.PerLayerFramebufferAttachmentTargets |=
|
||||||
|
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::Texture3D);
|
||||||
|
}
|
||||||
|
if (m_vulkanCaps.SupportsImageCubeArray) {
|
||||||
|
m_dynamicParameters.PerLayerFramebufferAttachmentTargets |=
|
||||||
|
DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::TextureCubeMapArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_dynamicParameters.SupportsFloat64VertexAttributes = m_vulkanCaps.SupportsShaderFloat64;
|
||||||
m_dynamicParameters.MaxShaderStorageBlockSize =
|
m_dynamicParameters.MaxShaderStorageBlockSize =
|
||||||
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
||||||
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
||||||
m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize;
|
m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize;
|
||||||
m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages);
|
m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages);
|
||||||
m_dynamicParameters.SubgroupSupportedFeatures = mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
m_dynamicParameters.SubgroupSupportedFeatures =
|
||||||
|
mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
||||||
m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages;
|
m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages;
|
||||||
} else {
|
} else {
|
||||||
m_dynamicParameters.SubgroupSize = 0;
|
m_dynamicParameters.SubgroupSize = 0;
|
||||||
@@ -298,8 +850,34 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
|
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
|
||||||
MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu",
|
MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu",
|
||||||
m_vulkanCaps.MaxShaderStorageBlockSize,
|
m_vulkanCaps.MaxShaderStorageBlockSize, m_dynamicParameters.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,6 +12,14 @@
|
|||||||
#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();
|
||||||
@@ -21,9 +29,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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;
|
void ReleaseEGLResources() override;
|
||||||
|
|
||||||
const RendererInfo& GetRendererInfo() const override;
|
const RendererInfo& GetRendererInfo() const override;
|
||||||
@@ -34,6 +45,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void ApplyVulkanCapabilitiesForTesting(const MG_External::VulkanCapabilities& capabilities);
|
void ApplyVulkanCapabilitiesForTesting(const MG_External::VulkanCapabilities& capabilities);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||||
|
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||||
void UpdateAdvertisedExtensions();
|
void UpdateAdvertisedExtensions();
|
||||||
void UpdateDynamicBackendParameters();
|
void UpdateDynamicBackendParameters();
|
||||||
|
|
||||||
@@ -42,4 +55,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MG_External::VulkanCapabilities m_vulkanCaps;
|
MG_External::VulkanCapabilities m_vulkanCaps;
|
||||||
RendererInfo m_rendererInfo;
|
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,10 +8,26 @@
|
|||||||
|
|
||||||
#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();
|
||||||
|
|
||||||
|
// Drops every cached program-resource reflection entry (CPU-side strings/vectors
|
||||||
|
// only, no Vulkan handles). Called at EGL teardown next to the renderer reset;
|
||||||
|
// safe because GL calls are serialized in this codebase, and any still-live
|
||||||
|
// program rebuilds its entry from the retained generated SPIR-V on demand.
|
||||||
|
void ClearProgramResourceCaches();
|
||||||
|
|
||||||
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);
|
||||||
@@ -19,12 +35,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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,
|
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
GLint drawbuffer, const GLfloat* value);
|
GLint drawbuffer, const GLfloat* value);
|
||||||
|
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
|
GLint drawbuffer, const GLint* value);
|
||||||
|
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
|
GLint drawbuffer, const GLuint* value);
|
||||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
GLint drawbuffer, GLfloat depth, GLint stencil);
|
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,
|
||||||
@@ -61,6 +82,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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 DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
void DispatchComputeIndirect(GLintptr indirect);
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
@@ -84,5 +110,39 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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,
|
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget uploadTarget,
|
||||||
GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels);
|
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();
|
||||||
|
BackendQueryHandle BeginXfbPrimitivesQuery(Bool generated);
|
||||||
|
void EndXfbPrimitivesQuery(BackendQueryHandle query);
|
||||||
|
BackendQueryHandle BeginOcclusionQuery();
|
||||||
|
void EndOcclusionQuery(BackendQueryHandle query);
|
||||||
|
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
|
||||||
|
|||||||
@@ -13,19 +13,22 @@ 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 * 2, VK_NULL_HANDLE);
|
||||||
VkCommandBufferAllocateInfo allocInfo{};
|
VkCommandBufferAllocateInfo allocInfo{};
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||||
allocInfo.commandPool = commandPool;
|
allocInfo.commandPool = commandPool;
|
||||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
allocInfo.commandBufferCount = frameCount;
|
allocInfo.commandBufferCount = frameCount * 2;
|
||||||
VkResult result = vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data());
|
VkResult result = vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data());
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||||
m_frames[i].commandBuffer = commandBuffers[i];
|
m_frames[i].commandBuffer = commandBuffers[i];
|
||||||
|
m_frames[i].preCommandBuffer = commandBuffers[frameCount + i];
|
||||||
}
|
}
|
||||||
|
|
||||||
VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||||
@@ -45,9 +48,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
void FrameContext::Destroy(VkDevice device, VkCommandPool commandPool) {
|
void FrameContext::Destroy(VkDevice device, VkCommandPool commandPool) {
|
||||||
const Uint32 frameCount = static_cast<Uint32>(m_frames.size());
|
const Uint32 frameCount = static_cast<Uint32>(m_frames.size());
|
||||||
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
Vector<VkCommandBuffer> commandBuffers(frameCount * 2, VK_NULL_HANDLE);
|
||||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||||
commandBuffers[i] = m_frames[i].commandBuffer;
|
commandBuffers[i] = m_frames[i].commandBuffer;
|
||||||
|
commandBuffers[frameCount + i] = m_frames[i].preCommandBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Uint32 i = 0; i < frameCount; ++i) {
|
for (Uint32 i = 0; i < frameCount; ++i) {
|
||||||
@@ -55,10 +59,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()) {
|
||||||
vkFreeCommandBuffers(device, commandPool, frameCount, commandBuffers.data());
|
for (auto& frame : m_frames) {
|
||||||
|
FreeRetiredCommandBuffers(frame);
|
||||||
|
}
|
||||||
|
vkFreeCommandBuffers(device, commandPool, frameCount * 2, 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() {
|
||||||
@@ -80,6 +89,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
currentFrameIndex = (currentFrameIndex + 1) % static_cast<Uint32>(m_frames.size());
|
currentFrameIndex = (currentFrameIndex + 1) % static_cast<Uint32>(m_frames.size());
|
||||||
GetCurrent().isCommandRecording = false;
|
GetCurrent().isCommandRecording = false;
|
||||||
GetCurrent().hasCommandBufferRecorded = false;
|
GetCurrent().hasCommandBufferRecorded = false;
|
||||||
|
GetCurrent().isPreCommandRecording = false;
|
||||||
|
GetCurrent().hasPreCommandBufferRecorded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkCommandBuffer& FrameContext::BeginCommandRecording(VkCommandBufferUsageFlags flags,
|
VkCommandBuffer& FrameContext::BeginCommandRecording(VkCommandBufferUsageFlags flags,
|
||||||
@@ -97,6 +108,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +122,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frame.hasCommandBufferRecorded = true;
|
frame.hasCommandBufferRecorded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkCommandBuffer FrameContext::BeginPreCommandRecording() {
|
||||||
|
auto& frame = GetCurrent();
|
||||||
|
if (frame.isPreCommandRecording) {
|
||||||
|
return frame.preCommandBuffer;
|
||||||
|
}
|
||||||
|
MOBILEGL_ASSERT(!frame.hasPreCommandBufferRecorded,
|
||||||
|
"BeginPreCommandRecording: a recorded pre stream is still awaiting submission");
|
||||||
|
VK_VERIFY(vkResetCommandBuffer(frame.preCommandBuffer, 0), "BeginPreCommandRecording, vkResetCommandBuffer");
|
||||||
|
VkCommandBufferBeginInfo beginInfo{};
|
||||||
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
|
VK_VERIFY(vkBeginCommandBuffer(frame.preCommandBuffer, &beginInfo),
|
||||||
|
"BeginPreCommandRecording, vkBeginCommandBuffer");
|
||||||
|
frame.isPreCommandRecording = true;
|
||||||
|
return frame.preCommandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameContext::EndPreCommandRecordingIfOpen() {
|
||||||
|
auto& frame = GetCurrent();
|
||||||
|
if (!frame.isPreCommandRecording) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VK_VERIFY(vkEndCommandBuffer(frame.preCommandBuffer), "EndPreCommandRecordingIfOpen, vkEndCommandBuffer");
|
||||||
|
frame.isPreCommandRecording = false;
|
||||||
|
frame.hasPreCommandBufferRecorded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameContext::AbandonPreCommandRecording() {
|
||||||
|
auto& frame = GetCurrent();
|
||||||
|
if (frame.isPreCommandRecording) {
|
||||||
|
VK_VERIFY(vkEndCommandBuffer(frame.preCommandBuffer), "AbandonPreCommandRecording, vkEndCommandBuffer");
|
||||||
|
}
|
||||||
|
frame.isPreCommandRecording = false;
|
||||||
|
frame.hasPreCommandBufferRecorded = false;
|
||||||
|
}
|
||||||
|
|
||||||
VkResult FrameContext::InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount) {
|
VkResult FrameContext::InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount) {
|
||||||
DestroySwapchainSemaphores(device);
|
DestroySwapchainSemaphores(device);
|
||||||
if (swapchainImageCount == 0) {
|
if (swapchainImageCount == 0) {
|
||||||
@@ -140,12 +189,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
Bool FrameContext::TransitionToPresent(VkImage image, VkImageLayout oldLayout, VkImageLayout presentLayout) {
|
Bool FrameContext::TransitionToPresent(VkImage image, VkImageLayout oldLayout, VkImageLayout presentLayout) {
|
||||||
auto& frame = GetCurrent();
|
auto& frame = GetCurrent();
|
||||||
if (frame.hasCommandBufferRecorded || frame.isCommandRecording || oldLayout == presentLayout ||
|
if (oldLayout == presentLayout || oldLayout == VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) {
|
||||||
oldLayout == VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& commandBuffer = BeginCommandRecording();
|
// The barrier belongs in the frame's own recording. Bailing out because
|
||||||
|
// something was already recorded (the previous behaviour) dropped the
|
||||||
|
// transition entirely for every frame that never ran a default-framebuffer
|
||||||
|
// render pass - the only other thing that carries the image to
|
||||||
|
// PRESENT_SRC_KHR, via that pass's finalLayout - so the swapchain image was
|
||||||
|
// handed to the WSI still in the layout it was acquired in.
|
||||||
|
// A closed-but-unsubmitted buffer can only come from a submit that already
|
||||||
|
// failed (SubmitPendingCommandBuffer leaves the flag set on error), and
|
||||||
|
// appending to it is illegal while reopening would reset the frame's own
|
||||||
|
// commands away. The device is gone on that path anyway - stay silent-safe
|
||||||
|
// rather than trade a lost device for a barrier into a closed buffer.
|
||||||
|
if (frame.hasCommandBufferRecorded) {
|
||||||
|
MGLOG_E("TransitionToPresent: command buffer already closed; skipping the present barrier");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopening a recording here would vkResetCommandBuffer this frame's own
|
||||||
|
// commands away, so append to the open one and let the caller close it.
|
||||||
|
const Bool openedRecording = !frame.isCommandRecording;
|
||||||
|
VkCommandBuffer commandBuffer = openedRecording ? BeginCommandRecording() : frame.commandBuffer;
|
||||||
|
|
||||||
VkImageMemoryBarrier presentBarrier{};
|
VkImageMemoryBarrier presentBarrier{};
|
||||||
presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||||
@@ -164,7 +231,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0,
|
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0,
|
||||||
nullptr, 0, nullptr, 1, &presentBarrier);
|
nullptr, 0, nullptr, 1, &presentBarrier);
|
||||||
|
|
||||||
EndCommandRecording();
|
if (openedRecording) {
|
||||||
|
EndCommandRecording();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,34 +241,44 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 swapchainImageIndex) const {
|
Uint32 swapchainImageIndex) const {
|
||||||
const auto& frame = GetCurrent();
|
const auto& frame = GetCurrent();
|
||||||
MOBILEGL_ASSERT(!frame.isCommandRecording, "GetSubmitInfo called while command buffer recording is still active");
|
MOBILEGL_ASSERT(!frame.isCommandRecording, "GetSubmitInfo called while command buffer recording is still active");
|
||||||
|
MOBILEGL_ASSERT(!frame.isPreCommandRecording,
|
||||||
|
"GetSubmitInfo called while the pre-pass stream is still recording");
|
||||||
AssertValidSwapchainImageIndex(swapchainImageIndex);
|
AssertValidSwapchainImageIndex(swapchainImageIndex);
|
||||||
SubmitInfoPacket packet{};
|
SubmitInfoPacket packet{};
|
||||||
packet.waitSemaphore = frame.imageAvailableSemaphore;
|
packet.waitSemaphore = frame.imageAvailableSemaphore;
|
||||||
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
||||||
packet.commandBuffer = frame.commandBuffer;
|
|
||||||
|
Uint32 commandBufferCount = 0;
|
||||||
|
// The pre-pass stream executes strictly before the frame's commands.
|
||||||
|
if (frame.hasPreCommandBufferRecorded) {
|
||||||
|
packet.commandBuffers[commandBufferCount++] = frame.preCommandBuffer;
|
||||||
|
}
|
||||||
|
if (shouldSubmitCommandBuffer) {
|
||||||
|
packet.commandBuffers[commandBufferCount++] = frame.commandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
|
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
|
||||||
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
|
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
|
||||||
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
|
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
|
||||||
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
|
packet.submitInfo.commandBufferCount = commandBufferCount;
|
||||||
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
|
packet.submitInfo.pCommandBuffers = commandBufferCount > 0 ? packet.commandBuffers : nullptr;
|
||||||
packet.submitInfo.signalSemaphoreCount = 1;
|
packet.submitInfo.signalSemaphoreCount = 1;
|
||||||
packet.submitInfo.pSignalSemaphores = &packet.signalSemaphore;
|
packet.submitInfo.pSignalSemaphores = &packet.signalSemaphore;
|
||||||
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,15 +290,27 @@ 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,
|
||||||
if (result != VK_SUCCESS) {
|
&outImageIndex);
|
||||||
|
// VK_SUBOPTIMAL_KHR is a success code: an image *was* acquired and
|
||||||
|
// imageAvailableSemaphore *will* be signaled. Bailing out on it skipped both
|
||||||
|
// the consumed-flag reset (leaving a stale "already consumed", so the next
|
||||||
|
// submit never waited on the pending signal) and the fence reset (leaving
|
||||||
|
// the slot's fence signaled for the next submit to reuse). Only a genuine
|
||||||
|
// failure - VK_ERROR_OUT_OF_DATE_KHR and friends, where nothing is acquired
|
||||||
|
// and nothing is signaled - skips the bookkeeping.
|
||||||
|
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.imageAvailableSemaphoreConsumed = false;
|
frame.imageAvailableSemaphoreConsumed = false;
|
||||||
return vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
const VkResult resetResult = vkResetFences(device, 1, &frame.imageInFlightFence);
|
||||||
&outImageIndex);
|
// Hand the acquire's own code back so the caller can schedule a rebuild.
|
||||||
|
return resetResult == VK_SUCCESS ? result : resetResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
||||||
@@ -230,6 +321,85 @@ 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(Bool retirePreCommandBuffer) {
|
||||||
|
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");
|
||||||
|
MOBILEGL_ASSERT(!frame.isPreCommandRecording,
|
||||||
|
"RetireCurrentCommandBuffer called while the pre-pass stream 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;
|
||||||
|
VkResult result = vkAllocateCommandBuffers(m_device, &allocInfo, &replacement);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (retirePreCommandBuffer) {
|
||||||
|
VkCommandBuffer preReplacement = VK_NULL_HANDLE;
|
||||||
|
result = vkAllocateCommandBuffers(m_device, &allocInfo, &preReplacement);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
vkFreeCommandBuffers(m_device, m_commandPool, 1, &replacement);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
frame.retiredCommandBuffers.push_back({frame.preCommandBuffer, frame.lastSubmitIndex});
|
||||||
|
frame.preCommandBuffer = preReplacement;
|
||||||
|
}
|
||||||
|
// lastSubmitIndex was just written by the renderer for the submission
|
||||||
|
// that carried this command buffer.
|
||||||
|
frame.retiredCommandBuffers.push_back({frame.commandBuffer, frame.lastSubmitIndex});
|
||||||
|
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) {
|
||||||
|
for (const auto& retired : frame.retiredCommandBuffers) {
|
||||||
|
vkFreeCommandBuffers(m_device, m_commandPool, 1, &retired.commandBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame.retiredCommandBuffers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameContext::FreeRetiredCommandBuffersCompletedUpTo(Uint64 completedSubmitIndex) {
|
||||||
|
if (m_device == VK_NULL_HANDLE || m_commandPool == VK_NULL_HANDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto& frame : m_frames) {
|
||||||
|
// Retired buffers are appended in submit order, so the completed
|
||||||
|
// ones form a prefix.
|
||||||
|
SizeT completedCount = 0;
|
||||||
|
while (completedCount < frame.retiredCommandBuffers.size() &&
|
||||||
|
frame.retiredCommandBuffers[completedCount].submitIndex <= completedSubmitIndex) {
|
||||||
|
vkFreeCommandBuffers(m_device, m_commandPool, 1,
|
||||||
|
&frame.retiredCommandBuffers[completedCount].commandBuffer);
|
||||||
|
++completedCount;
|
||||||
|
}
|
||||||
|
if (completedCount > 0) {
|
||||||
|
frame.retiredCommandBuffers.erase(frame.retiredCommandBuffers.begin(),
|
||||||
|
frame.retiredCommandBuffers.begin() + completedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameContext::FreeAllRetiredCommandBuffers() {
|
||||||
|
for (auto& frame : m_frames) {
|
||||||
|
FreeRetiredCommandBuffers(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,28 +14,66 @@
|
|||||||
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;
|
||||||
VkSemaphore signalSemaphore = VK_NULL_HANDLE;
|
VkSemaphore signalSemaphore = VK_NULL_HANDLE;
|
||||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
// [0] = pre-pass command buffer (when recorded), then the frame
|
||||||
|
// command buffer; submitInfo.pCommandBuffers points here.
|
||||||
|
VkCommandBuffer commandBuffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
||||||
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||||
};
|
};
|
||||||
|
|
||||||
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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A command buffer submitted mid-frame (FlushPendingCommands), tagged
|
||||||
|
// with the submit-tracker index it was submitted under so it can be
|
||||||
|
// freed as soon as that submission is observed complete - without
|
||||||
|
// waiting for the slot's fence to be waited again (present-less flush
|
||||||
|
// loops never wait it).
|
||||||
|
struct RetiredCommandBuffer {
|
||||||
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||||
|
Uint64 submitIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct FrameData {
|
struct FrameData {
|
||||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||||
|
// Pre-pass work stream: out-of-pass commands (deferred clear
|
||||||
|
// materialization, sampled-layout transitions) for resources the
|
||||||
|
// frame's recording has not touched yet. Submitted immediately
|
||||||
|
// BEFORE commandBuffer in the same vkQueueSubmit, so recording
|
||||||
|
// into it never has to split the frame's active render pass.
|
||||||
|
VkCommandBuffer preCommandBuffer = VK_NULL_HANDLE;
|
||||||
VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE;
|
VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE;
|
||||||
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
||||||
Bool isCommandRecording = false;
|
Bool isCommandRecording = false;
|
||||||
Bool hasCommandBufferRecorded = false;
|
Bool hasCommandBufferRecorded = false;
|
||||||
|
Bool isPreCommandRecording = false;
|
||||||
|
Bool hasPreCommandBufferRecorded = false;
|
||||||
Bool imageAvailableSemaphoreConsumed = false;
|
Bool imageAvailableSemaphoreConsumed = false;
|
||||||
|
// Command buffers submitted mid-frame (FlushPendingCommands),
|
||||||
|
// appended in submit order; freed once their submission is known
|
||||||
|
// complete (fence wait or completion poll).
|
||||||
|
Vector<RetiredCommandBuffer> 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);
|
||||||
@@ -49,18 +87,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkCommandBuffer& BeginCommandRecording(VkCommandBufferUsageFlags flags = 0,
|
VkCommandBuffer& BeginCommandRecording(VkCommandBufferUsageFlags flags = 0,
|
||||||
const VkCommandBufferInheritanceInfo* pInheritanceInfo = nullptr);
|
const VkCommandBufferInheritanceInfo* pInheritanceInfo = nullptr);
|
||||||
void EndCommandRecording();
|
void EndCommandRecording();
|
||||||
|
// Lazily opens the pre-pass work stream (see FrameData::preCommandBuffer).
|
||||||
|
VkCommandBuffer BeginPreCommandRecording();
|
||||||
|
// Closes the pre stream if open, marking it for submission ahead of the
|
||||||
|
// frame command buffer. Safe to call when it never opened.
|
||||||
|
void EndPreCommandRecordingIfOpen();
|
||||||
|
// Drops an in-progress or recorded-but-unsubmitted pre stream (dropped
|
||||||
|
// frame recordings, swapchain recreation).
|
||||||
|
void AbandonPreCommandRecording();
|
||||||
VkResult InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount);
|
VkResult InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount);
|
||||||
void DestroySwapchainSemaphores(VkDevice device);
|
void DestroySwapchainSemaphores(VkDevice device);
|
||||||
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, or as soon
|
||||||
|
// as their submission is observed complete.
|
||||||
|
VkResult RetireCurrentCommandBuffer(Bool retirePreCommandBuffer = false);
|
||||||
|
|
||||||
|
// Frees every retired command buffer whose tagged submission index is
|
||||||
|
// known complete. Driven by the renderer's submit tracker on completion
|
||||||
|
// events (fence waits and non-blocking polls), so present-less flush
|
||||||
|
// loops reclaim their buffers without any extra wait.
|
||||||
|
void FreeRetiredCommandBuffersCompletedUpTo(Uint64 completedSubmitIndex);
|
||||||
|
// Frees every slot's retired command buffers. Only valid when the
|
||||||
|
// caller has proven every queue submission complete.
|
||||||
|
void FreeAllRetiredCommandBuffers();
|
||||||
|
|
||||||
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;
|
||||||
@@ -69,9 +134,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,16 +8,183 @@
|
|||||||
|
|
||||||
#include "PipelineFactory.h"
|
#include "PipelineFactory.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
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):
|
PipelineFactory::PipelineFactory(VkDevice device, const VulkanRendererConfig& config):
|
||||||
m_device(device), m_config(config) {
|
m_device(device), m_config(config) {
|
||||||
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "PipelineFactory: device is null");
|
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};
|
VkPipelineCacheCreateInfo pipelineCacheInfo{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO};
|
||||||
VK_VERIFY(vkCreatePipelineCache(m_device, &pipelineCacheInfo, nullptr, &m_pipelineCache),
|
VK_VERIFY(vkCreatePipelineCache(m_device, &pipelineCacheInfo, nullptr, &m_pipelineCache),
|
||||||
"vkCreatePipelineCache");
|
"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) {
|
if (m_pipelineCache != VK_NULL_HANDLE) {
|
||||||
@@ -33,13 +200,40 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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.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.patchControlPoints, sizeof(payload.patchControlPoints)));
|
||||||
|
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.cullMode, sizeof(payload.cullMode)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.provokingVertexMode, sizeof(payload.provokingVertexMode)));
|
||||||
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.logicOp, sizeof(payload.logicOp)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilFailOp, sizeof(payload.backStencilFailOp)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilPassOp, sizeof(payload.backStencilPassOp)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth)));
|
||||||
if (payload.colorAttachmentCount > 0) {
|
if (payload.colorAttachmentCount > 0) {
|
||||||
XXHASH_VERIFY(XXH64_update(
|
XXHASH_VERIFY(XXH64_update(
|
||||||
m_hashState,
|
m_hashState,
|
||||||
@@ -53,23 +247,108 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const HashType hash = ComputeHash(payload);
|
const HashType hash = ComputeHash(payload);
|
||||||
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;
|
it->second.lastUsedFrame = m_frameCounter;
|
||||||
|
return it->second.pipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkPipeline pipeline = CreatePipeline(payload);
|
VkPipeline pipeline = CreatePipeline(payload);
|
||||||
m_cache.emplace(hash, pipeline);
|
m_cache.emplace(hash, PipelineCacheEntry{pipeline, payload.programHash, payload.renderPass,
|
||||||
|
m_frameCounter});
|
||||||
return pipeline;
|
return pipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineFactory::DestroyAll() {
|
void PipelineFactory::DestroyAll() {
|
||||||
for (auto& pair : m_cache) {
|
for (auto& pair : m_cache) {
|
||||||
if (pair.second != VK_NULL_HANDLE) {
|
if (pair.second.pipeline != VK_NULL_HANDLE) {
|
||||||
vkDestroyPipeline(m_device, pair.second, nullptr);
|
vkDestroyPipeline(m_device, pair.second.pipeline, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_cache.clear();
|
m_cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uint32 PipelineFactory::OnFrameBoundary() {
|
||||||
|
++m_frameCounter;
|
||||||
|
|
||||||
|
// Sweep cadence and retire age mirror VkRenderPassManager::OnPresent: an entry
|
||||||
|
// idle for more than kRetireAgeFrames frame boundaries cannot be referenced by
|
||||||
|
// any in-flight command buffer (frames-in-flight <= MOBILEGL_MAGMA_FRAMESINFLIGHT),
|
||||||
|
// so immediate vkDestroyPipeline is safe. The caller must drop its "last
|
||||||
|
// pipeline" memo when this returns non-zero: the memo can return a cached
|
||||||
|
// handle without touching this cache, so an evicted pipeline may still be
|
||||||
|
// memoized (present-less flush loops never reset the memo per frame).
|
||||||
|
constexpr Uint64 kSweepInterval = 256;
|
||||||
|
constexpr Uint64 kRetireAgeFrames = 1024;
|
||||||
|
if ((m_frameCounter % kSweepInterval) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 evicted = 0;
|
||||||
|
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
||||||
|
if (m_frameCounter - it->second.lastUsedFrame > kRetireAgeFrames) {
|
||||||
|
if (it->second.pipeline != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyPipeline(m_device, it->second.pipeline, nullptr);
|
||||||
|
}
|
||||||
|
it = m_cache.erase(it);
|
||||||
|
++evicted;
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (evicted > 0) {
|
||||||
|
MGLOG_D("PipelineFactory::OnFrameBoundary: evicted %u idle pipelines (%zu remain)", evicted,
|
||||||
|
m_cache.size());
|
||||||
|
}
|
||||||
|
return evicted;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 PipelineFactory::EvictByRenderPasses(const Vector<VkRenderPass>& renderPasses) {
|
||||||
|
if (renderPasses.empty() || m_cache.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Sorted-batch membership test keeps a mass eviction (shader-pack switch,
|
||||||
|
// dimension exit) at one O(cache * log batch) scan instead of one full scan
|
||||||
|
// per dying pass.
|
||||||
|
Vector<VkRenderPass> sortedPasses = renderPasses;
|
||||||
|
std::sort(sortedPasses.begin(), sortedPasses.end());
|
||||||
|
Uint32 evicted = 0;
|
||||||
|
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
||||||
|
if (std::binary_search(sortedPasses.begin(), sortedPasses.end(), it->second.renderPass)) {
|
||||||
|
if (it->second.pipeline != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyPipeline(m_device, it->second.pipeline, nullptr);
|
||||||
|
}
|
||||||
|
it = m_cache.erase(it);
|
||||||
|
++evicted;
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (evicted > 0) {
|
||||||
|
MGLOG_D("PipelineFactory::EvictByRenderPasses: evicted %u pipelines for %zu destroyed render passes",
|
||||||
|
evicted, sortedPasses.size());
|
||||||
|
}
|
||||||
|
return evicted;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 PipelineFactory::EvictByProgramHash(HashType programHash) {
|
||||||
|
Uint32 evicted = 0;
|
||||||
|
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
||||||
|
if (it->second.programHash == programHash) {
|
||||||
|
if (it->second.pipeline != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyPipeline(m_device, it->second.pipeline, nullptr);
|
||||||
|
}
|
||||||
|
it = m_cache.erase(it);
|
||||||
|
++evicted;
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (evicted > 0) {
|
||||||
|
MGLOG_D("PipelineFactory::EvictByProgramHash: evicted %u pipelines for program hash 0x%llx",
|
||||||
|
evicted, static_cast<unsigned long long>(programHash));
|
||||||
|
}
|
||||||
|
return evicted;
|
||||||
|
}
|
||||||
|
|
||||||
VkPipeline PipelineFactory::CreatePipeline(const PipelineCreatePayload& payload) const {
|
VkPipeline PipelineFactory::CreatePipeline(const PipelineCreatePayload& payload) const {
|
||||||
MOBILEGL_ASSERT(payload.stages != nullptr && !payload.stages->empty(), "PipelineFactory: stages are empty");
|
MOBILEGL_ASSERT(payload.stages != nullptr && !payload.stages->empty(), "PipelineFactory: stages are empty");
|
||||||
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
||||||
@@ -86,7 +365,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
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{};
|
||||||
@@ -96,32 +381,80 @@ 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;
|
||||||
|
|
||||||
|
// Only a patch topology has a tessellation stage to configure; leaving the pointer null
|
||||||
|
// otherwise is what the spec expects.
|
||||||
|
VkPipelineTessellationStateCreateInfo tessellation{VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO};
|
||||||
|
tessellation.patchControlPoints = payload.patchControlPoints;
|
||||||
|
|
||||||
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 = payload.cullMode;
|
raster.cullMode = payload.cullMode;
|
||||||
raster.frontFace = payload.frontFace;
|
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;
|
||||||
|
// Only chain the struct when the mode is not Vulkan's implicit default: a device without
|
||||||
|
// VK_EXT_provoking_vertex enabled must never see this pNext entry, and the renderer's
|
||||||
|
// selector already collapses to FIRST in exactly that case - so a device without the
|
||||||
|
// extension produces a byte-identical VkGraphicsPipelineCreateInfo to before.
|
||||||
|
VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provokingVertexState{
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT};
|
||||||
|
if (payload.provokingVertexMode != VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT) {
|
||||||
|
provokingVertexState.provokingVertexMode = payload.provokingVertexMode;
|
||||||
|
provokingVertexState.pNext = raster.pNext;
|
||||||
|
raster.pNext = &provokingVertexState;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
Vector<VkPipelineColorBlendAttachmentState> colorAttachments(payload.colorAttachmentCount);
|
Vector<VkPipelineColorBlendAttachmentState> colorAttachments(payload.colorAttachmentCount);
|
||||||
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
||||||
colorAttachments[i] = payload.colorBlendAttachments[i];
|
colorAttachments[i] = payload.colorBlendAttachments[i];
|
||||||
}
|
}
|
||||||
|
// Suppress depth writes on accumulation-blended pipelines when the active driver
|
||||||
|
// cannot keep vertex positions invariant across the pipelines of a multi-pass
|
||||||
|
// depth-equality chain (see SetSuppressBlendedDepthWrite). The decision is narrowed
|
||||||
|
// in ShouldSuppressDepthWrite: sorted-transparency "over" blends (vanilla MC water),
|
||||||
|
// 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.logicOpEnable = payload.logicOpEnable ? VK_TRUE : VK_FALSE;
|
||||||
|
blend.logicOp = payload.logicOp;
|
||||||
blend.attachmentCount = payload.colorAttachmentCount;
|
blend.attachmentCount = payload.colorAttachmentCount;
|
||||||
blend.pAttachments = colorAttachments.empty() ? nullptr : colorAttachments.data();
|
blend.pAttachments = colorAttachments.empty() ? nullptr : colorAttachments.data();
|
||||||
|
|
||||||
@@ -130,6 +463,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
gpi.pStages = payload.stages->data();
|
gpi.pStages = payload.stages->data();
|
||||||
gpi.pVertexInputState = payload.vertexInputState;
|
gpi.pVertexInputState = payload.vertexInputState;
|
||||||
gpi.pInputAssemblyState = &ia;
|
gpi.pInputAssemblyState = &ia;
|
||||||
|
gpi.pTessellationState =
|
||||||
|
payload.topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST ? &tessellation : nullptr;
|
||||||
gpi.pViewportState = &vpci;
|
gpi.pViewportState = &vpci;
|
||||||
gpi.pRasterizationState = &raster;
|
gpi.pRasterizationState = &raster;
|
||||||
gpi.pMultisampleState = &ms;
|
gpi.pMultisampleState = &ms;
|
||||||
@@ -141,8 +476,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, m_pipelineCache, 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
|
||||||
|
|||||||
@@ -26,13 +26,39 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||||
Uint32 colorAttachmentCount = 1;
|
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;
|
||||||
|
// GL_PATCH_VERTICES; only read for a PATCH_LIST topology.
|
||||||
|
Uint32 patchControlPoints = 3;
|
||||||
|
VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
|
||||||
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
|
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
|
||||||
VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE;
|
VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||||
|
// GL's provoking vertex, baked into the pipeline (VK_EXT_provoking_vertex). It selects
|
||||||
|
// which vertex a flat varying takes AND the vertex order transform feedback records for
|
||||||
|
// strips/fans, so it is part of the pipeline's identity, not dynamic state. Defaults to
|
||||||
|
// Vulkan's own convention, which is what a device without the extension gets.
|
||||||
|
VkProvokingVertexModeEXT provokingVertexMode = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT;
|
||||||
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;
|
||||||
|
VkLogicOp logicOp = VK_LOGIC_OP_COPY;
|
||||||
|
VkStencilOp frontStencilFailOp = VK_STENCIL_OP_KEEP;
|
||||||
|
VkStencilOp frontStencilPassOp = VK_STENCIL_OP_KEEP;
|
||||||
|
VkStencilOp frontStencilDepthFailOp = VK_STENCIL_OP_KEEP;
|
||||||
|
VkCompareOp frontStencilCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||||
|
VkStencilOp backStencilFailOp = VK_STENCIL_OP_KEEP;
|
||||||
|
VkStencilOp backStencilPassOp = VK_STENCIL_OP_KEEP;
|
||||||
|
VkStencilOp backStencilDepthFailOp = VK_STENCIL_OP_KEEP;
|
||||||
|
VkCompareOp backStencilCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||||
|
// 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{};
|
Array<VkPipelineColorBlendAttachmentState, kMaxColorAttachments> colorBlendAttachments{};
|
||||||
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
||||||
const VkPipelineVertexInputStateCreateInfo* vertexInputState = nullptr;
|
const VkPipelineVertexInputStateCreateInfo* vertexInputState = nullptr;
|
||||||
@@ -46,13 +72,69 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPipeline GetOrCreatePipeline(const PipelineCreatePayload& payload);
|
VkPipeline GetOrCreatePipeline(const PipelineCreatePayload& payload);
|
||||||
void DestroyAll();
|
void DestroyAll();
|
||||||
|
|
||||||
|
// Frame boundary hook: ages the pipeline cache and destroys long-unused entries
|
||||||
|
// (their command buffers retired many frames ago), mirroring
|
||||||
|
// VkRenderPassManager::OnPresent's sweep. Returns the number of pipelines
|
||||||
|
// destroyed so the caller can drop any memoized VkPipeline handle.
|
||||||
|
Uint32 OnFrameBoundary();
|
||||||
|
// Destroys every cached pipeline hashed on one of `renderPasses`. Only safe
|
||||||
|
// when the caller guarantees GPU idleness for them - the render-pass manager
|
||||||
|
// calls this (via the renderer) for passes its own >1024-boundary-idle sweep
|
||||||
|
// just evicted, and a pipeline hashed on those handles is only ever bound by
|
||||||
|
// draws that also hit the render-pass entries. Also closes the handle-recycling
|
||||||
|
// hazard: a recycled VkRenderPass value must never serve a stale pipeline.
|
||||||
|
// Batched: one cache scan regardless of how many passes died in the sweep.
|
||||||
|
// Returns the number destroyed (callers invalidate memos when non-zero).
|
||||||
|
Uint32 EvictByRenderPasses(const Vector<VkRenderPass>& renderPasses);
|
||||||
|
// Destroys every cached pipeline built from the program with content hash
|
||||||
|
// `programHash`. Called from the ProgramFactory eviction path, which proves the
|
||||||
|
// same >1024-boundary idleness (the program's pipelines are only bound by draws
|
||||||
|
// that stamp its factory entry). Returns the number destroyed.
|
||||||
|
Uint32 EvictByProgramHash(HashType programHash);
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
struct PipelineCacheEntry {
|
||||||
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
|
// The hashed inputs the eviction paths key on: programHash ties the entry to
|
||||||
|
// its ProgramFactory entry, renderPass records the exact handle the hash
|
||||||
|
// folded in (the hash is one-way, so targeted eviction needs them verbatim).
|
||||||
|
HashType programHash = 0;
|
||||||
|
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||||
|
// Frame-boundary counter value of the last GetOrCreatePipeline hit; drives
|
||||||
|
// cache eviction (see OnFrameBoundary).
|
||||||
|
Uint64 lastUsedFrame = 0;
|
||||||
|
};
|
||||||
|
|
||||||
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;
|
VkPipelineCache m_pipelineCache = VK_NULL_HANDLE;
|
||||||
UnorderedMap<HashType, VkPipeline> m_cache;
|
UnorderedMap<HashType, PipelineCacheEntry> m_cache;
|
||||||
|
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
||||||
|
Uint64 m_frameCounter = 0;
|
||||||
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
@@ -14,8 +14,16 @@
|
|||||||
#include "MG_State/GLState/TextureState/TextureEnum.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 {
|
enum class DescriptorBindingKind : Uint8 {
|
||||||
@@ -34,6 +42,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
SurfaceRotate90 = 1 << 2,
|
SurfaceRotate90 = 1 << 2,
|
||||||
SurfaceRotate180 = 1 << 3,
|
SurfaceRotate180 = 1 << 3,
|
||||||
SurfaceRotate270 = 1 << 4,
|
SurfaceRotate270 = 1 << 4,
|
||||||
|
// Rewrites the fragment stage's implicit-LOD image samples to explicit LOD 0.
|
||||||
|
// Only ever set for a draw whose every sampler binding is clamped to a single mip
|
||||||
|
// level, which makes the two forms produce identical texels (the implicit lambda is
|
||||||
|
// clamped into [minLod, maxLod] = [0, 0] regardless of derivatives or bias).
|
||||||
|
ExplicitLod0Sampling = 1 << 5,
|
||||||
|
// Decorates the last vertex-processing stage's captured varyings with
|
||||||
|
// XfbBuffer/XfbStride/Offset (VK_EXT_transform_feedback). Set only for draws
|
||||||
|
// recorded while GL transform feedback is active, so plain draws keep the
|
||||||
|
// undecorated variant.
|
||||||
|
XfbCapture = 1 << 6,
|
||||||
};
|
};
|
||||||
using CompileOptionFlags = Flags<CompileOptionBit>;
|
using CompileOptionFlags = Flags<CompileOptionBit>;
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
@@ -51,11 +69,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<DescriptorBindingKind> bindingKinds;
|
Vector<DescriptorBindingKind> bindingKinds;
|
||||||
Vector<Uint32> dynamicBindings;
|
Vector<Uint32> dynamicBindings;
|
||||||
Vector<Int> uniformBlockIndexByBinding;
|
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<String> samplerNameByBinding;
|
||||||
Vector<Int> samplerUniformLocationByBinding;
|
Vector<Int> samplerUniformLocationByBinding;
|
||||||
Vector<TextureTarget> samplerTextureTargetByBinding;
|
Vector<TextureTarget> samplerTextureTargetByBinding;
|
||||||
|
Vector<SamplerNumericDomain> samplerNumericDomainByBinding;
|
||||||
|
Vector<VkFormat> storageImageFormatByBinding;
|
||||||
|
Vector<Bool> storageImageUsesBindingFormatByBinding;
|
||||||
Vector<String> storageBlockNameByBinding;
|
Vector<String> storageBlockNameByBinding;
|
||||||
Vector<Int> storageBlockIndexByBinding;
|
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;
|
Int globalUboBinding = -1;
|
||||||
Uint32 activeVertexInputLocationMask = 0;
|
Uint32 activeVertexInputLocationMask = 0;
|
||||||
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
|
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
|
||||||
@@ -64,6 +94,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
ShaderStage rasterizationProducerStage = ShaderStage::Unknown;
|
ShaderStage rasterizationProducerStage = ShaderStage::Unknown;
|
||||||
Uint32 producerOutputComponentCount = 0;
|
Uint32 producerOutputComponentCount = 0;
|
||||||
Uint32 fragmentInputComponentCount = 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;
|
||||||
|
// Frame-boundary counter value of the last GetOrCreateProgram hit; drives
|
||||||
|
// cache eviction (see OnFrameBoundary).
|
||||||
|
Uint64 lastUsedFrame = 0;
|
||||||
|
|
||||||
static inline VkDevice s_device = VK_NULL_HANDLE;
|
static inline VkDevice s_device = VK_NULL_HANDLE;
|
||||||
|
|
||||||
@@ -79,11 +116,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
bindingKinds = std::move(other.bindingKinds);
|
bindingKinds = std::move(other.bindingKinds);
|
||||||
dynamicBindings = std::move(other.dynamicBindings);
|
dynamicBindings = std::move(other.dynamicBindings);
|
||||||
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
||||||
|
bindingDescriptorCounts = std::move(other.bindingDescriptorCounts);
|
||||||
|
arrayedUniformBlockIndicesByBinding = std::move(other.arrayedUniformBlockIndicesByBinding);
|
||||||
samplerNameByBinding = std::move(other.samplerNameByBinding);
|
samplerNameByBinding = std::move(other.samplerNameByBinding);
|
||||||
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
|
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
|
||||||
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
|
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);
|
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
||||||
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||||
|
hasStorageImages = other.hasStorageImages;
|
||||||
globalUboBinding = other.globalUboBinding;
|
globalUboBinding = other.globalUboBinding;
|
||||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||||
vertexInputTypes = other.vertexInputTypes;
|
vertexInputTypes = other.vertexInputTypes;
|
||||||
@@ -92,15 +136,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
rasterizationProducerStage = other.rasterizationProducerStage;
|
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||||
producerOutputComponentCount = other.producerOutputComponentCount;
|
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||||
|
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
||||||
|
lastUsedFrame = other.lastUsedFrame;
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
other.hasStorageImages = false;
|
||||||
other.globalUboBinding = -1;
|
other.globalUboBinding = -1;
|
||||||
other.activeVertexInputLocationMask = 0;
|
other.activeVertexInputLocationMask = 0;
|
||||||
other.activeFragmentOutputLocationMask = 0;
|
other.activeFragmentOutputLocationMask = 0;
|
||||||
other.rasterizationProducerStage = ShaderStage::Unknown;
|
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||||
other.producerOutputComponentCount = 0;
|
other.producerOutputComponentCount = 0;
|
||||||
other.fragmentInputComponentCount = 0;
|
other.fragmentInputComponentCount = 0;
|
||||||
|
other.fragmentReplacesDepth = false;
|
||||||
|
other.lastUsedFrame = 0;
|
||||||
}
|
}
|
||||||
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
||||||
if (this == &other) {
|
if (this == &other) {
|
||||||
@@ -115,11 +164,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
bindingKinds = std::move(other.bindingKinds);
|
bindingKinds = std::move(other.bindingKinds);
|
||||||
dynamicBindings = std::move(other.dynamicBindings);
|
dynamicBindings = std::move(other.dynamicBindings);
|
||||||
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding);
|
||||||
|
bindingDescriptorCounts = std::move(other.bindingDescriptorCounts);
|
||||||
|
arrayedUniformBlockIndicesByBinding = std::move(other.arrayedUniformBlockIndicesByBinding);
|
||||||
samplerNameByBinding = std::move(other.samplerNameByBinding);
|
samplerNameByBinding = std::move(other.samplerNameByBinding);
|
||||||
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
|
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
|
||||||
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
|
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);
|
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
|
||||||
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||||
|
hasStorageImages = other.hasStorageImages;
|
||||||
globalUboBinding = other.globalUboBinding;
|
globalUboBinding = other.globalUboBinding;
|
||||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||||
vertexInputTypes = other.vertexInputTypes;
|
vertexInputTypes = other.vertexInputTypes;
|
||||||
@@ -128,15 +184,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
rasterizationProducerStage = other.rasterizationProducerStage;
|
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||||
producerOutputComponentCount = other.producerOutputComponentCount;
|
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||||
|
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
||||||
|
lastUsedFrame = other.lastUsedFrame;
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
other.hasStorageImages = false;
|
||||||
other.globalUboBinding = -1;
|
other.globalUboBinding = -1;
|
||||||
other.activeVertexInputLocationMask = 0;
|
other.activeVertexInputLocationMask = 0;
|
||||||
other.activeFragmentOutputLocationMask = 0;
|
other.activeFragmentOutputLocationMask = 0;
|
||||||
other.rasterizationProducerStage = ShaderStage::Unknown;
|
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||||
other.producerOutputComponentCount = 0;
|
other.producerOutputComponentCount = 0;
|
||||||
other.fragmentInputComponentCount = 0;
|
other.fragmentInputComponentCount = 0;
|
||||||
|
other.fragmentReplacesDepth = false;
|
||||||
|
other.lastUsedFrame = 0;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +227,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16)
|
// Notified when the OnFrameBoundary sweep destroys an aged-out cache entry,
|
||||||
: m_device(device), m_config(config), m_maxBindings(maxBindings) {
|
// carrying the entry's content hash and the VkDescriptorSetLayout it owned.
|
||||||
|
// Dependent caches (compute pipelines, PipelineFactory entries, UniformManager's
|
||||||
|
// per-layout descriptor sets) must purge in the same step: after vkDestroy the
|
||||||
|
// layout handle value may be recycled for an unrelated layout, and the program
|
||||||
|
// hash may be re-inserted by a later rebuild of the same content.
|
||||||
|
class IEvictionObserver {
|
||||||
|
public:
|
||||||
|
virtual ~IEvictionObserver() = default;
|
||||||
|
virtual void OnProgramEvicted(HashType programHash, VkDescriptorSetLayout descriptorSetLayout) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16,
|
||||||
|
Bool shaderDrawParametersEnabled = false,
|
||||||
|
Bool unformattedFloatStorageImagesEnabled = false)
|
||||||
|
: m_device(device), m_maxBindings(maxBindings), m_config(config),
|
||||||
|
m_shaderDrawParametersEnabled(shaderDrawParametersEnabled),
|
||||||
|
m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled) {
|
||||||
VkProgramObject::s_device = device;
|
VkProgramObject::s_device = device;
|
||||||
}
|
}
|
||||||
~ProgramFactory() = default;
|
~ProgramFactory() = default;
|
||||||
@@ -177,7 +254,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const VkProgramObject& GetOrCreateProgram(
|
const VkProgramObject& GetOrCreateProgram(
|
||||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
||||||
|
|
||||||
|
// Observer may be null (no notifications). Not owned.
|
||||||
|
void SetEvictionObserver(IEvictionObserver* observer) { m_evictionObserver = observer; }
|
||||||
|
// Frame boundary hook: ages the program cache and evicts long-unused entries
|
||||||
|
// (their command buffers retired many frames ago), mirroring
|
||||||
|
// VkRenderPassManager::OnPresent's sweep.
|
||||||
|
void OnFrameBoundary();
|
||||||
|
|
||||||
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 {
|
struct ProgramLookupCache {
|
||||||
@@ -201,7 +295,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 m_maxBindings = 0;
|
Uint32 m_maxBindings = 0;
|
||||||
UnorderedMap<HashType, VkProgramObject> m_cache;
|
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;
|
mutable ProgramLookupCache m_lastLookup;
|
||||||
|
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
||||||
|
Uint64 m_frameCounter = 0;
|
||||||
|
IEvictionObserver* m_evictionObserver = nullptr;
|
||||||
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
|
||||||
|
|||||||
@@ -31,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) {
|
||||||
@@ -105,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;
|
||||||
}
|
}
|
||||||
@@ -130,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");
|
||||||
@@ -153,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));
|
||||||
@@ -164,6 +187,15 @@ 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 ||
|
if (swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
|
||||||
swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
|
swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
|
||||||
std::swap(createInfo.imageExtent.width, createInfo.imageExtent.height);
|
std::swap(createInfo.imageExtent.width, createInfo.imageExtent.height);
|
||||||
@@ -215,6 +247,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
m_surfaceFormat = {createInfo.imageFormat, createInfo.imageColorSpace};
|
m_surfaceFormat = {createInfo.imageFormat, createInfo.imageColorSpace};
|
||||||
m_extent = createInfo.imageExtent;
|
m_extent = createInfo.imageExtent;
|
||||||
|
// The surface-space extent this swapchain was built from, i.e. before the
|
||||||
|
// quarter-turn swap above. Out-of-date checks must compare in THIS space: comparing a
|
||||||
|
// freshly queried currentExtent against the swapped m_extent flips axes every rotation
|
||||||
|
// and makes the comparison alternate forever.
|
||||||
|
m_surfaceExtent = defaultFramebufferExtent;
|
||||||
m_preTransform = createInfo.preTransform;
|
m_preTransform = createInfo.preTransform;
|
||||||
|
|
||||||
VK_VERIFY(vkCreateSwapchainKHR(device, &createInfo, nullptr, &m_swapchain));
|
VK_VERIFY(vkCreateSwapchainKHR(device, &createInfo, nullptr, &m_swapchain));
|
||||||
@@ -225,6 +262,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_images.resize(imageCount, VK_NULL_HANDLE);
|
m_images.resize(imageCount, VK_NULL_HANDLE);
|
||||||
VK_VERIFY(vkGetSwapchainImagesKHR(device, m_swapchain, &imageCount, m_images.data()));
|
VK_VERIFY(vkGetSwapchainImagesKHR(device, m_swapchain, &imageCount, m_images.data()));
|
||||||
m_imageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
|
m_imageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
|
||||||
|
// Fresh swapchain images hold garbage until a render pass stores into them.
|
||||||
|
m_imageContentDefined.assign(imageCount, false);
|
||||||
|
m_depthStencilContentDefined.assign(imageCount, false);
|
||||||
|
|
||||||
CreateImageViews(device);
|
CreateImageViews(device);
|
||||||
CreateDepthStencilResources(device, physicalDevice);
|
CreateDepthStencilResources(device, physicalDevice);
|
||||||
@@ -234,11 +274,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
// Properly initialize Default FBO here
|
// Properly initialize Default FBO here
|
||||||
auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
|
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());
|
auto* colorTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->colorAttachment.get());
|
||||||
colorTex->AllocateStorage(
|
colorTex->AllocateStorage(
|
||||||
TextureUploadTarget::Texture2D, 0, {
|
TextureUploadTarget::Texture2D, 0, {
|
||||||
{(Int)createInfo.imageExtent.width, (Int)createInfo.imageExtent.height, 1},
|
{extentWidth, extentHeight, 1},
|
||||||
createInfo.imageExtent.width * (Int)createInfo.imageExtent.height * 4}); // TODO: 4 is format size
|
defaultAttachmentByteSize}); // TODO: 4 is format size
|
||||||
TextureInternalFormat depthFormat = TextureInternalFormat::Depth24Stencil8;
|
TextureInternalFormat depthFormat = TextureInternalFormat::Depth24Stencil8;
|
||||||
switch (m_depthStencilFormat) {
|
switch (m_depthStencilFormat) {
|
||||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||||
@@ -257,8 +303,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto* depthTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->depthAttachment.get());
|
auto* depthTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->depthAttachment.get());
|
||||||
depthTex->SetInternalFormat(depthFormat);
|
depthTex->SetInternalFormat(depthFormat);
|
||||||
depthTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {
|
depthTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {
|
||||||
{(Int)createInfo.imageExtent.width, (Int)createInfo.imageExtent.height, 1},
|
{extentWidth, extentHeight, 1},
|
||||||
createInfo.imageExtent.width * createInfo.imageExtent.width * 4}); // TODO: 4 is format size
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,9 +436,39 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
m_images.clear();
|
m_images.clear();
|
||||||
m_imageLayouts.clear();
|
m_imageLayouts.clear();
|
||||||
|
m_imageContentDefined.clear();
|
||||||
|
m_depthStencilContentDefined.clear();
|
||||||
m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool SwapchainObject::IsImageContentDefined(Uint32 index) const {
|
||||||
|
MOBILEGL_ASSERT(index < m_imageContentDefined.size(), "Swapchain image content index out of range");
|
||||||
|
return m_imageContentDefined[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwapchainObject::SetImageContentDefined(Uint32 index, Bool defined) {
|
||||||
|
MOBILEGL_ASSERT(index < m_imageContentDefined.size(), "Swapchain image content index out of range");
|
||||||
|
m_imageContentDefined[index] = defined;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool SwapchainObject::IsDepthStencilContentDefined(Uint32 index) const {
|
||||||
|
MOBILEGL_ASSERT(index < m_depthStencilContentDefined.size(),
|
||||||
|
"Swapchain depth/stencil content index out of range");
|
||||||
|
return m_depthStencilContentDefined[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwapchainObject::SetDepthStencilContentDefined(Uint32 index, Bool defined) {
|
||||||
|
MOBILEGL_ASSERT(index < m_depthStencilContentDefined.size(),
|
||||||
|
"Swapchain depth/stencil content index out of range");
|
||||||
|
m_depthStencilContentDefined[index] = defined;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwapchainObject::SetAllDepthStencilContentUndefined() {
|
||||||
|
for (SizeT i = 0; i < m_depthStencilContentDefined.size(); ++i) {
|
||||||
|
m_depthStencilContentDefined[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VkImage SwapchainObject::GetImage(Uint32 index) const {
|
VkImage SwapchainObject::GetImage(Uint32 index) const {
|
||||||
MOBILEGL_ASSERT(index < m_images.size(), "Swapchain image index out of range");
|
MOBILEGL_ASSERT(index < m_images.size(), "Swapchain image index out of range");
|
||||||
return m_images[index];
|
return m_images[index];
|
||||||
|
|||||||
@@ -29,12 +29,15 @@ 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; }
|
||||||
const VkSurfaceFormatKHR& GetSurfaceFormat() const { return m_surfaceFormat; }
|
const VkSurfaceFormatKHR& GetSurfaceFormat() const { return m_surfaceFormat; }
|
||||||
VkExtent2D GetExtent() const { return m_extent; }
|
VkExtent2D GetExtent() const { return m_extent; }
|
||||||
|
// Surface-space extent (before the pre-rotation quarter-turn swap) this swapchain was
|
||||||
|
// created from - the value to compare a freshly queried currentExtent against.
|
||||||
|
VkExtent2D GetSurfaceExtent() const { return m_surfaceExtent; }
|
||||||
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; }
|
||||||
@@ -49,6 +52,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void SetImageLayout(Uint32 index, VkImageLayout layout);
|
void SetImageLayout(Uint32 index, VkImageLayout layout);
|
||||||
SizeT GetImageCount() const { return m_images.size(); }
|
SizeT GetImageCount() const { return m_images.size(); }
|
||||||
|
|
||||||
|
// EGL content-validity tracking for the default framebuffer. A color
|
||||||
|
// buffer's content is undefined once its image has been presented
|
||||||
|
// (EGL_BUFFER_DESTROYED swap behaviour, the implementation default),
|
||||||
|
// and every ancillary (depth/stencil) buffer's content is undefined
|
||||||
|
// after ANY swap regardless of swap behaviour (EGL 1.5 §3.10.1). The
|
||||||
|
// render-pass manager turns an undefined attachment's tile load into
|
||||||
|
// LOAD_OP_DONT_CARE. Flags start false (a fresh swapchain image holds
|
||||||
|
// garbage) and a render pass storing into an attachment sets it back
|
||||||
|
// to defined.
|
||||||
|
Bool IsImageContentDefined(Uint32 index) const;
|
||||||
|
void SetImageContentDefined(Uint32 index, Bool defined);
|
||||||
|
Bool IsDepthStencilContentDefined(Uint32 index) const;
|
||||||
|
void SetDepthStencilContentDefined(Uint32 index, Bool defined);
|
||||||
|
void SetAllDepthStencilContentUndefined();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateImageViews(VkDevice device);
|
void CreateImageViews(VkDevice device);
|
||||||
void CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice);
|
void CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice);
|
||||||
@@ -63,6 +81,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
|
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
|
||||||
VkSurfaceFormatKHR m_surfaceFormat{};
|
VkSurfaceFormatKHR m_surfaceFormat{};
|
||||||
VkExtent2D m_extent{};
|
VkExtent2D m_extent{};
|
||||||
|
VkExtent2D m_surfaceExtent{};
|
||||||
VkSurfaceTransformFlagBitsKHR m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
VkSurfaceTransformFlagBitsKHR m_preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||||
Vector<VkImage> m_images;
|
Vector<VkImage> m_images;
|
||||||
Vector<VkImageView> m_imageViews;
|
Vector<VkImageView> m_imageViews;
|
||||||
@@ -73,5 +92,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<VkDeviceMemory> m_depthStencilImageMemories;
|
Vector<VkDeviceMemory> m_depthStencilImageMemories;
|
||||||
Vector<VkImageView> m_depthStencilImageViews;
|
Vector<VkImageView> m_depthStencilImageViews;
|
||||||
Vector<VkImageLayout> m_depthStencilImageLayouts;
|
Vector<VkImageLayout> m_depthStencilImageLayouts;
|
||||||
|
Vector<Bool> m_imageContentDefined;
|
||||||
|
Vector<Bool> m_depthStencilContentDefined;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -13,9 +13,12 @@
|
|||||||
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||||
#include "MG_State/GLState/TextureState/TextureObject2D.h"
|
#include "MG_State/GLState/TextureState/TextureObject2D.h"
|
||||||
#include "MG_State/GLState/TextureState/TextureObjectBuffer.h"
|
#include "MG_State/GLState/TextureState/TextureObjectBuffer.h"
|
||||||
|
#include "MG_Util/Converters/GLToMG/TextureEnumConverter.h"
|
||||||
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
||||||
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
||||||
#include "MG_Util/Metrics/TextureMetrics.h"
|
#include "MG_Util/Metrics/TextureMetrics.h"
|
||||||
|
#include <Config.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -77,12 +80,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return uniformUnit >= 0 ? uniformUnit : 0;
|
return uniformUnit >= 0 ? uniformUnit : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool ShouldDumpDescriptorStats() {
|
VkFormat UniformManager::ResolveStorageImageViewFormat(VkFormat reflectedFormat, GLenum bindingFormat,
|
||||||
static const Bool enabled = [] {
|
VkFormat resourceFormat, Bool useBindingFormat) {
|
||||||
const char* value = std::getenv("MOBILEGL_DESCRIPTOR_STATS");
|
if (useBindingFormat) {
|
||||||
return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0;
|
const TextureInternalFormat bindingInternalFormat =
|
||||||
}();
|
MG_Util::ConvertGLEnumToTextureInternalFormat(bindingFormat);
|
||||||
return enabled;
|
return MG_Util::ConvertTextureInternalFormatToVkEnum(bindingInternalFormat);
|
||||||
|
}
|
||||||
|
return reflectedFormat != VK_FORMAT_UNDEFINED ? reflectedFormat : resourceFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager,
|
Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager,
|
||||||
@@ -110,6 +115,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
|
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
|
||||||
m_frameCount = frameCount;
|
m_frameCount = frameCount;
|
||||||
m_maxBindings = maxBindings;
|
m_maxBindings = maxBindings;
|
||||||
|
m_samplerResolveMemo.assign(m_maxBindings, SamplerResolveMemo{});
|
||||||
m_setsPerFrame = setsPerFrame;
|
m_setsPerFrame = setsPerFrame;
|
||||||
m_peakDescriptorSetsObserved = 0;
|
m_peakDescriptorSetsObserved = 0;
|
||||||
m_textureManager = textureManager;
|
m_textureManager = textureManager;
|
||||||
@@ -196,6 +202,49 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
for (auto& cacheEntryPair : frame.descriptorSetCacheByLayout) {
|
for (auto& cacheEntryPair : frame.descriptorSetCacheByLayout) {
|
||||||
cacheEntryPair.second.cursor = 0;
|
cacheEntryPair.second.cursor = 0;
|
||||||
}
|
}
|
||||||
|
// The frame's descriptor sets are recycled above, so last frame's reuse target
|
||||||
|
// is gone: start the per-draw descriptor-reuse cache fresh this frame.
|
||||||
|
m_hasLastDescriptor = false;
|
||||||
|
m_lastBindValid = false;
|
||||||
|
// Re-fingerprint the bound sampler set fresh this frame so any GL object address
|
||||||
|
// reuse cannot outlive a single frame (see SamplerResolveMemo).
|
||||||
|
for (auto& memo : m_samplerResolveMemo) {
|
||||||
|
memo.valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UniformManager::OnDescriptorSetLayoutDestroyed(VkDescriptorSetLayout descriptorSetLayout) {
|
||||||
|
SizeT purgedSets = 0;
|
||||||
|
for (auto& frame : m_frames) {
|
||||||
|
const auto it = frame.descriptorSetCacheByLayout.find(descriptorSetLayout);
|
||||||
|
if (it == frame.descriptorSetCacheByLayout.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Free the sets back to their pools and credit the bucket accounting, so
|
||||||
|
// program churn recycles pool capacity instead of abandoning the slots.
|
||||||
|
// GPU-safe: the layout only dies after >1024 idle frame boundaries, so no
|
||||||
|
// in-flight command buffer references these sets.
|
||||||
|
for (const auto& cached : it->second.sets) {
|
||||||
|
if (cached.set == VK_NULL_HANDLE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vkFreeDescriptorSets(m_device, cached.pool, 1, &cached.set);
|
||||||
|
const auto bucket = std::find_if(
|
||||||
|
frame.descriptorPools.begin(), frame.descriptorPools.end(),
|
||||||
|
[&cached](const DescriptorPoolBucket& candidate) { return candidate.handle == cached.pool; });
|
||||||
|
if (bucket != frame.descriptorPools.end() && bucket->allocatedSets > 0) {
|
||||||
|
--bucket->allocatedSets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
purgedSets += it->second.sets.size();
|
||||||
|
frame.descriptorSetCacheByLayout.erase(it);
|
||||||
|
}
|
||||||
|
if (purgedSets > 0) {
|
||||||
|
// The per-draw reuse memo folds the layout handle into its signature; drop
|
||||||
|
// it so a recycled handle value cannot revive a purged set mid-frame.
|
||||||
|
m_hasLastDescriptor = false;
|
||||||
|
MGLOG_D("UniformDescriptorBinder: freed %zu descriptor sets for destroyed layout", purgedSets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
||||||
@@ -206,24 +255,35 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
|
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
|
||||||
MOBILEGL_ASSERT(binding < programObj.samplerNameByBinding.size(),
|
MOBILEGL_ASSERT(binding < programObj.samplerNameByBinding.size(),
|
||||||
"ResolveSamplerDescriptor: sampler binding %u name lookup out of range", binding);
|
"ResolveSamplerDescriptor: sampler binding %u name lookup out of range", binding);
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> texture;
|
// Raw-pointer resolve to skip the SharedPtr atomic refcount churn: the bound texture stays
|
||||||
const Bool resolvedTexture = ResolveSamplerTexture(program, programObj, binding, texture);
|
// alive through the draw via GL binding state. Only the fallback path needs a SharedPtr to
|
||||||
if (!resolvedTexture) {
|
// keep the fallback texture alive for the rest of this call.
|
||||||
MGLOG_E("ResolveSamplerDescriptor: failed to resolve sampler texture for binding %u ('%s')", binding,
|
MG_State::GLState::ITextureObject* texture = ResolveSamplerTextureRaw(program, programObj, binding);
|
||||||
programObj.samplerNameByBinding[binding].c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||||
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||||
const auto samplerOverride = textureUnit.GetSamplerObject();
|
const auto& samplerOverride = textureUnit.GetSamplerObject();
|
||||||
const auto preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
const auto preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
||||||
|
SharedPtr<MG_State::GLState::ITextureObject> fallbackHolder;
|
||||||
|
// A texture that fails the completeness rules for the filter in effect reads
|
||||||
|
// (0, 0, 0, 1), which is exactly what the fallback texture holds - so it takes the
|
||||||
|
// same route as a sampler with nothing bound.
|
||||||
|
if (texture != nullptr &&
|
||||||
|
MG_State::GLState::SamplesAsIncompleteTexture(
|
||||||
|
texture, samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get())) {
|
||||||
|
texture = nullptr;
|
||||||
|
}
|
||||||
if (texture == nullptr) {
|
if (texture == nullptr) {
|
||||||
texture = GetFallbackTexture(preferredTarget);
|
fallbackHolder = GetFallbackTexture(preferredTarget);
|
||||||
MOBILEGL_ASSERT(texture != nullptr,
|
texture = fallbackHolder.get();
|
||||||
"ResolveSamplerDescriptor: no fallback texture available for binding=%u location=%d unit=%d target=%d",
|
if (texture == nullptr) {
|
||||||
binding, location, unit, static_cast<Int>(preferredTarget));
|
MGLOG_E("ResolveSamplerDescriptor: no fallback texture available for binding=%u ('%s') "
|
||||||
|
"location=%d unit=%d target=%d",
|
||||||
|
binding, programObj.samplerNameByBinding[binding].c_str(), location, unit,
|
||||||
|
static_cast<Int>(preferredTarget));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
MGLOG_W(
|
MGLOG_W(
|
||||||
"ResolveSamplerDescriptor: using fallback texture for unbound sampler binding=%u ('%s') location=%d unit=%d target=%d",
|
"ResolveSamplerDescriptor: using fallback texture for unbound sampler binding=%u ('%s') location=%d unit=%d target=%d",
|
||||||
binding, programObj.samplerNameByBinding[binding].c_str(), location, unit,
|
binding, programObj.samplerNameByBinding[binding].c_str(), location, unit,
|
||||||
@@ -275,26 +335,97 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
"ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u",
|
"ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u",
|
||||||
static_cast<Int>(resource->layout), texture->GetExternalIndex(), binding);
|
static_cast<Int>(resource->layout), texture->GetExternalIndex(), binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MOBILEGL_ASSERT(binding < programObj.samplerNumericDomainByBinding.size(),
|
||||||
|
"ResolveSamplerDescriptor: sampler numeric-domain binding %u out of range", binding);
|
||||||
|
const SamplerNumericDomain numericDomain = programObj.samplerNumericDomainByBinding[binding];
|
||||||
|
// Vulkan forbids linear filtering and anisotropy for integer sampled-image formats.
|
||||||
|
// Some desktop GL shader packs deliberately bit-read a mutable float texture through a
|
||||||
|
// usampler and still leave the texture's ordinary linear parameters in place; texelFetch
|
||||||
|
// ignores filtering, so a nearest VkSampler preserves the operation while keeping the
|
||||||
|
// descriptor valid.
|
||||||
|
const Bool forceNearestFiltering = numericDomain == SamplerNumericDomain::SignedInteger ||
|
||||||
|
numericDomain == SamplerNumericDomain::UnsignedInteger;
|
||||||
|
SamplerResolveMemo* viewFormatMemo =
|
||||||
|
binding < m_samplerResolveMemo.size() ? &m_samplerResolveMemo[binding] : nullptr;
|
||||||
|
VkFormat sampledViewFormat;
|
||||||
|
if (viewFormatMemo != nullptr && viewFormatMemo->viewFormatValid &&
|
||||||
|
viewFormatMemo->viewFormatSource == resource->format &&
|
||||||
|
viewFormatMemo->viewFormatDomain == numericDomain) {
|
||||||
|
sampledViewFormat = viewFormatMemo->viewFormat;
|
||||||
|
} else {
|
||||||
|
sampledViewFormat =
|
||||||
|
VkTextureManager::ResolveSampledImageViewFormat(resource->format, numericDomain);
|
||||||
|
if (viewFormatMemo != nullptr) {
|
||||||
|
viewFormatMemo->viewFormatSource = resource->format;
|
||||||
|
viewFormatMemo->viewFormatDomain = numericDomain;
|
||||||
|
viewFormatMemo->viewFormat = sampledViewFormat;
|
||||||
|
viewFormatMemo->viewFormatValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sampledViewFormat == VK_FORMAT_UNDEFINED) {
|
||||||
|
MGLOG_E("ResolveSamplerDescriptor: no compatible sampled view for binding=%u ('%s') "
|
||||||
|
"textureId=%d imageFormat=%d numericDomain=%d",
|
||||||
|
binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(),
|
||||||
|
static_cast<Int>(resource->format), static_cast<Int>(numericDomain));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// No reinterpretation requested: bind the depth-or-color aspect view the sync above
|
||||||
|
// already produced instead of re-entering GetOrCreateSampledImageView's sync path.
|
||||||
|
const VkImageView sampledImageView =
|
||||||
|
sampledViewFormat == resource->format
|
||||||
|
? resource->sampledView
|
||||||
|
: m_textureManager->GetOrCreateSampledImageView(*texture, sampledViewFormat);
|
||||||
|
if (sampledImageView == VK_NULL_HANDLE) {
|
||||||
|
MGLOG_E("ResolveSamplerDescriptor: failed to resolve sampled view for binding=%u ('%s') "
|
||||||
|
"textureId=%d imageFormat=%d viewFormat=%d numericDomain=%d",
|
||||||
|
binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(),
|
||||||
|
static_cast<Int>(resource->format), static_cast<Int>(sampledViewFormat),
|
||||||
|
static_cast<Int>(numericDomain));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Skip GetOrCreateSampler's per-draw key hash + map lookup when this binding's
|
||||||
|
// sampler object and texture (both by lifetime id + version) are unchanged from the
|
||||||
|
// last draw that resolved it: the resulting sampler key, and therefore the VkSampler
|
||||||
|
// handle, are guaranteed identical. Lifetime ids are never reused, so a freed-and-
|
||||||
|
// reallocated sampler/texture at the same address gets a fresh id and misses instead
|
||||||
|
// of false-hitting. Cached handles live until Shutdown, so the memo can never hand
|
||||||
|
// back a destroyed sampler.
|
||||||
|
VkSampler resolvedSampler = VK_NULL_HANDLE;
|
||||||
|
if (binding < m_samplerResolveMemo.size()) {
|
||||||
|
auto& memo = m_samplerResolveMemo[binding];
|
||||||
|
const Uint64 samplerLifetimeId = samplerToUse->GetLifetimeId();
|
||||||
|
const Uint16 samplerVersion = samplerToUse->GetVersion();
|
||||||
|
const Uint64 textureLifetimeId = texture->GetLifetimeId();
|
||||||
|
const Uint16 textureParamsVersion = texture->GetTextureParamsVersion();
|
||||||
|
// The sampler's LOD clamp depends on how many levels the sampled view exposes, and that
|
||||||
|
// follows uploads as well as GL parameters - so it belongs in the memo key too.
|
||||||
|
const Uint32 viewLevelCount = resource->sampledLevelCount;
|
||||||
|
if (memo.valid && memo.samplerLifetimeId == samplerLifetimeId && memo.samplerVersion == samplerVersion &&
|
||||||
|
memo.textureLifetimeId == textureLifetimeId && memo.textureParamsVersion == textureParamsVersion &&
|
||||||
|
memo.forceNearestFiltering == forceNearestFiltering && memo.viewLevelCount == viewLevelCount) {
|
||||||
|
resolvedSampler = memo.sampler;
|
||||||
|
} else {
|
||||||
|
resolvedSampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture,
|
||||||
|
forceNearestFiltering, viewLevelCount);
|
||||||
|
memo.samplerLifetimeId = samplerLifetimeId;
|
||||||
|
memo.samplerVersion = samplerVersion;
|
||||||
|
memo.textureLifetimeId = textureLifetimeId;
|
||||||
|
memo.textureParamsVersion = textureParamsVersion;
|
||||||
|
memo.forceNearestFiltering = forceNearestFiltering;
|
||||||
|
memo.viewLevelCount = viewLevelCount;
|
||||||
|
memo.sampler = resolvedSampler;
|
||||||
|
memo.valid = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolvedSampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture, forceNearestFiltering,
|
||||||
|
resource->sampledLevelCount);
|
||||||
|
}
|
||||||
outImageInfo = {
|
outImageInfo = {
|
||||||
.sampler = m_samplerManager->GetOrCreateSampler(*samplerToUse, *texture),
|
.sampler = resolvedSampler,
|
||||||
.imageView = resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView,
|
.imageView = sampledImageView,
|
||||||
.imageLayout = resource->layout,
|
.imageLayout = resource->layout,
|
||||||
};
|
};
|
||||||
if (ShouldDumpDescriptorStats()) {
|
|
||||||
std::fprintf(stderr,
|
|
||||||
"MOBILEGL_DESCRIPTOR_STATS program=%u binding=%u name=%s location=%d unit=%d "
|
|
||||||
"texture=%u target=%d sampler=%p imageView=%p layout=%d\n",
|
|
||||||
program.GetExternalIndex(),
|
|
||||||
binding,
|
|
||||||
programObj.samplerNameByBinding[binding].c_str(),
|
|
||||||
location,
|
|
||||||
unit,
|
|
||||||
texture->GetExternalIndex(),
|
|
||||||
static_cast<Int>(texture->GetTarget()),
|
|
||||||
reinterpret_cast<void*>(outImageInfo.sampler),
|
|
||||||
reinterpret_cast<void*>(outImageInfo.imageView),
|
|
||||||
static_cast<Int>(outImageInfo.imageLayout));
|
|
||||||
}
|
|
||||||
return outImageInfo.sampler != VK_NULL_HANDLE;
|
return outImageInfo.sampler != VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +460,48 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return outImageInfo.sampler != VK_NULL_HANDLE;
|
return outImageInfo.sampler != VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool UniformManager::ProgramSamplesOnlySingleLevelTextures(
|
||||||
|
const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj) {
|
||||||
|
Bool sawSampler = false;
|
||||||
|
for (Uint32 binding = 0; binding < programObj.bindingKinds.size(); ++binding) {
|
||||||
|
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto* texture = ResolveSamplerTextureRaw(program, programObj, binding);
|
||||||
|
if (texture == nullptr) return false;
|
||||||
|
const auto& levelRange = texture->GetLevelRange();
|
||||||
|
if (levelRange.x() != levelRange.y()) return false;
|
||||||
|
|
||||||
|
// An explicit-LOD sample is a single filtered tap, so it also gives up anisotropic
|
||||||
|
// filtering - which a single-level view can still have. Resolve the sampler exactly
|
||||||
|
// the way ResolveSamplerDescriptor does and bail if anisotropy would apply.
|
||||||
|
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||||
|
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
||||||
|
const auto& samplerOverride = MG_State::pGLContext->GetTextureUnitObject(unit).GetSamplerObject();
|
||||||
|
const auto* effectiveSampler =
|
||||||
|
samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get();
|
||||||
|
if (effectiveSampler == nullptr) return false;
|
||||||
|
if (effectiveSampler->GetMaxAnisotropy() > 1.0f &&
|
||||||
|
effectiveSampler->GetMinFilter() == SamplerFilterMode::Linear &&
|
||||||
|
effectiveSampler->GetMagFilter() == SamplerFilterMode::Linear) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An explicit LOD 0 makes lambda exactly 0, which is the magnification side of the
|
||||||
|
// min/mag decision. That only matches the implicit form when lambda could not have been
|
||||||
|
// positive anyway (the LOD clamp already pins it at or below 0), or when the two
|
||||||
|
// filters are the same and the choice cannot be observed.
|
||||||
|
const Float effectiveMaxLod = effectiveSampler->GetMipmapMode() == SamplerMipmapMode::None
|
||||||
|
? 0.0f
|
||||||
|
: effectiveSampler->GetMaxLod();
|
||||||
|
if (effectiveMaxLod > 0.0f && effectiveSampler->GetMinFilter() != effectiveSampler->GetMagFilter()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sawSampler = true;
|
||||||
|
}
|
||||||
|
return sawSampler;
|
||||||
|
}
|
||||||
|
|
||||||
Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||||
@@ -345,10 +518,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||||
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
||||||
outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject();
|
outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject();
|
||||||
|
// The slot always holds at least the target's default texture (name 0). While that
|
||||||
|
// default has no image it is unsampleable; report it as "unbound" so callers keep
|
||||||
|
// taking their fallback paths instead of trying to sync a storage-less texture.
|
||||||
|
if (MG_State::GLState::IsUndefinedDefaultTexture(outTexture.get())) {
|
||||||
|
outTexture.reset();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MG_State::GLState::ITextureObject* UniformManager::ResolveSamplerTextureRaw(
|
||||||
|
const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj,
|
||||||
|
Uint32 binding) {
|
||||||
|
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveSamplerTextureRaw: GL context is null");
|
||||||
|
MOBILEGL_ASSERT(binding < programObj.samplerUniformLocationByBinding.size(),
|
||||||
|
"ResolveSamplerTextureRaw: sampler location binding %u out of range", binding);
|
||||||
|
MOBILEGL_ASSERT(binding < programObj.samplerTextureTargetByBinding.size(),
|
||||||
|
"ResolveSamplerTextureRaw: sampler target binding %u out of range", binding);
|
||||||
|
|
||||||
|
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||||
|
const Int unit = ResolveSamplerUnitIndex(program, location, binding);
|
||||||
|
|
||||||
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||||
|
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
||||||
|
// GetBoundObject() returns the SharedPtr by const ref; .get() reads the pointer without
|
||||||
|
// touching the refcount (no atomic inc/dec per binding per draw).
|
||||||
|
MG_State::GLState::ITextureObject* texture =
|
||||||
|
textureUnit.GetBindingSlot(preferredTarget).GetBoundObject().get();
|
||||||
|
// The slot always holds at least the target's default texture (name 0). While that
|
||||||
|
// default has no image it is unsampleable; report it as "unbound" so the caller
|
||||||
|
// substitutes its fallback texture exactly like it did for the old null slot.
|
||||||
|
if (MG_State::GLState::IsUndefinedDefaultTexture(texture)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
Bool UniformManager::ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
Bool UniformManager::ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj,
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
Uint32 binding, Uint32 frameIndex,
|
Uint32 binding, Uint32 frameIndex,
|
||||||
@@ -374,7 +580,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto* textureBuffer = static_cast<MG_State::GLState::TextureObjectBuffer*>(texture.get());
|
auto* textureBuffer = static_cast<MG_State::GLState::TextureObjectBuffer*>(texture.get());
|
||||||
const auto bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject();
|
const auto& bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject();
|
||||||
if (bufferObject == nullptr) {
|
if (bufferObject == nullptr) {
|
||||||
MGLOG_E("ResolveTexelBufferDescriptor: texture buffer binding %u ('%s') has no GL buffer bound",
|
MGLOG_E("ResolveTexelBufferDescriptor: texture buffer binding %u ('%s') has no GL buffer bound",
|
||||||
binding, programObj.samplerNameByBinding[binding].c_str());
|
binding, programObj.samplerNameByBinding[binding].c_str());
|
||||||
@@ -382,7 +588,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BufferSlice slice{};
|
BufferSlice slice{};
|
||||||
if (!m_bufferManager->SyncResidentBuffer(BufferKind::TextureBuffer, bufferObject, slice) || !slice.IsValid()) {
|
if (!m_bufferManager->AcquireResidentSlice(BufferKind::TextureBuffer, bufferObject, slice) || !slice.IsValid()) {
|
||||||
MGLOG_E("ResolveTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u",
|
MGLOG_E("ResolveTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u",
|
||||||
bufferObject->GetExternalIndex(), texture->GetExternalIndex());
|
bufferObject->GetExternalIndex(), texture->GetExternalIndex());
|
||||||
return false;
|
return false;
|
||||||
@@ -398,7 +604,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
const VkDeviceSize texelSize =
|
const VkDeviceSize texelSize =
|
||||||
static_cast<VkDeviceSize>(MG_Util::GetSizedInternalFormatSizeInBytes(internalFormat));
|
static_cast<VkDeviceSize>(MG_Util::GetSizedInternalFormatSizeInBytes(internalFormat));
|
||||||
VkDeviceSize viewRange = slice.size;
|
// glTextureBufferRange addresses a window of the buffer, not all of it; the whole-buffer
|
||||||
|
// forms report the buffer's current size here, so both go through the same clamp.
|
||||||
|
const VkDeviceSize rangeOffset = static_cast<VkDeviceSize>(textureBuffer->GetBufferRangeOffset());
|
||||||
|
const VkDeviceSize rangeSize = static_cast<VkDeviceSize>(textureBuffer->GetBufferRangeSizeInBytes());
|
||||||
|
VkDeviceSize viewRange = std::min(rangeSize, slice.size > rangeOffset ? slice.size - rangeOffset : 0);
|
||||||
if (texelSize > 0) {
|
if (texelSize > 0) {
|
||||||
viewRange = (viewRange / texelSize) * texelSize;
|
viewRange = (viewRange / texelSize) * texelSize;
|
||||||
}
|
}
|
||||||
@@ -411,7 +621,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
viewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
|
viewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
|
||||||
viewInfo.buffer = slice.buffer;
|
viewInfo.buffer = slice.buffer;
|
||||||
viewInfo.format = vkFormat;
|
viewInfo.format = vkFormat;
|
||||||
viewInfo.offset = slice.offset;
|
viewInfo.offset = slice.offset + rangeOffset;
|
||||||
viewInfo.range = viewRange;
|
viewInfo.range = viewRange;
|
||||||
|
|
||||||
VkBufferView bufferView = VK_NULL_HANDLE;
|
VkBufferView bufferView = VK_NULL_HANDLE;
|
||||||
@@ -449,15 +659,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frontendBinding, programObj.storageBlockNameByBinding[binding].c_str());
|
frontendBinding, programObj.storageBlockNameByBinding[binding].c_str());
|
||||||
|
|
||||||
auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, frontendBinding);
|
auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, frontendBinding);
|
||||||
const auto bufferObject = bindingPoint.GetBoundObject();
|
const auto& bufferObject = bindingPoint.GetBoundObject();
|
||||||
if (bufferObject == nullptr) {
|
if (bufferObject == nullptr) {
|
||||||
MGLOG_E("ResolveStorageBufferDescriptor: no SSBO bound at frontend binding %u for block '%s'",
|
MGLOG_E("ResolveStorageBufferDescriptor: no SSBO bound at frontend binding %u for block '%s'",
|
||||||
frontendBinding, programObj.storageBlockNameByBinding[binding].c_str());
|
frontendBinding, programObj.storageBlockNameByBinding[binding].c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The shader may write this buffer, and those writes land in GPU memory behind the
|
||||||
|
// frontend's CPU shadow - which is what MapBuffer and GetBufferSubData read.
|
||||||
|
// Host-visible coherent GPU residency makes the shadow BE that memory, so the
|
||||||
|
// results are visible without a readback path, exactly as for a capture buffer.
|
||||||
|
bufferObject->EnsureGpuResidentStorage();
|
||||||
|
// ... and the read that follows has to wait for this draw or dispatch to retire.
|
||||||
|
bufferObject->MarkGpuWritten();
|
||||||
|
|
||||||
BufferSlice slice{};
|
BufferSlice slice{};
|
||||||
if (!m_bufferManager->SyncResidentBuffer(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) {
|
if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) {
|
||||||
MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'",
|
MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'",
|
||||||
bufferObject->GetExternalIndex(), programObj.storageBlockNameByBinding[binding].c_str());
|
bufferObject->GetExternalIndex(), programObj.storageBlockNameByBinding[binding].c_str());
|
||||||
return false;
|
return false;
|
||||||
@@ -524,9 +742,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Uint32 mipLevel = static_cast<Uint32>(std::max<GLint>(0, imageBinding.Level));
|
const Uint32 mipLevel = static_cast<Uint32>(std::max<GLint>(0, imageBinding.Level));
|
||||||
VkImageView view = m_textureManager->GetOrCreateViewAtMipLevel(*imageBinding.Texture, mipLevel);
|
MOBILEGL_ASSERT(binding < programObj.storageImageFormatByBinding.size(),
|
||||||
|
"ResolveStorageImageDescriptor: storage image format binding %u out of range", binding);
|
||||||
|
MOBILEGL_ASSERT(binding < programObj.storageImageUsesBindingFormatByBinding.size(),
|
||||||
|
"ResolveStorageImageDescriptor: storage image format policy binding %u out of range",
|
||||||
|
binding);
|
||||||
|
const VkFormat reflectedFormat = programObj.storageImageFormatByBinding[binding];
|
||||||
|
const Bool useBindingFormat = programObj.storageImageUsesBindingFormatByBinding[binding];
|
||||||
|
const VkFormat viewFormat = ResolveStorageImageViewFormat(
|
||||||
|
reflectedFormat, imageBinding.Format, resource->format, useBindingFormat);
|
||||||
|
if (viewFormat == VK_FORMAT_UNDEFINED) {
|
||||||
|
MGLOG_E("ResolveStorageImageDescriptor: unsupported glBindImageTexture format=0x%x "
|
||||||
|
"for binding=%u imageUnit=%d textureId=%d bindingPolicy=%s",
|
||||||
|
imageBinding.Format, binding, imageUnit, imageBinding.Texture->GetExternalIndex(),
|
||||||
|
useBindingFormat ? "true" : "false");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const VkImageView view = m_textureManager->GetOrCreateStorageImageView(
|
||||||
|
*imageBinding.Texture, mipLevel, viewFormat, imageBinding.Layered != GL_FALSE, imageBinding.Layer);
|
||||||
if (view == VK_NULL_HANDLE) {
|
if (view == VK_NULL_HANDLE) {
|
||||||
view = resource->fullView;
|
MGLOG_E("ResolveStorageImageDescriptor: failed to resolve storage view textureId=%d mip=%u "
|
||||||
|
"bindingFormat=0x%x imageFormat=%d reflectedFormat=%d selectedFormat=%d bindingPolicy=%s",
|
||||||
|
imageBinding.Texture->GetExternalIndex(), mipLevel, imageBinding.Format,
|
||||||
|
static_cast<Int>(resource->format), static_cast<Int>(reflectedFormat),
|
||||||
|
static_cast<Int>(viewFormat),
|
||||||
|
useBindingFormat ? "true" : "false");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
outImageInfo.sampler = VK_NULL_HANDLE;
|
outImageInfo.sampler = VK_NULL_HANDLE;
|
||||||
outImageInfo.imageView = view;
|
outImageInfo.imageView = view;
|
||||||
@@ -535,15 +776,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> UniformManager::GetFallbackTexture(TextureTarget target) const {
|
SharedPtr<MG_State::GLState::ITextureObject> UniformManager::GetFallbackTexture(TextureTarget target) const {
|
||||||
MOBILEGL_ASSERT(target == TextureTarget::Texture2D || target == TextureTarget::TextureRectangle,
|
// The fallback is a single-sampled 2D image, so it can only stand in for a sampler that
|
||||||
"UniformManager::GetFallbackTexture: unsupported fallback target=%d",
|
// would accept one. A multisample sampler in particular cannot: its descriptor demands a
|
||||||
static_cast<Int>(target));
|
// multisample view, and handing it this one is invalid Vulkan, not a degraded picture.
|
||||||
|
// Report that there is no fallback and let the caller decline the draw - aborting the
|
||||||
|
// process over an unbound sampler is never the right answer.
|
||||||
|
if (target != TextureTarget::Texture2D && target != TextureTarget::TextureRectangle) {
|
||||||
|
MGLOG_E("UniformManager::GetFallbackTexture: no fallback exists for target=%d",
|
||||||
|
static_cast<Int>(target));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_fallbackTexture2D == nullptr) {
|
if (m_fallbackTexture2D == nullptr) {
|
||||||
auto fallbackTexture = MakeShared<MG_State::GLState::TextureObject2D>(kFallbackTexture2DExternalIndex);
|
auto fallbackTexture = MakeShared<MG_State::GLState::TextureObject2D>(kFallbackTexture2DExternalIndex);
|
||||||
fallbackTexture->SetInternalFormat(TextureInternalFormat::RGBA8);
|
fallbackTexture->SetInternalFormat(TextureInternalFormat::RGBA8);
|
||||||
fallbackTexture->AllocateStorage(TextureUploadTarget::Texture2D, 0,
|
fallbackTexture->AllocateStorage(TextureUploadTarget::Texture2D, 0,
|
||||||
{.texelSize = {1, 1, 1}, .byteSize = 4});
|
{.texelSize = {1, 1, 1}, .byteSize = 4});
|
||||||
|
// (0, 0, 0, 1): what GL reads from a texture that is not complete, and the only
|
||||||
|
// sensible answer for a sampler with nothing bound.
|
||||||
|
static Uint8 kOpaqueBlackTexel[4] = {0, 0, 0, 255};
|
||||||
|
fallbackTexture->UpdateMipmapSubData(TextureUploadTarget::Texture2D, 0,
|
||||||
|
{kOpaqueBlackTexel, sizeof(kOpaqueBlackTexel)});
|
||||||
fallbackTexture->MarkStorageDirty(TextureUploadTarget::Texture2D, 0, true);
|
fallbackTexture->MarkStorageDirty(TextureUploadTarget::Texture2D, 0, true);
|
||||||
m_fallbackTexture2D = fallbackTexture;
|
m_fallbackTexture2D = fallbackTexture;
|
||||||
}
|
}
|
||||||
@@ -563,14 +816,66 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> texture;
|
MG_State::GLState::ITextureObject* texture = ResolveSamplerTextureRaw(program, programObj, binding);
|
||||||
if (!ResolveSamplerTexture(program, programObj, binding, texture) || !texture) {
|
if (!texture) {
|
||||||
continue;
|
// ResolveSamplerDescriptor will substitute the fallback texture for this binding;
|
||||||
|
// include it in the sampled set so the pre-render-pass sync/transition pass covers
|
||||||
|
// its first use instead of leaving that work to happen inside an active pass.
|
||||||
|
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
|
||||||
|
if (preferredTarget != TextureTarget::Texture2D &&
|
||||||
|
preferredTarget != TextureTarget::TextureRectangle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
texture = GetFallbackTexture(preferredTarget).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto found = std::find(outTextures.begin(), outTextures.end(), texture.get());
|
auto found = std::find(outTextures.begin(), outTextures.end(), texture);
|
||||||
if (found == outTextures.end()) {
|
if (found == outTextures.end()) {
|
||||||
outTextures.push_back(texture.get());
|
outTextures.push_back(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool UniformManager::CollectStorageImageTextures(
|
||||||
|
const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
|
Vector<MG_State::GLState::ITextureObject*>& outTextures) const {
|
||||||
|
outTextures.clear();
|
||||||
|
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr,
|
||||||
|
"CollectStorageImageTextures: GL context is null");
|
||||||
|
|
||||||
|
const Uint32 bindingCount =
|
||||||
|
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
|
||||||
|
for (Uint32 binding = 0; binding < bindingCount; ++binding) {
|
||||||
|
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::StorageImage) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (binding >= programObj.samplerUniformLocationByBinding.size()) {
|
||||||
|
MGLOG_E("CollectStorageImageTextures: binding %u has no uniform-location mapping", binding);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||||
|
if (location < 0) {
|
||||||
|
MGLOG_E("CollectStorageImageTextures: binding %u has no image uniform location", binding);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Int imageUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||||
|
if (imageUnit < 0 || imageUnit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) {
|
||||||
|
MGLOG_E("CollectStorageImageTextures: image unit %d is invalid for binding %u",
|
||||||
|
imageUnit, binding);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* texture = MG_State::pGLContext->GetImageTextureBinding(imageUnit).Texture.get();
|
||||||
|
if (texture == nullptr) {
|
||||||
|
MGLOG_E("CollectStorageImageTextures: image unit %d is unbound for binding %u",
|
||||||
|
imageUnit, binding);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std::find(outTextures.begin(), outTextures.end(), texture) == outTextures.end()) {
|
||||||
|
outTextures.push_back(texture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -578,9 +883,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
Bool UniformManager::ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
Bool UniformManager::ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
const void*& outData, VkDeviceSize& outSize) const {
|
Uint32 arrayElement, UboBindResult& out) const {
|
||||||
outData = nullptr;
|
const void* outData = nullptr;
|
||||||
outSize = 0;
|
VkDeviceSize outSize = 0;
|
||||||
|
|
||||||
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveUniformBufferPayload: GL context is null");
|
MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveUniformBufferPayload: GL context is null");
|
||||||
MOBILEGL_ASSERT(binding < programObj.bindingKinds.size(),
|
MOBILEGL_ASSERT(binding < programObj.bindingKinds.size(),
|
||||||
@@ -596,12 +901,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
outData = emptyGlobalUbo.data();
|
outData = emptyGlobalUbo.data();
|
||||||
outSize = static_cast<VkDeviceSize>(emptyGlobalUbo.size());
|
outSize = static_cast<VkDeviceSize>(emptyGlobalUbo.size());
|
||||||
}
|
}
|
||||||
|
// The global UBO is CPU uniform data, not an app buffer -> always UploadTransient.
|
||||||
|
out.payload = outData;
|
||||||
|
out.payloadSize = outSize;
|
||||||
return outData != nullptr && outSize > 0;
|
return outData != nullptr && outSize > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
MOBILEGL_ASSERT(binding < programObj.uniformBlockIndexByBinding.size(),
|
MOBILEGL_ASSERT(binding < programObj.uniformBlockIndexByBinding.size(),
|
||||||
"ResolveUniformBufferPayload: UBO mapping binding %u out of range", binding);
|
"ResolveUniformBufferPayload: UBO mapping binding %u out of range", binding);
|
||||||
const Int blockIndex = programObj.uniformBlockIndexByBinding[binding];
|
Int blockIndex = programObj.uniformBlockIndexByBinding[binding];
|
||||||
|
if (arrayElement > 0) {
|
||||||
|
const auto arrayIt = programObj.arrayedUniformBlockIndicesByBinding.find(binding);
|
||||||
|
const Bool elementValid = arrayIt != programObj.arrayedUniformBlockIndicesByBinding.end() &&
|
||||||
|
arrayElement < arrayIt->second.size();
|
||||||
|
MOBILEGL_ASSERT(elementValid,
|
||||||
|
"ResolveUniformBufferPayload: UBO binding %u has no array element %u", binding,
|
||||||
|
arrayElement);
|
||||||
|
if (!elementValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
blockIndex = arrayIt->second[arrayElement];
|
||||||
|
}
|
||||||
MOBILEGL_ASSERT(blockIndex >= 0,
|
MOBILEGL_ASSERT(blockIndex >= 0,
|
||||||
"ResolveUniformBufferPayload: no uniform block mapped to descriptor binding %u", binding);
|
"ResolveUniformBufferPayload: no uniform block mapped to descriptor binding %u", binding);
|
||||||
|
|
||||||
@@ -618,14 +938,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frontendBinding, program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
frontendBinding, program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
||||||
|
|
||||||
auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, frontendBinding);
|
auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, frontendBinding);
|
||||||
const auto bufferObject = bindingPoint.GetBoundObject();
|
const auto& bufferObject = bindingPoint.GetBoundObject();
|
||||||
MOBILEGL_ASSERT(bufferObject != nullptr,
|
MOBILEGL_ASSERT(bufferObject != nullptr,
|
||||||
"ResolveUniformBufferPayload: no UBO bound at frontend binding %u for block '%s'",
|
"ResolveUniformBufferPayload: no UBO bound at frontend binding %u for block '%s'",
|
||||||
frontendBinding, program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
frontendBinding, program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
||||||
bufferObject->MarkPersistentMappedRangeDirty();
|
bufferObject->SyncPersistentMappedRange();
|
||||||
|
|
||||||
const auto bufferData = bufferObject->GetDataReadOnly();
|
MOBILEGL_ASSERT(bufferObject->MappedData() != nullptr && bufferObject->GetSize() != 0,
|
||||||
MOBILEGL_ASSERT(bufferData != nullptr && !bufferData->empty(),
|
|
||||||
"ResolveUniformBufferPayload: bound UBO data is empty for block '%s'",
|
"ResolveUniformBufferPayload: bound UBO data is empty for block '%s'",
|
||||||
program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
||||||
|
|
||||||
@@ -652,13 +971,34 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
||||||
|
|
||||||
const VkDeviceSize available = rangeEnd - rangeStart;
|
const VkDeviceSize available = rangeEnd - rangeStart;
|
||||||
MOBILEGL_ASSERT(available >= blockSize,
|
|
||||||
"ResolveUniformBufferPayload: bound range %zu is smaller than UBO size %zu for block '%s'",
|
|
||||||
static_cast<SizeT>(available), static_cast<SizeT>(blockSize),
|
|
||||||
program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
|
|
||||||
|
|
||||||
outData = bufferData->data() + static_cast<SizeT>(rangeStart);
|
|
||||||
outSize = blockSize;
|
outSize = blockSize;
|
||||||
|
outData = bufferObject->MappedData() + static_cast<SizeT>(rangeStart);
|
||||||
|
if (available < blockSize) {
|
||||||
|
static thread_local Vector<Uint8> paddedUbo;
|
||||||
|
paddedUbo.assign(static_cast<SizeT>(blockSize), 0);
|
||||||
|
Memcpy(paddedUbo.data(), outData, static_cast<SizeT>(available));
|
||||||
|
outData = paddedUbo.data();
|
||||||
|
}
|
||||||
|
out.payload = outData;
|
||||||
|
out.payloadSize = outSize;
|
||||||
|
|
||||||
|
// Zero-copy direct bind: for a persistent-mapped coherent app buffer whose full reflected
|
||||||
|
// block fits within the aligned bound range, point the descriptor straight at the app's
|
||||||
|
// resident VkBuffer (the same buffer the GLES backend binds) with the block range as the
|
||||||
|
// dynamic offset - no per-draw copy into a transient ring. Any gate failing keeps the
|
||||||
|
// UploadTransient payload above. AcquireResidentSlice does the persistent busy-tracking and
|
||||||
|
// (for the persistent case) hits a zero-work fast path returning the whole-buffer slice.
|
||||||
|
if (bufferObject->IsBackendPersistentMapped() && available >= blockSize &&
|
||||||
|
(rangeStart % m_minDynamicOffsetAlignment) == 0) {
|
||||||
|
BufferSlice slice{};
|
||||||
|
if (m_bufferManager->AcquireResidentSlice(BufferKind::Uniform, bufferObject, slice) &&
|
||||||
|
slice.IsValid() && slice.offset == 0 && slice.size >= rangeStart + blockSize) {
|
||||||
|
out.directBindable = true;
|
||||||
|
out.buffer = slice.buffer;
|
||||||
|
out.range = blockSize;
|
||||||
|
out.dynamicOffset = rangeStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,6 +1029,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
VkDescriptorPoolCreateInfo poolInfo{};
|
VkDescriptorPoolCreateInfo poolInfo{};
|
||||||
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||||
|
// FREE_DESCRIPTOR_SET_BIT lets a destroyed layout's cached sets be freed back
|
||||||
|
// (OnDescriptorSetLayoutDestroyed) so program churn recycles pool capacity.
|
||||||
|
// The cost is on set allocation only, which happens when a layout's per-frame
|
||||||
|
// cache grows - never on the per-draw reuse path.
|
||||||
|
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
||||||
poolInfo.maxSets = maxSets;
|
poolInfo.maxSets = maxSets;
|
||||||
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
|
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
|
||||||
poolInfo.pPoolSizes = poolSizes;
|
poolInfo.pPoolSizes = poolSizes;
|
||||||
@@ -768,7 +1113,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto& frame = m_frames[frameIndex];
|
auto& frame = m_frames[frameIndex];
|
||||||
auto& cache = frame.descriptorSetCacheByLayout[programObj.descriptorSetLayout];
|
auto& cache = frame.descriptorSetCacheByLayout[programObj.descriptorSetLayout];
|
||||||
if (cache.cursor < cache.sets.size()) {
|
if (cache.cursor < cache.sets.size()) {
|
||||||
outDescriptorSet = cache.sets[cache.cursor++];
|
outDescriptorSet = cache.sets[cache.cursor++].set;
|
||||||
} else {
|
} else {
|
||||||
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
|
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
|
||||||
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
|
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
|
||||||
@@ -782,7 +1127,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return allocResult;
|
return allocResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.sets.push_back(outDescriptorSet);
|
// The successful allocation came from the bucket the alloc helper left
|
||||||
|
// active; record it so a layout-destroyed purge can free the set back.
|
||||||
|
cache.sets.push_back({outDescriptorSet, frame.descriptorPools[frame.activeDescriptorPoolIndex].handle});
|
||||||
++cache.cursor;
|
++cache.cursor;
|
||||||
MGLOG_D("UniformDescriptorBinder: cached descriptor set count for frame=%u grew to %zu", frameIndex,
|
MGLOG_D("UniformDescriptorBinder: cached descriptor set count for frame=%u grew to %zu", frameIndex,
|
||||||
cache.sets.size());
|
cache.sets.size());
|
||||||
@@ -809,28 +1156,36 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frame.activeDescriptorPoolIndex = 0;
|
frame.activeDescriptorPoolIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The descriptor set is chosen AFTER the writes are built (below), so a draw
|
||||||
|
// whose resolved descriptor content matches the previous draw can reuse that
|
||||||
|
// set and skip both AcquireDescriptorSet and vkUpdateDescriptorSets.
|
||||||
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
|
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
|
||||||
VkResult allocResult = AcquireDescriptorSet(frameIndex, programObj, descriptorSet);
|
|
||||||
if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) {
|
|
||||||
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor set acquire returned %d",
|
|
||||||
allocResult);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MOBILEGL_ASSERT(m_textureManager != nullptr, "BindProgramUniformBuffers: texture manager is null");
|
MOBILEGL_ASSERT(m_textureManager != nullptr, "BindProgramUniformBuffers: texture manager is null");
|
||||||
MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null");
|
MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null");
|
||||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null");
|
MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null");
|
||||||
|
|
||||||
Vector<VkWriteDescriptorSet> writes;
|
auto& writes = m_writesScratch;
|
||||||
|
auto& bufferInfos = m_bufferInfosScratch;
|
||||||
|
auto& imageInfos = m_imageInfosScratch;
|
||||||
|
auto& texelBufferViews = m_texelBufferViewsScratch;
|
||||||
|
auto& dynamicOffsets = m_dynamicOffsetsScratch;
|
||||||
|
writes.clear();
|
||||||
|
bufferInfos.clear();
|
||||||
|
imageInfos.clear();
|
||||||
|
texelBufferViews.clear();
|
||||||
|
dynamicOffsets.clear();
|
||||||
|
// Arrayed UBO bindings contribute extra buffer infos and dynamic offsets; reserve for
|
||||||
|
// the worst case so the pBufferInfo pointers taken below never dangle on reallocation.
|
||||||
|
Uint32 uboArrayExtra = 0;
|
||||||
|
for (const auto& arrayEntry : programObj.arrayedUniformBlockIndicesByBinding) {
|
||||||
|
uboArrayExtra += static_cast<Uint32>(arrayEntry.second.size()) - 1u;
|
||||||
|
}
|
||||||
writes.reserve(m_maxBindings);
|
writes.reserve(m_maxBindings);
|
||||||
Vector<VkDescriptorBufferInfo> bufferInfos;
|
bufferInfos.reserve(m_maxBindings + uboArrayExtra);
|
||||||
Vector<VkDescriptorImageInfo> imageInfos;
|
|
||||||
Vector<VkBufferView> texelBufferViews;
|
|
||||||
Vector<Uint32> dynamicOffsets;
|
|
||||||
bufferInfos.reserve(m_maxBindings);
|
|
||||||
imageInfos.reserve(m_maxBindings);
|
imageInfos.reserve(m_maxBindings);
|
||||||
texelBufferViews.reserve(m_maxBindings);
|
texelBufferViews.reserve(m_maxBindings);
|
||||||
dynamicOffsets.reserve(programObj.dynamicBindings.size());
|
dynamicOffsets.reserve(programObj.dynamicBindings.size() + uboArrayExtra);
|
||||||
|
|
||||||
const Uint32 bindingCount =
|
const Uint32 bindingCount =
|
||||||
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
|
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
|
||||||
@@ -848,31 +1203,82 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
write.descriptorCount = 1;
|
write.descriptorCount = 1;
|
||||||
|
|
||||||
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) {
|
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) {
|
||||||
const void* payload = nullptr;
|
const Uint32 descriptorCount =
|
||||||
VkDeviceSize payloadSize = 0;
|
binding < programObj.bindingDescriptorCounts.size()
|
||||||
const Bool hasPayload = ResolveUniformBufferPayload(program, programObj, binding, payload, payloadSize);
|
? std::max<Uint32>(1, programObj.bindingDescriptorCounts[binding])
|
||||||
MOBILEGL_ASSERT(hasPayload && payload != nullptr && payloadSize > 0,
|
: 1u;
|
||||||
"UniformDescriptorBinder::BindProgramUniformBuffers failed: missing UBO payload on binding %u",
|
const SizeT firstBufferInfoIndex = bufferInfos.size();
|
||||||
binding);
|
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||||
|
UboBindResult ubo{};
|
||||||
|
const Bool hasPayload =
|
||||||
|
ResolveUniformBufferPayload(program, programObj, binding, element, ubo);
|
||||||
|
MOBILEGL_ASSERT(hasPayload && ubo.payload != nullptr && ubo.payloadSize > 0,
|
||||||
|
"UniformDescriptorBinder::BindProgramUniformBuffers failed: missing UBO payload on binding %u element %u",
|
||||||
|
binding, element);
|
||||||
|
|
||||||
BufferSlice slice{};
|
VkDescriptorBufferInfo bufferInfo{};
|
||||||
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, payload, payloadSize,
|
// Keep offset 0 (sub-range selected via the dynamic offset) so the hashed bufferInfo
|
||||||
m_minDynamicOffsetAlignment, slice)) {
|
// is stable across draws and the descriptor-set reuse cache keeps hitting.
|
||||||
MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
|
bufferInfo.offset = 0;
|
||||||
binding);
|
Uint32 dynOffset;
|
||||||
return false;
|
if (ubo.directBindable) {
|
||||||
|
// Zero-copy: bind the app's resident VkBuffer directly, no per-draw memcpy.
|
||||||
|
bufferInfo.buffer = ubo.buffer;
|
||||||
|
bufferInfo.range = ubo.range;
|
||||||
|
dynOffset = static_cast<Uint32>(ubo.dynamicOffset);
|
||||||
|
} else {
|
||||||
|
// Global-UBO slice reuse (see GlobalUboSliceMemo): unchanged
|
||||||
|
// uniform bytes re-use the slice already uploaded this frame.
|
||||||
|
const Bool isGlobalUbo =
|
||||||
|
programObj.globalUboBinding == static_cast<Int>(binding) && element == 0;
|
||||||
|
const Uint64 uboFrameSerial = m_bufferManager->GetFrameSerial();
|
||||||
|
const Uint64 uboProgramLifetimeId = program.GetLifetimeId();
|
||||||
|
const Uint32 uboContentVersion = program.GetUBOContentVersion();
|
||||||
|
Bool reusedSlice = false;
|
||||||
|
if (isGlobalUbo) {
|
||||||
|
for (const auto& memo : m_globalUboMemo) {
|
||||||
|
if (memo.buffer != VK_NULL_HANDLE &&
|
||||||
|
memo.programLifetimeId == uboProgramLifetimeId &&
|
||||||
|
memo.frameSerial == uboFrameSerial &&
|
||||||
|
memo.uboContentVersion == uboContentVersion &&
|
||||||
|
memo.range == static_cast<VkDeviceSize>(ubo.payloadSize)) {
|
||||||
|
bufferInfo.buffer = memo.buffer;
|
||||||
|
bufferInfo.range = memo.range;
|
||||||
|
dynOffset = static_cast<Uint32>(memo.offset);
|
||||||
|
reusedSlice = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!reusedSlice) {
|
||||||
|
BufferSlice slice{};
|
||||||
|
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, ubo.payload,
|
||||||
|
ubo.payloadSize, m_minDynamicOffsetAlignment, slice)) {
|
||||||
|
MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u element %u",
|
||||||
|
binding, element);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bufferInfo.buffer = slice.buffer;
|
||||||
|
bufferInfo.range = ubo.payloadSize;
|
||||||
|
dynOffset = static_cast<Uint32>(slice.offset);
|
||||||
|
if (isGlobalUbo) {
|
||||||
|
m_globalUboMemo[m_globalUboMemoNext] = GlobalUboSliceMemo{
|
||||||
|
uboProgramLifetimeId, uboFrameSerial, uboContentVersion,
|
||||||
|
slice.buffer, slice.offset, static_cast<VkDeviceSize>(ubo.payloadSize)};
|
||||||
|
m_globalUboMemoNext = (m_globalUboMemoNext + 1) % kGlobalUboMemoSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bufferInfos.push_back(bufferInfo);
|
||||||
|
// Dynamic offsets are consumed in binding order, then array element order,
|
||||||
|
// matching Vulkan's dynamic-offset consumption rules.
|
||||||
|
dynamicOffsets.push_back(dynOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkDescriptorBufferInfo bufferInfo{};
|
|
||||||
bufferInfo.buffer = slice.buffer;
|
|
||||||
bufferInfo.offset = 0;
|
|
||||||
bufferInfo.range = payloadSize;
|
|
||||||
bufferInfos.push_back(bufferInfo);
|
|
||||||
|
|
||||||
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||||
write.pBufferInfo = &bufferInfos.back();
|
write.descriptorCount = descriptorCount;
|
||||||
|
write.pBufferInfo = &bufferInfos[firstBufferInfoIndex];
|
||||||
writes.push_back(write);
|
writes.push_back(write);
|
||||||
dynamicOffsets.push_back(static_cast<Uint32>(slice.offset));
|
|
||||||
} else if (kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer) {
|
} else if (kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer) {
|
||||||
VkBufferView bufferView = VK_NULL_HANDLE;
|
VkBufferView bufferView = VK_NULL_HANDLE;
|
||||||
if (!ResolveTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) ||
|
if (!ResolveTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) ||
|
||||||
@@ -942,12 +1348,92 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!writes.empty()) {
|
// Reuse the previous draw's descriptor set when the resolved content is
|
||||||
vkUpdateDescriptorSets(m_device, static_cast<Uint32>(writes.size()), writes.data(), 0, nullptr);
|
// byte-identical (only the bind-time dynamic offsets differ). The signature
|
||||||
|
// covers the descriptor-set layout + every write's binding/type/count + the
|
||||||
|
// pointed-to buffer/image/texel-buffer infos (all value-initialized, so no
|
||||||
|
// padding noise). Correctness: bindings are re-resolved every draw, so the
|
||||||
|
// signature always reflects the current state and reuse happens only on an
|
||||||
|
// exact match; the reused set is never re-acquired within a frame (the acquire
|
||||||
|
// cursor only advances), so its written contents survive; the layout is part of
|
||||||
|
// the signature so reuse never crosses programs. Sampler overrides (blits)
|
||||||
|
// bypass and invalidate the cache.
|
||||||
|
const Bool cacheable = (samplerBindingOverride == nullptr);
|
||||||
|
Uint64 signature = 0xcbf29ce484222325ULL;
|
||||||
|
{
|
||||||
|
const auto mix64 = [&signature](Uint64 word) {
|
||||||
|
signature = (signature ^ word) * 0x100000001b3ULL;
|
||||||
|
};
|
||||||
|
// The hashed descriptor payloads (VkDescriptorBufferInfo=24B,
|
||||||
|
// VkDescriptorImageInfo=24B, VkBufferView=8B) are all 8-byte-multiple sized
|
||||||
|
// and value-initialized (padding is zero), so hashing 64-bit words at a time
|
||||||
|
// is exact and ~8x cheaper than byte-wise - the signature is recomputed every
|
||||||
|
// draw, so its own cost has to stay small.
|
||||||
|
const auto mixWords = [&mix64](const void* data, SizeT byteSize) {
|
||||||
|
const auto* words = static_cast<const Uint64*>(data);
|
||||||
|
for (SizeT i = 0; i < byteSize / sizeof(Uint64); ++i) {
|
||||||
|
mix64(words[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mixWords(&programObj.descriptorSetLayout, sizeof(programObj.descriptorSetLayout));
|
||||||
|
for (const auto& write : writes) {
|
||||||
|
mix64((static_cast<Uint64>(write.dstBinding) << 40) ^
|
||||||
|
(static_cast<Uint64>(write.descriptorType) << 8) ^
|
||||||
|
static_cast<Uint64>(write.descriptorCount));
|
||||||
|
}
|
||||||
|
mixWords(bufferInfos.data(), bufferInfos.size() * sizeof(VkDescriptorBufferInfo));
|
||||||
|
mixWords(imageInfos.data(), imageInfos.size() * sizeof(VkDescriptorImageInfo));
|
||||||
|
mixWords(texelBufferViews.data(), texelBufferViews.size() * sizeof(VkBufferView));
|
||||||
}
|
}
|
||||||
|
|
||||||
vkCmdBindDescriptorSets(commandBuffer, bindPoint, programObj.pipelineLayout, 0, 1,
|
if (cacheable && m_hasLastDescriptor && signature == m_lastDescriptorSignature) {
|
||||||
&descriptorSet, static_cast<Uint32>(dynamicOffsets.size()), dynamicOffsets.data());
|
descriptorSet = m_lastBoundDescriptorSet;
|
||||||
|
} else {
|
||||||
|
VkResult allocResult = AcquireDescriptorSet(frameIndex, programObj, descriptorSet);
|
||||||
|
if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) {
|
||||||
|
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor set acquire returned %d",
|
||||||
|
allocResult);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto& write : writes) {
|
||||||
|
write.dstSet = descriptorSet;
|
||||||
|
}
|
||||||
|
if (!writes.empty()) {
|
||||||
|
vkUpdateDescriptorSets(m_device, static_cast<Uint32>(writes.size()), writes.data(), 0, nullptr);
|
||||||
|
}
|
||||||
|
m_lastBoundDescriptorSet = descriptorSet;
|
||||||
|
m_lastDescriptorSignature = signature;
|
||||||
|
m_hasLastDescriptor = cacheable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the driver call when this exact binding is already live on the
|
||||||
|
// command buffer (see the bind-dedup shadow in the header).
|
||||||
|
const Uint32 offsetCount = static_cast<Uint32>(dynamicOffsets.size());
|
||||||
|
Bool identicalBind = m_lastBindValid && m_lastBindSet == descriptorSet &&
|
||||||
|
m_lastBindLayout == programObj.pipelineLayout && m_lastBindPoint == bindPoint &&
|
||||||
|
m_lastBindOffsetCount == offsetCount && offsetCount <= kMaxShadowedDynamicOffsets;
|
||||||
|
if (identicalBind) {
|
||||||
|
for (Uint32 i = 0; i < offsetCount; ++i) {
|
||||||
|
if (m_lastBindOffsets[i] != dynamicOffsets[i]) {
|
||||||
|
identicalBind = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!identicalBind) {
|
||||||
|
vkCmdBindDescriptorSets(commandBuffer, bindPoint, programObj.pipelineLayout, 0, 1,
|
||||||
|
&descriptorSet, offsetCount, dynamicOffsets.data());
|
||||||
|
if (offsetCount <= kMaxShadowedDynamicOffsets) {
|
||||||
|
m_lastBindValid = true;
|
||||||
|
m_lastBindSet = descriptorSet;
|
||||||
|
m_lastBindLayout = programObj.pipelineLayout;
|
||||||
|
m_lastBindPoint = bindPoint;
|
||||||
|
m_lastBindOffsetCount = offsetCount;
|
||||||
|
std::copy_n(dynamicOffsets.data(), offsetCount, m_lastBindOffsets);
|
||||||
|
} else {
|
||||||
|
m_lastBindValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -39,9 +39,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void BeginFrame(Uint32 frameIndex);
|
void BeginFrame(Uint32 frameIndex);
|
||||||
|
// A command buffer (re)began recording: descriptor bindings recorded into
|
||||||
|
// the previous buffer do not carry over, so drop the bind-dedup shadow.
|
||||||
|
void OnCommandBufferBoundary() { m_lastBindValid = false; }
|
||||||
|
// A ProgramFactory eviction just destroyed this layout: purge every frame
|
||||||
|
// slot's cached descriptor sets for it, so a recycled handle value can never
|
||||||
|
// stale-hit sets written for the dead layout's bindings. The sets are
|
||||||
|
// vkFreeDescriptorSets'd back to their pools (created with
|
||||||
|
// FREE_DESCRIPTOR_SET_BIT) and the pool accounting is credited, so program
|
||||||
|
// churn recycles pool capacity instead of abandoning it. GPU-safe: the layout
|
||||||
|
// only dies after >1024 idle frame boundaries, so no in-flight command buffer
|
||||||
|
// references its sets. This is the only eviction path for the per-layout
|
||||||
|
// caches - a live layout's entry must never be purged (its sets would be
|
||||||
|
// unreachable pool slots), so there is deliberately no age-based sweep here.
|
||||||
|
void OnDescriptorSetLayoutDestroyed(VkDescriptorSetLayout descriptorSetLayout);
|
||||||
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
|
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj,
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
Vector<MG_State::GLState::ITextureObject*>& outTextures);
|
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,
|
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
|
||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj,
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
@@ -49,6 +66,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
const SamplerBindingOverride* samplerBindingOverride = nullptr);
|
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);
|
||||||
|
|
||||||
|
// True when the program reads at least one sampler and every one of them is bound to a
|
||||||
|
// texture whose GL level range is a single level. Such a sampler resolves to
|
||||||
|
// minLod = maxLod = 0 (see VkSamplerManager::GetOrCreateSampler), so an implicit-LOD sample
|
||||||
|
// and an explicit LOD 0 sample must read the same texel - which is what makes the
|
||||||
|
// ExplicitLod0Sampling SPIR-V rewrite safe to request. Deliberately conservative: it reads
|
||||||
|
// only GL state, so a texture that ends up single-level for another reason (one uploaded
|
||||||
|
// level under a wide level range) merely misses the rewrite.
|
||||||
|
static Bool ProgramSamplesOnlySingleLevelTextures(const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct DescriptorPoolBucket {
|
struct DescriptorPoolBucket {
|
||||||
VkDescriptorPool handle = VK_NULL_HANDLE;
|
VkDescriptorPool handle = VK_NULL_HANDLE;
|
||||||
@@ -56,8 +89,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 allocatedSets = 0;
|
Uint32 allocatedSets = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A cached descriptor set together with the pool it was allocated from, so a
|
||||||
|
// layout-destroyed purge can vkFreeDescriptorSets it back and credit the
|
||||||
|
// owning bucket's accounting.
|
||||||
|
struct CachedDescriptorSet {
|
||||||
|
VkDescriptorSet set = VK_NULL_HANDLE;
|
||||||
|
VkDescriptorPool pool = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
|
||||||
struct DescriptorSetCacheEntry {
|
struct DescriptorSetCacheEntry {
|
||||||
Vector<VkDescriptorSet> sets;
|
Vector<CachedDescriptorSet> sets;
|
||||||
Uint32 cursor = 0;
|
Uint32 cursor = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,6 +114,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
static Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
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;
|
SharedPtr<MG_State::GLState::ITextureObject> GetFallbackTexture(TextureTarget target) const;
|
||||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
@@ -89,9 +136,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
VkDescriptorImageInfo& outImageInfo) const;
|
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,
|
Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
const void*& outData, VkDeviceSize& outSize) const;
|
Uint32 arrayElement, UboBindResult& out) const;
|
||||||
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
||||||
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
||||||
VkResult AllocateDescriptorSetsFromActivePool(
|
VkResult AllocateDescriptorSetsFromActivePool(
|
||||||
@@ -113,6 +170,82 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkTextureManager* m_textureManager = nullptr;
|
VkTextureManager* m_textureManager = nullptr;
|
||||||
VkSamplerManager* m_samplerManager = nullptr;
|
VkSamplerManager* m_samplerManager = nullptr;
|
||||||
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2D;
|
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;
|
||||||
|
|
||||||
|
// vkCmdBindDescriptorSets dedup: consecutive draws with a static uniform
|
||||||
|
// block resolve to the same set AND the same dynamic offsets, so the
|
||||||
|
// driver call can be skipped outright. Command-buffer-scope state; reset
|
||||||
|
// via OnCommandBufferBoundary whenever a recording (re)begins. Keyed on
|
||||||
|
// layout+bind point, so a pipeline-layout switch always rebinds.
|
||||||
|
static constexpr Uint32 kMaxShadowedDynamicOffsets = 8;
|
||||||
|
Bool m_lastBindValid = false;
|
||||||
|
VkDescriptorSet m_lastBindSet = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout m_lastBindLayout = VK_NULL_HANDLE;
|
||||||
|
VkPipelineBindPoint m_lastBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||||
|
Uint32 m_lastBindOffsetCount = 0;
|
||||||
|
Uint32 m_lastBindOffsets[kMaxShadowedDynamicOffsets] = {};
|
||||||
|
|
||||||
|
// Global-UBO transient-slice reuse: MC leaves the default uniform block
|
||||||
|
// untouched across long GUI/terrain runs, so the per-draw re-upload of
|
||||||
|
// the same bytes can reuse the slice uploaded earlier THIS frame (frame
|
||||||
|
// serial guards arena recycling; the content version guards writes).
|
||||||
|
struct GlobalUboSliceMemo {
|
||||||
|
Uint64 programLifetimeId = 0;
|
||||||
|
Uint64 frameSerial = 0;
|
||||||
|
Uint32 uboContentVersion = 0;
|
||||||
|
VkBuffer buffer = VK_NULL_HANDLE;
|
||||||
|
VkDeviceSize offset = 0;
|
||||||
|
VkDeviceSize range = 0;
|
||||||
|
};
|
||||||
|
static constexpr Uint32 kGlobalUboMemoSize = 4;
|
||||||
|
GlobalUboSliceMemo m_globalUboMemo[kGlobalUboMemoSize];
|
||||||
|
Uint32 m_globalUboMemoNext = 0;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
Uint32 viewLevelCount = 0;
|
||||||
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,17 @@ 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.IsLong, sizeof(attr.IsLong)));
|
||||||
|
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)));
|
||||||
|
|
||||||
|
// The buffer's heap address is an identity component of the key: a freed
|
||||||
|
// buffer's reused address can alias an old cache entry, but only under a
|
||||||
|
// byte-identical attribute layout - and the entry payload is a pure function
|
||||||
|
// of the hashed inputs, with the draw path re-resolving bindingBufferKeys
|
||||||
|
// against the live VAO attribute pointers, so an aliased hit returns exactly
|
||||||
|
// what a rebuild would. Address drift only grows the map; the OnFrameBoundary
|
||||||
|
// aging sweep bounds that.
|
||||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey)));
|
||||||
}
|
}
|
||||||
@@ -38,17 +47,39 @@ 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);
|
// Per-draw fast path: the VAO carries a pointer to its resolved entry,
|
||||||
return GetOrCreateVertexInputState(vao, hash);
|
// valid while its config version and the cache's eviction epoch both
|
||||||
|
// match - no re-hash, no map lookup.
|
||||||
|
const void* memoState = nullptr;
|
||||||
|
Uint64 memoEpoch = 0;
|
||||||
|
if (vao.GetBackendStateMemo(memoState, memoEpoch) && memoEpoch == m_evictionEpoch) {
|
||||||
|
const auto* entry = static_cast<const BackendVertexInputState*>(memoState);
|
||||||
|
entry->lastUsedFrameBoundary = m_frameBoundaryCounter;
|
||||||
|
return *entry;
|
||||||
|
}
|
||||||
|
const BackendVertexInputState& entry = GetOrCreateVertexInputState(vao, GetOrComputeHash(vao));
|
||||||
|
vao.SetBackendStateMemo(&entry, m_evictionEpoch);
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash) {
|
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;
|
it->second->lastUsedFrameBoundary = m_frameBoundaryCounter;
|
||||||
|
return *it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexInputStateBuilder builder;
|
VertexInputStateBuilder builder;
|
||||||
@@ -56,6 +87,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<SizeT> bindingBaseOffsets;
|
Vector<SizeT> bindingBaseOffsets;
|
||||||
Vector<Uint32> bindingAttributeLocations;
|
Vector<Uint32> bindingAttributeLocations;
|
||||||
Vector<Bool> bindingUsesClientMemory;
|
Vector<Bool> bindingUsesClientMemory;
|
||||||
|
Vector<VertexStreamConversion> bindingConversions;
|
||||||
|
Vector<VkVertexInputBindingDivisorDescriptionEXT> bindingDivisors;
|
||||||
|
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);
|
||||||
@@ -63,23 +97,79 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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, attr.IsLong);
|
||||||
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;
|
||||||
|
|
||||||
@@ -89,28 +179,142 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
bindingBaseOffsets.push_back(attr.Buffer ? attr.Offset : 0);
|
bindingBaseOffsets.push_back(attr.Buffer ? attr.Offset : 0);
|
||||||
bindingAttributeLocations.push_back(location);
|
bindingAttributeLocations.push_back(location);
|
||||||
bindingUsesClientMemory.push_back(attr.Buffer == nullptr);
|
bindingUsesClientMemory.push_back(attr.Buffer == nullptr);
|
||||||
|
bindingConversions.push_back(conversion);
|
||||||
builder.AddBinding(binding, stride, inputRate);
|
builder.AddBinding(binding, stride, inputRate);
|
||||||
builder.AddAttribute(location, binding, vkFormat, 0);
|
builder.AddAttribute(location, binding, vkFormat, 0);
|
||||||
|
// Divisor 1 is what VK_VERTEX_INPUT_RATE_INSTANCE already means; only anything
|
||||||
|
// else needs the extension to say it.
|
||||||
|
if (inputRate == VK_VERTEX_INPUT_RATE_INSTANCE && attr.Divisor != 1) {
|
||||||
|
bindingDivisors.push_back({binding, static_cast<Uint32>(attr.Divisor)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& state = builder.Build();
|
const auto& state = builder.Build();
|
||||||
|
|
||||||
auto& entry = m_cache[hash];
|
auto& slot = m_cache[hash];
|
||||||
|
if (!slot) {
|
||||||
|
slot = MakeUnique<BackendVertexInputState>();
|
||||||
|
}
|
||||||
|
BackendVertexInputState& entry = *slot;
|
||||||
entry.hash = hash;
|
entry.hash = hash;
|
||||||
|
entry.lastUsedFrameBoundary = m_frameBoundaryCounter;
|
||||||
|
entry.bindingDivisors = Move(bindingDivisors);
|
||||||
entry.bindings = builder.GetBindings();
|
entry.bindings = builder.GetBindings();
|
||||||
entry.attributes = builder.GetAttributes();
|
entry.attributes = builder.GetAttributes();
|
||||||
|
// See the layoutHash declaration: hash only the resolved layout, never
|
||||||
|
// buffer identities, so identical layouts across VAOs/buffers agree.
|
||||||
|
XXHASH_VERIFY(XXH64_reset(m_hashState, 0));
|
||||||
|
for (const auto& binding : entry.bindings) {
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.binding, sizeof(binding.binding)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.stride, sizeof(binding.stride)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.inputRate, sizeof(binding.inputRate)));
|
||||||
|
}
|
||||||
|
for (const auto& attribute : entry.attributes) {
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.location, sizeof(attribute.location)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.binding, sizeof(attribute.binding)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.format, sizeof(attribute.format)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.offset, sizeof(attribute.offset)));
|
||||||
|
}
|
||||||
|
for (const auto& divisor : entry.bindingDivisors) {
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &divisor.binding, sizeof(divisor.binding)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &divisor.divisor, sizeof(divisor.divisor)));
|
||||||
|
}
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &unsupportedAttribMask, sizeof(unsupportedAttribMask)));
|
||||||
|
entry.layoutHash = XXH64_digest(m_hashState);
|
||||||
|
entry.attributeLocationMask = 0;
|
||||||
|
for (const auto& attribute : entry.attributes) {
|
||||||
|
if (attribute.location < 32u) {
|
||||||
|
entry.attributeLocationMask |= (1u << attribute.location);
|
||||||
|
}
|
||||||
|
}
|
||||||
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
||||||
entry.bindingBaseOffsets = std::move(bindingBaseOffsets);
|
entry.bindingBaseOffsets = std::move(bindingBaseOffsets);
|
||||||
entry.bindingAttributeLocations = std::move(bindingAttributeLocations);
|
entry.bindingAttributeLocations = std::move(bindingAttributeLocations);
|
||||||
entry.bindingUsesClientMemory = std::move(bindingUsesClientMemory);
|
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();
|
||||||
|
if (!entry.bindingDivisors.empty()) {
|
||||||
|
entry.divisorState.vertexBindingDivisorCount = static_cast<Uint32>(entry.bindingDivisors.size());
|
||||||
|
entry.divisorState.pVertexBindingDivisors = entry.bindingDivisors.data();
|
||||||
|
entry.state.pNext = &entry.divisorState;
|
||||||
|
} else {
|
||||||
|
entry.state.pNext = nullptr;
|
||||||
|
}
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger) {
|
void VertexInputStateFactory::OnFrameBoundary() {
|
||||||
|
++m_frameBoundaryCounter;
|
||||||
|
|
||||||
|
// Sweep occasionally; evict entries whose last hit is far in the past.
|
||||||
|
// Erasure happens only here, never mid-frame: the draw path holds a
|
||||||
|
// reference into the current entry across its setup, and unordered_map
|
||||||
|
// erase would invalidate it. Entries are CPU-side only, so no GPU-idle
|
||||||
|
// proof is needed; an evicted entry that is used again is simply rebuilt
|
||||||
|
// from the VAO state (same hash, same content).
|
||||||
|
constexpr Uint64 kSweepInterval = 256;
|
||||||
|
constexpr Uint64 kRetireAgeBoundaries = 1024;
|
||||||
|
if ((m_frameBoundaryCounter % kSweepInterval) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = m_cache.begin(); it != m_cache.end();) {
|
||||||
|
if (m_frameBoundaryCounter - it->second->lastUsedFrameBoundary > kRetireAgeBoundaries) {
|
||||||
|
it = m_cache.erase(it);
|
||||||
|
// Invalidate every VAO's state-pointer memo: the erased node's
|
||||||
|
// address may be reused by a future insert.
|
||||||
|
++m_evictionEpoch;
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger,
|
||||||
|
Bool isBgra, Bool isLong) {
|
||||||
|
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::Float64:
|
||||||
|
// A 64-bit attribute is fetched as its 32-bit word pair and bitcast back to double in the
|
||||||
|
// shader (PackDoubleVertexInputsPass does the shader half). That is bit-exact and, unlike
|
||||||
|
// VK_FORMAT_R64*_SFLOAT, needs no format capability: lavapipe reports bufferFeatures = 0
|
||||||
|
// for every R64 float format, so a native 64-bit vertex fetch is simply unavailable there
|
||||||
|
// while shaderFloat64 is not. Both halves key off nothing but the attribute being long,
|
||||||
|
// so they always agree without extra plumbing.
|
||||||
|
if (!isLong || isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
||||||
|
switch (size) {
|
||||||
|
case 1: return VK_FORMAT_R32G32_UINT;
|
||||||
|
case 2: return VK_FORMAT_R32G32B32A32_UINT;
|
||||||
|
// A dvec3/dvec4 input is 6/8 uint32 components: no single VkFormat, and GL spreads it
|
||||||
|
// over two attribute locations, which the location-per-VAO-index model here does not
|
||||||
|
// express. Declined rather than fetched wrong.
|
||||||
|
default: return VK_FORMAT_UNDEFINED;
|
||||||
|
}
|
||||||
case DataType::Float32:
|
case DataType::Float32:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||||
@@ -119,6 +323,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) {
|
||||||
@@ -222,4 +437,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,35 +19,101 @@ 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;
|
||||||
|
// Hash of the resolved Vulkan vertex layout only (bindings, attributes,
|
||||||
|
// unsupported mask) - NO buffer identities. `hash` mixes buffer heap
|
||||||
|
// addresses so per-chunk VBOs mint a fresh identity per buffer; keying
|
||||||
|
// pipelines on that minted one VkPipeline per chunk section for an
|
||||||
|
// identical layout, defeating pipeline reuse and the per-draw memo.
|
||||||
|
// Pipelines depend only on the layout, so they key on this instead.
|
||||||
|
HashType layoutHash = 0;
|
||||||
|
// Frame boundary of the last cache hit; entries idle past the
|
||||||
|
// OnFrameBoundary retirement age are evicted (CPU heap only).
|
||||||
|
// Mutable: the VAO's state-pointer memo fast path stamps it through
|
||||||
|
// a const entry reference.
|
||||||
|
mutable Uint64 lastUsedFrameBoundary = 0;
|
||||||
Vector<VkVertexInputBindingDescription> bindings;
|
Vector<VkVertexInputBindingDescription> bindings;
|
||||||
Vector<VkVertexInputAttributeDescription> attributes;
|
Vector<VkVertexInputAttributeDescription> attributes;
|
||||||
Vector<SizeT> bindingBufferKeys;
|
Vector<SizeT> bindingBufferKeys;
|
||||||
Vector<SizeT> bindingBaseOffsets;
|
Vector<SizeT> bindingBaseOffsets;
|
||||||
Vector<Uint32> bindingAttributeLocations;
|
Vector<Uint32> bindingAttributeLocations;
|
||||||
Vector<Bool> bindingUsesClientMemory;
|
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;
|
||||||
|
// Bitmask of `attributes[i].location` - the draw path needs it up to
|
||||||
|
// three times per draw, so it is baked once at build time.
|
||||||
|
Uint32 attributeLocationMask = 0;
|
||||||
|
// Per-binding glVertexAttribDivisor values other than 1. Vulkan's instance input
|
||||||
|
// rate advances once per instance and nothing else, so anything else has to be
|
||||||
|
// stated through VK_EXT_vertex_attribute_divisor. Empty when every instanced
|
||||||
|
// binding uses divisor 1, which is what the plain input rate already means.
|
||||||
|
Vector<VkVertexInputBindingDivisorDescriptionEXT> bindingDivisors;
|
||||||
|
VkPipelineVertexInputDivisorStateCreateInfoEXT divisorState{
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT
|
||||||
|
};
|
||||||
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 BackendVertexInputState& GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
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);
|
||||||
|
// Frame boundary hook: ages the cache and evicts entries not hit for many
|
||||||
|
// frames. The key mixes buffer heap addresses, so buffer/VAO churn keeps
|
||||||
|
// minting fresh keys; without eviction the map grows for the whole session.
|
||||||
|
// Entries hold no Vulkan handles (pipeline creation copies the descriptions)
|
||||||
|
// and the draw path's entry reference never spans a frame boundary, so
|
||||||
|
// eviction here needs no GPU-idle proof. Self-gated: one counter bump and
|
||||||
|
// compare except on sweep boundaries.
|
||||||
|
void OnFrameBoundary();
|
||||||
static SizeT GetComponentSize(DataType type);
|
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,
|
||||||
|
Bool isLong = false);
|
||||||
|
static Bool IsScaledIntegerVertexFormat(VkFormat format);
|
||||||
|
static VkFormat ToFloat32VertexFormat(Int componentCount);
|
||||||
|
Bool SupportsVertexBufferFormat(VkFormat format) const;
|
||||||
|
|
||||||
const VulkanRendererConfig& m_config;
|
const VulkanRendererConfig& m_config;
|
||||||
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||||
|
// Values are heap-allocated: FastSTL::unordered_map is open-addressing,
|
||||||
|
// so INSERT invalidates references to stored values. The draw path (and
|
||||||
|
// the VAOs' state-pointer memos) hold entry pointers across inserts;
|
||||||
|
// only the unique_ptr cell moves, never the pointee.
|
||||||
|
UnorderedMap<HashType, UniquePtr<BackendVertexInputState>> m_cache;
|
||||||
|
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
||||||
|
Uint64 m_frameBoundaryCounter = 0;
|
||||||
|
// Bumped whenever any cache entry is erased. VAOs memo a raw pointer to
|
||||||
|
// their heap-allocated entry (stable across map insert/rehash by
|
||||||
|
// construction); a memo is honored only while its recorded epoch
|
||||||
|
// matches, so an evicted entry can never be dereferenced through a
|
||||||
|
// stale memo.
|
||||||
|
Uint64 m_evictionEpoch = 1;
|
||||||
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
|
||||||
|
|||||||
@@ -7,12 +7,95 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "VkBufferManager.h"
|
#include "VkBufferManager.h"
|
||||||
|
#include "../DirectVulkan.h"
|
||||||
|
#include "VulkanRenderer.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
namespace {
|
namespace {
|
||||||
constexpr Uint32 kResidentBufferGCInterval = 60;
|
|
||||||
constexpr VmaAllocationCreateFlags kResidentBufferAllocationFlags =
|
constexpr VmaAllocationCreateFlags kResidentBufferAllocationFlags =
|
||||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
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;
|
||||||
|
// Appended to kPersistentBackedUsage when VK_EXT_transform_feedback is enabled
|
||||||
|
// (see VkBufferManagerInitInfo::transformFeedbackUsageEnabled).
|
||||||
|
constexpr VkBufferUsageFlags kTransformFeedbackUsage =
|
||||||
|
VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The CPU is about to read a buffer a shader wrote. Its bytes live in coherent
|
||||||
|
// host-visible GPU storage (EnsureGpuResidentStorage adopts it when the buffer is
|
||||||
|
// bound as a shader storage buffer), so nothing needs copying - but coherence only
|
||||||
|
// says the writes are visible once they have happened, so the work has to retire
|
||||||
|
// first.
|
||||||
|
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
||||||
|
(void)bufferObject;
|
||||||
|
if (pVulkanRenderer) {
|
||||||
|
pVulkanRenderer->FinishPendingGpuWork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
.ReadbackFromGpu = Ops_ReadbackFromGpu,
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
|
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
|
||||||
@@ -22,40 +105,102 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkBufferManager::Initialize requires non-zero frame count");
|
MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkBufferManager::Initialize requires non-zero frame count");
|
||||||
|
|
||||||
m_initInfo = initInfo;
|
m_initInfo = initInfo;
|
||||||
m_deferredResidentReleases.resize(initInfo.frameCount);
|
m_deferredBufferReleases.resize(initInfo.frameCount);
|
||||||
|
m_deferredResourceReleases.resize(initInfo.frameCount);
|
||||||
m_currentFrameIndex = 0;
|
m_currentFrameIndex = 0;
|
||||||
return InitializeTransientArenas();
|
m_frameSerial = 1;
|
||||||
|
m_completedSerialFloor = 0;
|
||||||
|
if (!InitializeTransientArenas()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_activeBufferManager = this;
|
||||||
|
MG_State::GLState::SetBufferBackendOps(&g_vulkanBufferBackendOps);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::Shutdown() {
|
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();
|
m_transientUploadArena.Shutdown();
|
||||||
DestroyResidentBuffers();
|
DestroyAllDeferredReleases();
|
||||||
DestroyDeferredResidentReleases();
|
ReleaseAllLiveResources();
|
||||||
|
m_copyProvider = nullptr;
|
||||||
m_initInfo = {};
|
m_initInfo = {};
|
||||||
m_currentFrameIndex = 0;
|
m_currentFrameIndex = 0;
|
||||||
m_residentGcTick = 0;
|
m_frameSerial = 1;
|
||||||
|
m_completedSerialFloor = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool VkBufferManager::RecreateTransientArenas(Uint32 frameCount) {
|
Bool VkBufferManager::RecreateTransientArenas(Uint32 frameCount) {
|
||||||
MOBILEGL_ASSERT(m_initInfo.allocator != nullptr, "VkBufferManager::RecreateTransientArenas requires initialized manager");
|
MOBILEGL_ASSERT(m_initInfo.allocator != nullptr,
|
||||||
|
"VkBufferManager::RecreateTransientArenas requires initialized manager");
|
||||||
MOBILEGL_ASSERT(frameCount > 0, "VkBufferManager::RecreateTransientArenas requires non-zero frame count");
|
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_transientUploadArena.Shutdown();
|
||||||
m_initInfo.frameCount = frameCount;
|
m_initInfo.frameCount = frameCount;
|
||||||
DestroyDeferredResidentReleases();
|
DestroyAllDeferredReleases();
|
||||||
m_deferredResidentReleases.resize(frameCount);
|
m_deferredBufferReleases.resize(frameCount);
|
||||||
|
m_deferredResourceReleases.resize(frameCount);
|
||||||
m_currentFrameIndex = 0;
|
m_currentFrameIndex = 0;
|
||||||
return InitializeTransientArenas();
|
return InitializeTransientArenas();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::BeginFrame(Uint32 frameIndex) {
|
void VkBufferManager::BeginFrame(Uint32 frameIndex) {
|
||||||
MOBILEGL_ASSERT(frameIndex < m_deferredResidentReleases.size(),
|
MOBILEGL_ASSERT(frameIndex < m_deferredBufferReleases.size(),
|
||||||
"VkBufferManager::BeginFrame frame index out of range");
|
"VkBufferManager::BeginFrame frame index out of range");
|
||||||
m_currentFrameIndex = frameIndex;
|
m_currentFrameIndex = frameIndex;
|
||||||
CollectDeferredResidentReleases(frameIndex);
|
++m_frameSerial;
|
||||||
|
CollectDeferredReleases(frameIndex);
|
||||||
m_transientUploadArena.BeginFrame(frameIndex);
|
m_transientUploadArena.BeginFrame(frameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VkBufferManager::CollectAllDeferredReleases() {
|
||||||
|
for (Uint32 frameIndex = 0; frameIndex < m_deferredBufferReleases.size(); ++frameIndex) {
|
||||||
|
CollectDeferredReleases(frameIndex);
|
||||||
|
}
|
||||||
|
for (Uint32 frameIndex = 0; frameIndex < m_transientUploadArena.GetFrameCount(); ++frameIndex) {
|
||||||
|
m_transientUploadArena.CollectDeferredReleases(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,
|
Bool VkBufferManager::UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data,
|
||||||
VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) {
|
VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) {
|
||||||
(void)kind;
|
(void)kind;
|
||||||
@@ -67,7 +212,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
.allocator = m_initInfo.allocator,
|
.allocator = m_initInfo.allocator,
|
||||||
.frameCount = m_initInfo.frameCount,
|
.frameCount = m_initInfo.frameCount,
|
||||||
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
|
.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_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
|
||||||
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||||
.memoryUsage = m_initInfo.transientMemoryUsage,
|
.memoryUsage = m_initInfo.transientMemoryUsage,
|
||||||
.allocationFlags = m_initInfo.transientAllocationFlags,
|
.allocationFlags = m_initInfo.transientAllocationFlags,
|
||||||
.minBufferSize = m_initInfo.minUploadBytes,
|
.minBufferSize = m_initInfo.minUploadBytes,
|
||||||
@@ -75,116 +221,407 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool VkBufferManager::SyncResidentBuffer(BufferKind kind,
|
VkBufferResource* VkBufferManager::ResourceOf(MG_State::GLState::BufferObject& bufferObject) {
|
||||||
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
return static_cast<VkBufferResource*>(bufferObject.GetBackendResource().get());
|
||||||
BufferSlice& outSlice) {
|
}
|
||||||
const VkBufferUsageFlags requiredUsage = GetVkBufferUsage(kind);
|
|
||||||
MOBILEGL_ASSERT(requiredUsage != 0,
|
|
||||||
"VkBufferManager::SyncResidentBuffer unsupported resident buffer kind");
|
|
||||||
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::SyncResidentBuffer requires valid buffer object");
|
|
||||||
CollectResidentGarbageIfNeeded();
|
|
||||||
|
|
||||||
const auto* bufferData = bufferObject->GetDataReadOnly().get();
|
VkBufferResource* VkBufferManager::GetOrCreateResource(
|
||||||
MOBILEGL_ASSERT(bufferData != nullptr, "VkBufferManager::SyncResidentBuffer requires frontend buffer data");
|
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
|
||||||
bufferObject->MarkPersistentMappedRangeDirty();
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
const VkDeviceSize bufferSize = static_cast<VkDeviceSize>(bufferObject->GetSize());
|
void VkBufferManager::TrackLiveResource(const SharedPtr<VkBufferResource>& resource) {
|
||||||
if (bufferSize == 0) {
|
if (m_liveResources.size() >= kLiveResourcePruneThreshold) {
|
||||||
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: buffer size is zero");
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
resource.storageSize = size;
|
||||||
auto& entry = m_residentBuffers[bufferObject.get()];
|
resource.usageFlags = usage;
|
||||||
entry.aliveRef = bufferObject;
|
|
||||||
|
|
||||||
const auto changeBits = bufferObject->GetChangeBits();
|
|
||||||
const Bool needsRecreate = !entry.buffer.IsValid() || entry.size != bufferSize ||
|
|
||||||
((entry.usage & requiredUsage) != requiredUsage) ||
|
|
||||||
(changeBits & BufferChangeBits::PreferReallocationBit);
|
|
||||||
if (needsRecreate) {
|
|
||||||
const VkBufferUsageFlags recreatedUsage = entry.usage | requiredUsage;
|
|
||||||
DeferResidentRelease(std::move(entry.buffer));
|
|
||||||
const Bool created = entry.buffer.Create({
|
|
||||||
.allocator = m_initInfo.allocator,
|
|
||||||
.size = bufferSize,
|
|
||||||
.usage = recreatedUsage,
|
|
||||||
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
|
|
||||||
.allocationFlags = kResidentBufferAllocationFlags,
|
|
||||||
});
|
|
||||||
if (!created || entry.buffer.Map() == nullptr) {
|
|
||||||
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: unable to create resident buffer");
|
|
||||||
entry.buffer.Destroy();
|
|
||||||
entry.size = 0;
|
|
||||||
entry.usage = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!entry.buffer.Upload(bufferData->data(), bufferSize, 0)) {
|
|
||||||
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: initial upload failed");
|
|
||||||
entry.buffer.Destroy();
|
|
||||||
entry.size = 0;
|
|
||||||
entry.usage = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
entry.size = bufferSize;
|
|
||||||
entry.usage = recreatedUsage;
|
|
||||||
bufferObject->ClearDirty();
|
|
||||||
outSlice = entry.buffer.GetSlice(0, bufferSize);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changeBits & BufferChangeBits::DirtyBit) {
|
|
||||||
const auto& dirtyRanges = bufferObject->GetDirtyRanges();
|
|
||||||
for (const auto& range : dirtyRanges) {
|
|
||||||
const VkDeviceSize rangeOffset = static_cast<VkDeviceSize>(range.start);
|
|
||||||
const VkDeviceSize rangeSize = static_cast<VkDeviceSize>(range.end - range.start);
|
|
||||||
if (rangeSize == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!entry.buffer.Upload(bufferData->data() + range.start, rangeSize, rangeOffset)) {
|
|
||||||
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: dirty range upload failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bufferObject->ClearDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
outSlice = entry.buffer.GetSlice(0, bufferSize);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::DowngradeResidentBufferToTransient(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
|
Bool VkBufferManager::SwapStorageAndUploadAll(VkBufferResource& resource,
|
||||||
if (bufferObject == nullptr) {
|
MG_State::GLState::BufferObject& bufferObject) {
|
||||||
return;
|
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)) {
|
||||||
auto it = m_residentBuffers.find(bufferObject.get());
|
MGLOG_E("VkBufferManager::SwapStorageAndUploadAll: upload failed");
|
||||||
if (it == m_residentBuffers.end()) {
|
resource.pendingFullUpload = true;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
resource.pendingFullUpload = false;
|
||||||
DeferResidentRelease(std::move(it->second.buffer));
|
return true;
|
||||||
m_residentBuffers.erase(it);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::DeferResidentRelease(VkBufferObject&& buffer) {
|
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));
|
||||||
|
const VkBufferUsageFlags persistentUsage =
|
||||||
|
kPersistentBackedUsage |
|
||||||
|
(m_initInfo.transformFeedbackUsageEnabled ? kTransformFeedbackUsage : 0);
|
||||||
|
if (!CreateResidentStorage(*resource, size, persistentUsage, 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();
|
||||||
|
|
||||||
|
// A persistently mapped resource's storage IS the application's copy of the bytes -
|
||||||
|
// the frontend adopted it in place of the shadow and hands out pointers into it, and
|
||||||
|
// a shader can have written bytes the shadow never saw (a transform feedback
|
||||||
|
// capture). Streaming a second copy would feed this draw the stale shadow, and the
|
||||||
|
// downgrade below would release the storage the application still points at,
|
||||||
|
// breaking the "never recreated" promise AcquirePersistentMap makes.
|
||||||
|
if (resource->persistentMapped) {
|
||||||
|
return AcquireResidentSlice(kind, bufferObject, outSlice);
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
if (!buffer.IsValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_deferredResidentReleases.empty()) {
|
if (m_deferredBufferReleases.empty()) {
|
||||||
buffer.Destroy();
|
buffer.Destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredResidentReleases.size(),
|
MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredBufferReleases.size(),
|
||||||
"VkBufferManager::DeferResidentRelease current frame index out of range");
|
"VkBufferManager::DeferRelease current frame index out of range");
|
||||||
m_deferredResidentReleases[m_currentFrameIndex].push_back(std::move(buffer));
|
m_deferredBufferReleases[m_currentFrameIndex].push_back(std::move(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::CollectDeferredResidentReleases(Uint32 frameIndex) {
|
void VkBufferManager::CollectDeferredReleases(Uint32 frameIndex) {
|
||||||
MOBILEGL_ASSERT(frameIndex < m_deferredResidentReleases.size(),
|
MOBILEGL_ASSERT(frameIndex < m_deferredBufferReleases.size(),
|
||||||
"VkBufferManager::CollectDeferredResidentReleases frame index out of range");
|
"VkBufferManager::CollectDeferredReleases frame index out of range");
|
||||||
m_deferredResidentReleases[frameIndex].clear();
|
m_deferredBufferReleases[frameIndex].clear();
|
||||||
|
m_deferredResourceReleases[frameIndex].clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
VkBufferUsageFlags VkBufferManager::GetVkBufferUsage(BufferKind kind) {
|
VkBufferUsageFlags VkBufferManager::GetVkBufferUsage(BufferKind kind) {
|
||||||
@@ -209,46 +646,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::CollectResidentGarbageIfNeeded() {
|
void VkBufferManager::DestroyAllDeferredReleases() {
|
||||||
++m_residentGcTick;
|
for (auto& releases : m_deferredBufferReleases) {
|
||||||
if (m_residentGcTick < kResidentBufferGCInterval) {
|
for (auto& buffer : releases) {
|
||||||
return;
|
buffer.Destroy();
|
||||||
}
|
|
||||||
CollectResidentGarbageNow();
|
|
||||||
m_residentGcTick = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkBufferManager::CollectResidentGarbageNow() {
|
|
||||||
Vector<MG_State::GLState::BufferObject*> staleBuffers;
|
|
||||||
staleBuffers.reserve(m_residentBuffers.size());
|
|
||||||
|
|
||||||
for (const auto& [rawBuffer, entry] : m_residentBuffers) {
|
|
||||||
if (entry.aliveRef.expired()) {
|
|
||||||
staleBuffers.push_back(rawBuffer);
|
|
||||||
}
|
}
|
||||||
|
releases.clear();
|
||||||
}
|
}
|
||||||
|
m_deferredBufferReleases.clear();
|
||||||
for (const auto* rawBuffer : staleBuffers) {
|
for (auto& releases : m_deferredResourceReleases) {
|
||||||
auto it = m_residentBuffers.find(const_cast<MG_State::GLState::BufferObject*>(rawBuffer));
|
for (auto& resource : releases) {
|
||||||
if (it == m_residentBuffers.end()) {
|
resource->buffer.Destroy();
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
DeferResidentRelease(std::move(it->second.buffer));
|
releases.clear();
|
||||||
m_residentBuffers.erase(it);
|
|
||||||
}
|
}
|
||||||
}
|
m_deferredResourceReleases.clear();
|
||||||
|
|
||||||
void VkBufferManager::DestroyDeferredResidentReleases() {
|
|
||||||
for (auto& deferredReleases : m_deferredResidentReleases) {
|
|
||||||
deferredReleases.clear();
|
|
||||||
}
|
|
||||||
m_deferredResidentReleases.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VkBufferManager::DestroyResidentBuffers() {
|
|
||||||
for (auto& [_, entry] : m_residentBuffers) {
|
|
||||||
entry.buffer.Destroy();
|
|
||||||
}
|
|
||||||
m_residentBuffers.clear();
|
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -31,6 +31,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
|
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
|
||||||
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||||
Bool transientPersistentMapping = false;
|
Bool transientPersistentMapping = false;
|
||||||
|
// VK_EXT_transform_feedback is enabled: persistent-map storage additionally
|
||||||
|
// carries the transform feedback usage so capture targets can bind directly.
|
||||||
|
Bool transformFeedbackUsageEnabled = 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 {
|
class VkBufferManager {
|
||||||
@@ -41,35 +80,82 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// Recreate all per-frame transient arenas
|
// Recreate all per-frame transient arenas
|
||||||
Bool RecreateTransientArenas(Uint32 frameCount);
|
Bool RecreateTransientArenas(Uint32 frameCount);
|
||||||
void BeginFrame(Uint32 frameIndex);
|
void BeginFrame(Uint32 frameIndex);
|
||||||
|
// Drains every frame slot's deferred buffer/resource releases (and the
|
||||||
|
// transient arena's parked superseded blocks). Only valid when the
|
||||||
|
// caller has proven every queue submission complete; used by the
|
||||||
|
// present-less frame-boundary drain.
|
||||||
|
void CollectAllDeferredReleases();
|
||||||
|
// 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,
|
Bool UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size,
|
||||||
VkDeviceSize alignment, BufferSlice& outSlice);
|
VkDeviceSize alignment, BufferSlice& outSlice);
|
||||||
Bool SyncResidentBuffer(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
|
||||||
BufferSlice& outSlice);
|
// Draw-time acquire for resident (device-storage) buffers: ensures the
|
||||||
void DowngradeResidentBufferToTransient(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
// 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:
|
private:
|
||||||
struct ResidentBufferEntry {
|
|
||||||
WeakPtr<MG_State::GLState::BufferObject> aliveRef;
|
|
||||||
VkBufferObject buffer;
|
|
||||||
VkDeviceSize size = 0;
|
|
||||||
VkBufferUsageFlags usage = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Bool InitializeTransientArenas();
|
Bool InitializeTransientArenas();
|
||||||
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
|
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
|
||||||
void DeferResidentRelease(VkBufferObject&& buffer);
|
VkBufferResource* GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||||
void CollectDeferredResidentReleases(Uint32 frameIndex);
|
static VkBufferResource* ResourceOf(MG_State::GLState::BufferObject& bufferObject);
|
||||||
void CollectResidentGarbageIfNeeded();
|
Bool CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||||
void CollectResidentGarbageNow();
|
VkMemoryPropertyFlags requiredFlags = 0);
|
||||||
void DestroyDeferredResidentReleases();
|
// Swap storage (conditional orphan) and refill it from the shadow copy.
|
||||||
void DestroyResidentBuffers();
|
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{};
|
VkBufferManagerInitInfo m_initInfo{};
|
||||||
BufferArena m_transientUploadArena;
|
BufferArena m_transientUploadArena;
|
||||||
UnorderedMap<MG_State::GLState::BufferObject*, ResidentBufferEntry> m_residentBuffers;
|
IBufferCopyCommandProvider* m_copyProvider = nullptr;
|
||||||
Vector<Vector<VkBufferObject>> m_deferredResidentReleases;
|
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||||
|
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
|
||||||
|
Vector<WeakPtr<VkBufferResource>> m_liveResources;
|
||||||
Uint32 m_currentFrameIndex = 0;
|
Uint32 m_currentFrameIndex = 0;
|
||||||
Uint32 m_residentGcTick = 0;
|
Uint64 m_frameSerial = 1;
|
||||||
|
Uint64 m_completedSerialFloor = 0;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -49,11 +49,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bool VkBufferObject::Create(const VkBufferObjectDesc& desc) {
|
Bool VkBufferObject::Create(const VkBufferObjectDesc& desc) {
|
||||||
return Create(desc.allocator, desc.size, desc.usage, desc.memoryUsage, desc.allocationFlags);
|
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");
|
||||||
|
|
||||||
@@ -69,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);
|
||||||
@@ -154,6 +157,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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 {
|
BufferSlice VkBufferObject::GetSlice(VkDeviceSize offset, VkDeviceSize size) const {
|
||||||
MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::GetSlice offset out of range");
|
MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::GetSlice offset out of range");
|
||||||
const VkDeviceSize resolvedSize = (size == VK_WHOLE_SIZE) ? (m_size - offset) : size;
|
const VkDeviceSize resolvedSize = (size == VK_WHOLE_SIZE) ? (m_size - offset) : size;
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkBufferUsageFlags usage = 0;
|
VkBufferUsageFlags usage = 0;
|
||||||
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO;
|
VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO;
|
||||||
VmaAllocationCreateFlags allocationFlags = 0;
|
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 {
|
||||||
@@ -34,12 +37,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
Bool Create(const VkBufferObjectDesc& desc);
|
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* Map();
|
||||||
void Unmap();
|
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; }
|
||||||
|
|||||||
@@ -8,11 +8,107 @@
|
|||||||
|
|
||||||
#include "VkClearManager.h"
|
#include "VkClearManager.h"
|
||||||
|
|
||||||
|
#include "MG_State/GLState/Core.h"
|
||||||
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
|
||||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
static SharedPtr<MG_State::GLState::ITextureObject> GetClearableAttachmentTexture(
|
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
|
||||||
|
return target >= TextureUploadTarget::CubeMapPositiveX &&
|
||||||
|
target <= TextureUploadTarget::CubeMapNegativeZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkClearColorValue MakeVkClearColorValue(const ClearAttachmentPayload& payload, Bool formatLacksAlpha) {
|
||||||
|
VkClearColorValue clearValue{};
|
||||||
|
switch (payload.colorEncoding) {
|
||||||
|
case ClearColorEncoding::Int:
|
||||||
|
clearValue.int32[0] = payload.colorInt.x();
|
||||||
|
clearValue.int32[1] = payload.colorInt.y();
|
||||||
|
clearValue.int32[2] = payload.colorInt.z();
|
||||||
|
clearValue.int32[3] = formatLacksAlpha ? 1 : payload.colorInt.w();
|
||||||
|
break;
|
||||||
|
case ClearColorEncoding::Uint:
|
||||||
|
clearValue.uint32[0] = payload.colorUint.x();
|
||||||
|
clearValue.uint32[1] = payload.colorUint.y();
|
||||||
|
clearValue.uint32[2] = payload.colorUint.z();
|
||||||
|
clearValue.uint32[3] = formatLacksAlpha ? 1u : payload.colorUint.w();
|
||||||
|
break;
|
||||||
|
case ClearColorEncoding::Float:
|
||||||
|
clearValue.float32[0] = payload.color.x();
|
||||||
|
clearValue.float32[1] = payload.color.y();
|
||||||
|
clearValue.float32[2] = payload.color.z();
|
||||||
|
clearValue.float32[3] = formatLacksAlpha ? 1.0f : payload.color.w();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return clearValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreCompensateSrgbClearColor(ClearAttachmentPayload& payload, VkFormat destinationFormat) {
|
||||||
|
if (payload.colorEncoding != ClearColorEncoding::Float) return;
|
||||||
|
// With GL_FRAMEBUFFER_SRGB enabled GL performs the encoding itself, so the driver doing it
|
||||||
|
// is exactly right and there is nothing to undo.
|
||||||
|
if (MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb)) return;
|
||||||
|
if (ResolveSrgbAttachmentWriteFormat(destinationFormat, false) == destinationFormat) return;
|
||||||
|
|
||||||
|
// sRGB -> linear (GL 4.6 core 8.24), applied to the colour channels only: alpha is stored
|
||||||
|
// linearly in an sRGB format and must pass through untouched.
|
||||||
|
const auto toLinear = [](Float encoded) {
|
||||||
|
const Float value = std::clamp(encoded, 0.0f, 1.0f);
|
||||||
|
return value <= 0.04045f ? value / 12.92f : std::pow((value + 0.055f) / 1.055f, 2.4f);
|
||||||
|
};
|
||||||
|
payload.color = FloatVec4(toLinear(payload.color.x()), toLinear(payload.color.y()),
|
||||||
|
toLinear(payload.color.z()), payload.color.w());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForceOpaqueClearAlpha(ClearAttachmentPayload& payload) {
|
||||||
|
switch (payload.colorEncoding) {
|
||||||
|
case ClearColorEncoding::Int:
|
||||||
|
payload.colorInt = IntVec4(payload.colorInt.x(), payload.colorInt.y(), payload.colorInt.z(), 1);
|
||||||
|
break;
|
||||||
|
case ClearColorEncoding::Uint:
|
||||||
|
payload.colorUint = UintVec4(payload.colorUint.x(), payload.colorUint.y(), payload.colorUint.z(), 1u);
|
||||||
|
break;
|
||||||
|
case ClearColorEncoding::Float:
|
||||||
|
payload.color = FloatVec4(payload.color.x(), payload.color.y(), payload.color.z(), 1.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
const MG_State::GLState::FramebufferObject& drawFbo, FramebufferAttachmentType attachmentType) {
|
||||||
if (attachmentType == FramebufferAttachmentType::None) {
|
if (attachmentType == FramebufferAttachmentType::None) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -23,7 +119,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return attachment.GetTexture();
|
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() {
|
Bool VkClearManager::Initialize() {
|
||||||
@@ -31,7 +150,76 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VkClearManager::Shutdown() {
|
void VkClearManager::Shutdown() {
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
m_pendingClears.clear();
|
||||||
|
m_aliveObjects.clear();
|
||||||
|
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||||
@@ -40,88 +228,155 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto& drawbufs = drawFbo.GetDrawBuffers();
|
auto& drawbufs = drawFbo.GetDrawBuffers();
|
||||||
// This should automatically work on default & offscreen FBO
|
// This should automatically work on default & offscreen FBO
|
||||||
for (auto drawbuf: drawbufs) {
|
for (auto drawbuf: drawbufs) {
|
||||||
auto texture = GetClearableAttachmentTexture(drawFbo, drawbuf);
|
const auto* attachment = GetClearableAttachment(drawFbo, drawbuf);
|
||||||
if (!texture) {
|
if (!attachment) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QueueClear({
|
QueueClear({
|
||||||
.mask = GL_COLOR_BUFFER_BIT,
|
.mask = GL_COLOR_BUFFER_BIT,
|
||||||
.color = clearPayload.color
|
.color = clearPayload.color
|
||||||
}, texture);
|
}, *attachment);
|
||||||
|
|
||||||
MGLOG_D("%s: %s (texture %d) - color = (%.2f, %.2f, %.2f, %.2f)", __func__,
|
MGLOG_D("%s: %s (texture %d) - color = (%.2f, %.2f, %.2f, %.2f)", __func__,
|
||||||
MG_Util::ConvertFramebufferAttachmentTypeToString(drawbuf).c_str(),
|
MG_Util::ConvertFramebufferAttachmentTypeToString(drawbuf).c_str(),
|
||||||
texture->GetExternalIndex(),
|
attachment->GetTexture()->GetExternalIndex(),
|
||||||
clearPayload.color[0], clearPayload.color[1], clearPayload.color[2], clearPayload.color[3]);
|
clearPayload.color[0], clearPayload.color[1], clearPayload.color[2], clearPayload.color[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mask & GL_DEPTH_BUFFER_BIT) {
|
if (mask & GL_DEPTH_BUFFER_BIT) {
|
||||||
auto texture = GetClearableAttachmentTexture(drawFbo, FramebufferAttachmentType::Depth);
|
const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Depth);
|
||||||
if (texture) {
|
if (attachment) {
|
||||||
QueueClear({
|
QueueClear({
|
||||||
.mask = GL_DEPTH_BUFFER_BIT,
|
.mask = GL_DEPTH_BUFFER_BIT,
|
||||||
.depth = clearPayload.depth,
|
.depth = clearPayload.depth,
|
||||||
}, texture);
|
}, *attachment);
|
||||||
|
|
||||||
MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__,
|
MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__,
|
||||||
texture->GetExternalIndex(), clearPayload.depth);
|
attachment->GetTexture()->GetExternalIndex(), clearPayload.depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mask & GL_STENCIL_BUFFER_BIT) {
|
if (mask & GL_STENCIL_BUFFER_BIT) {
|
||||||
auto texture = GetClearableAttachmentTexture(drawFbo, FramebufferAttachmentType::Stencil);
|
const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Stencil);
|
||||||
if (texture) {
|
if (attachment) {
|
||||||
QueueClear({
|
QueueClear({
|
||||||
.mask = GL_STENCIL_BUFFER_BIT,
|
.mask = GL_STENCIL_BUFFER_BIT,
|
||||||
.stencil = clearPayload.stencil,
|
.stencil = clearPayload.stencil,
|
||||||
}, texture);
|
}, *attachment);
|
||||||
|
|
||||||
MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__,
|
MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__,
|
||||||
texture->GetExternalIndex(), clearPayload.stencil);
|
attachment->GetTexture()->GetExternalIndex(), clearPayload.stencil);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||||
if (clearPayload.mask == 0) {
|
if (clearPayload.mask == 0 || !texture) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WeakPtr<MG_State::GLState::ITextureObject> weakTexturePtr = texture;
|
|
||||||
if (weakTexturePtr.expired())
|
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);
|
||||||
|
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||||
|
if (clearPayload.mask == 0 || !attachment.IsTexture() || attachment.IsRenderbuffer()) {
|
||||||
return;
|
return;
|
||||||
auto* pTexture = weakTexturePtr.lock().get();
|
|
||||||
m_aliveObjects[pTexture] = weakTexturePtr;
|
|
||||||
auto& pending = m_pendingClears[pTexture];
|
|
||||||
pending.mask |= clearPayload.mask;
|
|
||||||
if (clearPayload.mask & GL_COLOR_BUFFER_BIT) {
|
|
||||||
pending.color = clearPayload.color;
|
|
||||||
}
|
}
|
||||||
if (clearPayload.mask & GL_DEPTH_BUFFER_BIT) {
|
const auto texture = attachment.GetTexture();
|
||||||
pending.depth = clearPayload.depth;
|
if (!texture) {
|
||||||
}
|
return;
|
||||||
if (clearPayload.mask & GL_STENCIL_BUFFER_BIT) {
|
|
||||||
pending.stencil = clearPayload.stencil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
|
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
|
||||||
return m_pendingClears.find(texture) != m_pendingClears.end();
|
if (texture == nullptr) {
|
||||||
}
|
|
||||||
|
|
||||||
Bool VkClearManager::GetPendingClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload) {
|
|
||||||
if (m_aliveObjects.find(texture) == m_aliveObjects.end() ||
|
|
||||||
m_pendingClears.find(texture) == m_pendingClears.end()) {
|
|
||||||
MGLOG_D("%s: Failed getting pending clear for texture %d", __func__, texture->GetExternalIndex());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
outPayload = m_pendingClears[texture];
|
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||||
MGLOG_D("%s: Got pending clear for texture %d (%s), mask=0x%x clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__,
|
return false; // per-draw hot path: nothing pending anywhere
|
||||||
texture->GetExternalIndex(),
|
}
|
||||||
MG_Util::ConvertTextureInternalFormatToString(texture->GetFormat()).c_str(),
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||||
|
return false; // per-draw hot path: nothing pending anywhere
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||||
|
return false; // per-draw hot path: nothing pending anywhere
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
static_cast<Uint32>(outPayload.mask),
|
||||||
outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3],
|
outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3],
|
||||||
outPayload.depth,
|
outPayload.depth,
|
||||||
@@ -129,27 +384,100 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||||
|
return false; // per-draw hot path: nothing pending anywhere
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
void VkClearManager::PopPendingClear(MG_State::GLState::ITextureObject* texture) {
|
||||||
MGLOG_D("%s: Pop pending clear for texture %d", __func__, texture->GetExternalIndex());
|
if (texture == nullptr) {
|
||||||
m_aliveObjects.erase(texture);
|
return;
|
||||||
m_pendingClears.erase(texture);
|
}
|
||||||
|
|
||||||
|
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||||
|
return; // per-draw hot path: nothing pending anywhere
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
SizeT VkClearManager::CollectGarbage() {
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
m_gcCounter++;
|
m_gcCounter++;
|
||||||
if (m_gcCounter != 0) {
|
if (m_gcCounter != 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SizeT count = 0;
|
Vector<TextureIdentity> expiredTextures;
|
||||||
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end();) {
|
expiredTextures.reserve(m_aliveObjects.size());
|
||||||
auto current = it++;
|
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
|
||||||
if (current->second.expired()) {
|
if (it->second.expired()) {
|
||||||
m_pendingClears.erase(current->first);
|
expiredTextures.emplace_back(it->first);
|
||||||
m_aliveObjects.erase(current);
|
|
||||||
++count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
if (expiredTextures.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& identity : expiredTextures) {
|
||||||
|
ErasePendingClearsForTextureLocked(identity);
|
||||||
|
}
|
||||||
|
return expiredTextures.size();
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
#include "MG_Util/Math/VectorTypes.h"
|
#include "MG_Util/Math/VectorTypes.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
struct ClearFramebufferPayload {
|
struct ClearFramebufferPayload {
|
||||||
@@ -22,15 +24,99 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 stencil{};
|
Uint32 stencil{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A colour clear reaches us from one of glClear/ClearBufferfv, ClearBufferiv or
|
||||||
|
// ClearBufferuiv, and Vulkan reads VkClearColorValue's union according to the destination
|
||||||
|
// image's format rather than converting between the members - a float written where an
|
||||||
|
// integer format is expected is reinterpreted bit for bit, not rounded. Remember which entry
|
||||||
|
// point supplied the value so the member written when the clear is materialized matches.
|
||||||
|
enum class ClearColorEncoding : Uint8 { Float, Int, Uint };
|
||||||
|
|
||||||
struct ClearAttachmentPayload {
|
struct ClearAttachmentPayload {
|
||||||
GLbitfield mask = 0;
|
GLbitfield mask = 0;
|
||||||
FloatVec4 color = FloatVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
FloatVec4 color = FloatVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
ClearColorEncoding colorEncoding = ClearColorEncoding::Float;
|
||||||
|
IntVec4 colorInt = IntVec4(0, 0, 0, 0);
|
||||||
|
UintVec4 colorUint = UintVec4(0u, 0u, 0u, 0u);
|
||||||
Float depth = 1.0f;
|
Float depth = 1.0f;
|
||||||
Uint32 stencil = 0;
|
Uint32 stencil = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Builds the clear value for `payload` in the union member its encoding calls for.
|
||||||
|
// `formatLacksAlpha` applies GL's rule that a format without an alpha channel reads as one,
|
||||||
|
// expressed in whichever type matches (GL 4.6 core 15.2.3).
|
||||||
|
VkClearColorValue MakeVkClearColorValue(const ClearAttachmentPayload& payload, Bool formatLacksAlpha);
|
||||||
|
|
||||||
|
// Applies that same rule in place, for the paths that have to bake it into the payload before
|
||||||
|
// the destination is known.
|
||||||
|
void ForceOpaqueClearAlpha(ClearAttachmentPayload& payload);
|
||||||
|
|
||||||
|
// vkCmdClearColorImage names the image, so the driver applies the destination format's transfer
|
||||||
|
// function to whatever value it is handed. Every other write path in this backend goes through
|
||||||
|
// the UNORM twin view while GL_FRAMEBUFFER_SRGB is off (ResolveSrgbAttachmentWriteFormat) and
|
||||||
|
// therefore stores the raw value GL asked for. Rewrites `payload` to the linear colour whose
|
||||||
|
// encoding is that raw value, so a direct image clear of an sRGB destination agrees with them.
|
||||||
|
// A no-op for every other format, for integer clear encodings, and when GL is doing the
|
||||||
|
// encoding itself.
|
||||||
|
void PreCompensateSrgbClearColor(ClearAttachmentPayload& payload, VkFormat destinationFormat);
|
||||||
|
|
||||||
|
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 {
|
class VkClearManager {
|
||||||
public:
|
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();
|
Bool Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
@@ -38,13 +124,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void QueueClear(
|
void QueueClear(
|
||||||
const ClearAttachmentPayload& clearPayload,
|
const ClearAttachmentPayload& clearPayload,
|
||||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture);
|
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(MG_State::GLState::ITextureObject* texture);
|
||||||
Bool GetPendingClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload);
|
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(MG_State::GLState::ITextureObject* texture);
|
||||||
|
void PopPendingClear(const PendingClearKey& key);
|
||||||
|
void PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||||
SizeT CollectGarbage();
|
SizeT CollectGarbage();
|
||||||
private:
|
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;
|
Uint8 m_gcCounter = 0;
|
||||||
UnorderedMap<MG_State::GLState::ITextureObject*, ClearAttachmentPayload> m_pendingClears;
|
public:
|
||||||
UnorderedMap<MG_State::GLState::ITextureObject*, WeakPtr<MG_State::GLState::ITextureObject>> m_aliveObjects;
|
// Lock-free probe for the consecutive-draw fast path: any pending clear
|
||||||
|
// forces the full SetupDraw path (which materializes/consumes it).
|
||||||
|
Bool HasAnyPendingClears() const { return m_pendingCount.load(std::memory_order_relaxed) != 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::mutex m_mutex;
|
||||||
|
// Lock-free mirror of m_pendingClears.size(), maintained under m_mutex
|
||||||
|
// by every mutation. The per-draw probes (HasPendingClear/GetPending*)
|
||||||
|
// read it before taking the lock: during draw batches the pending set
|
||||||
|
// is almost always empty, so this turns several locked map probes per
|
||||||
|
// draw into one relaxed load.
|
||||||
|
std::atomic<Uint32> m_pendingCount{0};
|
||||||
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,22 +16,39 @@
|
|||||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
enum class TrackedAttachmentTarget : Uint8 {
|
enum class TrackedAttachmentTarget : Uint8 {
|
||||||
Texture,
|
Texture,
|
||||||
|
Renderbuffer,
|
||||||
SwapchainColor,
|
SwapchainColor,
|
||||||
SwapchainDepthStencil
|
SwapchainDepthStencil
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PendingClearAttachmentInfo {
|
struct PendingClearAttachmentInfo {
|
||||||
|
// Index into the render pass attachment descriptions (VkRenderPassBeginInfo::pClearValues space).
|
||||||
Uint32 attachmentIndex = 0;
|
Uint32 attachmentIndex = 0;
|
||||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
// 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 {
|
struct TrackedAttachmentLayoutInfo {
|
||||||
TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture;
|
TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture;
|
||||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
WeakPtr<MG_State::GLState::ITextureObject> texture;
|
||||||
|
// Identity-compare shortcut for the per-draw "does the active pass use
|
||||||
|
// this sampled texture" probe: comparing this against a LIVE texture's
|
||||||
|
// address needs no weak_ptr::lock (two refcount atomics per probe).
|
||||||
|
// May dangle once the texture dies - compare only, never dereference.
|
||||||
|
MG_State::GLState::ITextureObject* textureRaw = nullptr;
|
||||||
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
Uint32 textureMipLevel = 0;
|
Uint32 textureMipLevel = 0;
|
||||||
Uint32 swapchainImageIndex = 0;
|
Uint32 swapchainImageIndex = 0;
|
||||||
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
@@ -45,6 +62,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
|
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
|
||||||
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil);
|
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil);
|
||||||
|
IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent,
|
||||||
|
VkExtent2D swapchainExtent);
|
||||||
|
|
||||||
struct RenderPassEntry {
|
struct RenderPassEntry {
|
||||||
static inline VkDevice s_device;
|
static inline VkDevice s_device;
|
||||||
@@ -58,8 +77,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 attachmentCount = 0;
|
Uint32 attachmentCount = 0;
|
||||||
Uint32 colorAttachmentCount = 0;
|
Uint32 colorAttachmentCount = 0;
|
||||||
Bool hasDepthStencilAttachment = false;
|
Bool hasDepthStencilAttachment = false;
|
||||||
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
IntVec2 extent = {0, 0};
|
IntVec2 extent = {0, 0};
|
||||||
Uint32 subpass = 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() = default;
|
||||||
RenderPassEntry(const RenderPassEntry&) = delete;
|
RenderPassEntry(const RenderPassEntry&) = delete;
|
||||||
@@ -73,8 +96,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
std::swap(attachmentCount, that.attachmentCount);
|
std::swap(attachmentCount, that.attachmentCount);
|
||||||
std::swap(colorAttachmentCount, that.colorAttachmentCount);
|
std::swap(colorAttachmentCount, that.colorAttachmentCount);
|
||||||
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
|
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
|
||||||
|
std::swap(sampleCount, that.sampleCount);
|
||||||
std::swap(extent, that.extent);
|
std::swap(extent, that.extent);
|
||||||
std::swap(subpass, that.subpass);
|
std::swap(layers, that.layers);
|
||||||
|
std::swap(lastUsedFrame, that.lastUsedFrame);
|
||||||
}
|
}
|
||||||
RenderPassEntry(
|
RenderPassEntry(
|
||||||
Uint64 hash,
|
Uint64 hash,
|
||||||
@@ -86,7 +111,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 attachmentCount,
|
Uint32 attachmentCount,
|
||||||
Uint32 colorAttachmentCount,
|
Uint32 colorAttachmentCount,
|
||||||
Bool hasDepthStencilAttachment,
|
Bool hasDepthStencilAttachment,
|
||||||
IntVec2 extent, int subpass):
|
VkSampleCountFlagBits sampleCount,
|
||||||
|
IntVec2 extent, Uint32 layers):
|
||||||
hash(hash),
|
hash(hash),
|
||||||
renderPass(renderpass),
|
renderPass(renderpass),
|
||||||
framebuffer(framebuffer),
|
framebuffer(framebuffer),
|
||||||
@@ -96,8 +122,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
attachmentCount(attachmentCount),
|
attachmentCount(attachmentCount),
|
||||||
colorAttachmentCount(colorAttachmentCount),
|
colorAttachmentCount(colorAttachmentCount),
|
||||||
hasDepthStencilAttachment(hasDepthStencilAttachment),
|
hasDepthStencilAttachment(hasDepthStencilAttachment),
|
||||||
|
sampleCount(sampleCount),
|
||||||
extent(extent),
|
extent(extent),
|
||||||
subpass(subpass)
|
layers(layers)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
~RenderPassEntry() {
|
~RenderPassEntry() {
|
||||||
@@ -136,34 +163,200 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
class VkRenderPassManager {
|
class VkRenderPassManager {
|
||||||
public:
|
public:
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|
||||||
|
// Notified once per OnPresent sweep with every aged-out entry's VkRenderPass
|
||||||
|
// value: pipelines are hashed on the raw handle, and once destroyed the value
|
||||||
|
// may be recycled for an incompatible pass, so dependent caches must purge
|
||||||
|
// everything keyed on them before any new pass can be created (the sweep and
|
||||||
|
// the notification run back-to-back with no creation in between; observers
|
||||||
|
// compare the values, never dereference them). Batched so a mass-idle cohort
|
||||||
|
// (shader-pack switch, dimension exit) costs the observer one pipeline-cache
|
||||||
|
// scan, not one per dying pass. The wholesale paths
|
||||||
|
// (Shutdown/RecreateSwapchain) do not notify - their callers already drop
|
||||||
|
// every pipeline outright.
|
||||||
|
class IEvictionObserver {
|
||||||
|
public:
|
||||||
|
virtual ~IEvictionObserver() = default;
|
||||||
|
virtual void OnRenderPassesDestroyed(const Vector<VkRenderPass>& renderPasses) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
VkRenderPassManager(VkDevice device,
|
VkRenderPassManager(VkDevice device,
|
||||||
const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager,
|
VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config,
|
||||||
SwapchainObject& swapchainObject);
|
VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject);
|
||||||
~VkRenderPassManager();
|
~VkRenderPassManager();
|
||||||
|
|
||||||
|
// Observer may be null (no notifications). Not owned.
|
||||||
|
void SetEvictionObserver(IEvictionObserver* observer) { m_evictionObserver = observer; }
|
||||||
|
|
||||||
Bool Initialize();
|
Bool Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
HashType ComputeHash(
|
HashType ComputeHash(
|
||||||
const MG_State::GLState::FramebufferObject& fbo,
|
const MG_State::GLState::FramebufferObject& fbo,
|
||||||
Uint32 swapchainImageIndex,
|
Uint32 swapchainImageIndex,
|
||||||
Bool includePendingClear = true) const;
|
Bool includePendingClear = true,
|
||||||
RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex);
|
Bool includeDefaultFboDepthStencil = true);
|
||||||
|
// drawUsesDepthStencil: whether the operation about to run inside the pass
|
||||||
|
// reads or writes the depth/stencil buffer (depth test or stencil test
|
||||||
|
// enabled, or a depth/stencil clear). Only consulted for the DEFAULT
|
||||||
|
// framebuffer: EGL undefines its ancillary buffers at every swap, so a
|
||||||
|
// default-FBO pass whose draws provably never touch depth/stencil is
|
||||||
|
// created WITHOUT the depth attachment - on a tiler that skips the whole
|
||||||
|
// depth tile load AND store. The flavor only escalates: once a pass with
|
||||||
|
// depth is active, later depth-less draws keep using it, and a depth-using
|
||||||
|
// draw against a depth-less active pass resolves to a new (incompatible)
|
||||||
|
// entry, which the caller's compatibility check turns into a pass split;
|
||||||
|
// the new pass's depth loads DONT_CARE (content was undefined all along).
|
||||||
|
RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
||||||
|
Uint32 swapchainImageIndex,
|
||||||
|
Bool drawUsesDepthStencil = true);
|
||||||
|
void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||||
|
const MG_State::GLState::FramebufferObject& drawFbo);
|
||||||
|
void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload,
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||||
|
void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer);
|
||||||
|
// Frame boundary hook: ages the render-pass cache and evicts long-unused
|
||||||
|
// entries (their command buffers retired many frames ago).
|
||||||
|
void OnPresent();
|
||||||
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
|
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
|
||||||
static Bool EndRenderPass(VkCommandBuffer commandBuffer);
|
static Bool EndRenderPass(VkCommandBuffer commandBuffer);
|
||||||
static ActiveRenderPassInfo* GetActiveRenderPass();
|
static ActiveRenderPassInfo* GetActiveRenderPass();
|
||||||
private:
|
private:
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||||
|
VmaAllocator m_allocator = nullptr;
|
||||||
const VulkanRendererConfig& m_config;
|
const VulkanRendererConfig& m_config;
|
||||||
VkClearManager& m_clearManager;
|
VkClearManager& m_clearManager;
|
||||||
VkTextureManager& m_textureManager;
|
VkTextureManager& m_textureManager;
|
||||||
SwapchainObject& m_swapchainObject;
|
SwapchainObject& m_swapchainObject;
|
||||||
UnorderedMap<Uint64, RenderPassEntry> m_renderPasses;
|
UnorderedMap<Uint64, RenderPassEntry> m_renderPasses;
|
||||||
|
// Monotonic frame counter (bumped in OnPresent) for render-pass cache aging.
|
||||||
|
Uint64 m_frameCounter = 0;
|
||||||
|
IEvictionObserver* m_evictionObserver = nullptr;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Bumped whenever a renderbuffer backing is (re)created; consecutive-draw
|
||||||
|
// snapshots include it so an attachment respecify forces a re-resolve.
|
||||||
|
Uint64 GetRenderbufferImageEpoch() const { return m_renderbufferImageEpoch; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// Whether the memoized entry carries a depth/stencil attachment; a
|
||||||
|
// default-FBO resolution whose effective depth request differs must
|
||||||
|
// miss the memo (the depth-less/depth-full flavors hash differently).
|
||||||
|
Bool m_rpFastHadDepthStencil = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct RenderbufferResource {
|
||||||
|
// deadSinceFrame sentinel: the owning weak reference has not been observed
|
||||||
|
// expired. Dead resources age past every in-flight frame before Destroy
|
||||||
|
// (see CollectRenderbufferGarbage); the GPU may still reference the image
|
||||||
|
// for frames-in-flight frames after the GL object dies.
|
||||||
|
static constexpr Uint64 kNeverObservedDead = UINT64_MAX;
|
||||||
|
|
||||||
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
|
VkImage image = VK_NULL_HANDLE;
|
||||||
|
VmaAllocation allocation = nullptr;
|
||||||
|
VkImageView view = VK_NULL_HANDLE;
|
||||||
|
// UNORM reinterpretation of an sRGB image, used as the attachment view while
|
||||||
|
// GL_FRAMEBUFFER_SRGB is disabled (raw writes). Null for non-sRGB formats.
|
||||||
|
VkImageView unormTwinView = VK_NULL_HANDLE;
|
||||||
|
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
|
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
||||||
|
VkExtent2D extent = {0, 0};
|
||||||
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||||
|
Int samples = 0;
|
||||||
|
// m_frameCounter value at which the weak reference was first seen expired.
|
||||||
|
Uint64 deadSinceFrame = kNeverObservedDead;
|
||||||
|
|
||||||
|
void Destroy(VkDevice device, VmaAllocator allocator);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Public so the renderer's blit/copy/readback bindings can source renderbuffer
|
||||||
|
// attachments the same way texture attachments go through the texture manager.
|
||||||
|
RenderbufferResource* GetOrCreateRenderbufferResource(
|
||||||
|
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||||
|
Bool GetPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer,
|
||||||
|
ClearAttachmentPayload& outPayload) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PendingRenderbufferClear {
|
||||||
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
|
ClearAttachmentPayload payload{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// A superseded renderbuffer backing (glRenderbufferStorage respecify) parked
|
||||||
|
// until enough frame boundaries have passed that no in-flight command buffer
|
||||||
|
// can still reference it; destroyed in OnPresent (see RetireAgeFrames).
|
||||||
|
struct DeferredRenderbufferRelease {
|
||||||
|
VkImage image = VK_NULL_HANDLE;
|
||||||
|
VmaAllocation allocation = nullptr;
|
||||||
|
VkImageView view = VK_NULL_HANDLE;
|
||||||
|
VkImageView unormTwinView = VK_NULL_HANDLE;
|
||||||
|
Uint64 deferredAtFrame = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Node-based std::unordered_map, deliberately not FastSTL's open-addressing UnorderedMap:
|
||||||
|
// callers cache a RenderbufferResource* - or a bare &resource->layout - and then make further
|
||||||
|
// calls that touch this map. BlitFramebuffer is the one that bit: it resolves the source and
|
||||||
|
// destination colour bindings (ResolveColorBlitBinding caches &rbResource->layout), then
|
||||||
|
// materializes the source's pending clear, which looks that same resource up again. FastSTL's
|
||||||
|
// operator[] runs its load-factor check before find_key and reallocates the whole bucket array
|
||||||
|
// when occupancy crosses it, so even a plain lookup relocates every element; erase only
|
||||||
|
// tombstones and never decrements the occupancy, so the doubling keeps firing. After a
|
||||||
|
// relocation the cached pointer names freed storage still holding the pre-clear
|
||||||
|
// VK_IMAGE_LAYOUT_UNDEFINED, and BlitFramebuffer bails out at "source image layout is
|
||||||
|
// undefined", silently dropping the blit - renderbuffers_storage_multisample read back zero
|
||||||
|
// instead of the clear colour on exactly the iterations that grew the table.
|
||||||
|
//
|
||||||
|
// Reordering the materialize ahead of the resolves - the fix ReadPixels got - does not cover
|
||||||
|
// this: the destination resolve still runs after the source pointer is taken. The depth blit,
|
||||||
|
// GetOrCreateRenderPass's depthRenderbufferResource and ReadDepthStencilPixels cache the same
|
||||||
|
// kind of pointer, so the invariant belongs in the container rather than in a per-call-site
|
||||||
|
// ordering rule. m_textureResources is node-based for the same reason. This buys stability
|
||||||
|
// across rehash and insert only - erase still invalidates the erased element, which is safe
|
||||||
|
// here because a renderbuffer that is an FBO attachment is held alive by that attachment.
|
||||||
|
std::unordered_map<MG_State::GLState::RenderbufferObject*, RenderbufferResource> m_renderbufferResources;
|
||||||
|
UnorderedMap<MG_State::GLState::RenderbufferObject*, PendingRenderbufferClear> m_pendingRenderbufferClears;
|
||||||
|
Vector<DeferredRenderbufferRelease> m_deferredRenderbufferReleases;
|
||||||
|
// Supported sample counts per attachment format, so per-draw resource lookups
|
||||||
|
// do not repeat vkGetPhysicalDeviceImageFormatProperties.
|
||||||
|
UnorderedMap<VkFormat, VkSampleCountFlags> m_attachmentSampleCountsByFormat;
|
||||||
|
|
||||||
|
Bool HasPendingRenderbufferClear(
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment) const;
|
||||||
|
void CollectRenderbufferGarbage();
|
||||||
|
// Frame-boundary margin after which a resource last referenced by a retired
|
||||||
|
// GL object (or superseded backing) is provably past every in-flight frame.
|
||||||
|
Uint64 RetireAgeFrames() const;
|
||||||
|
void DeferRenderbufferBackingRelease(RenderbufferResource& resource);
|
||||||
|
void CollectDeferredRenderbufferReleases(Bool destroyAll);
|
||||||
|
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
static inline ActiveRenderPassInfo s_activeRenderPass{};
|
static inline ActiveRenderPassInfo s_activeRenderPass{};
|
||||||
static inline Bool s_hasActiveRenderPass = false;
|
static inline Bool s_hasActiveRenderPass = false;
|
||||||
static inline VkClearManager* s_clearManager = nullptr;
|
static inline VkClearManager* s_clearManager = nullptr;
|
||||||
static inline VkTextureManager* s_textureManager = nullptr;
|
static inline VkTextureManager* s_textureManager = nullptr;
|
||||||
static inline SwapchainObject* s_swapchainObject = nullptr;
|
static inline SwapchainObject* s_swapchainObject = nullptr;
|
||||||
|
static inline VkRenderPassManager* s_renderPassManager = nullptr;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -51,6 +51,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Float ResolveEffectiveMinLod(const MG_State::GLState::SamplerObject& sampler, Float effectiveMaxLod) {
|
Float ResolveEffectiveMinLod(const MG_State::GLState::SamplerObject& sampler, Float effectiveMaxLod) {
|
||||||
return std::min(sampler.GetMinLod(), effectiveMaxLod);
|
return std::min(sampler.GetMinLod(), effectiveMaxLod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A single-level view can only ever deliver the base level, but the LOD clamp must not be
|
||||||
|
// collapsed to exactly 0: both GL and Vulkan pick magFilter over minFilter from the
|
||||||
|
// *clamped* lambda, so maxLod = 0 would make every fragment magnify and quietly retire the
|
||||||
|
// min filter. 0.25 is the value VkSamplerCreateInfo's own note prescribes for emulating
|
||||||
|
// GL's non-mipmapped minification - large enough for lambda to stay positive, small enough
|
||||||
|
// that a NEAREST mip mode still rounds down to level 0. Clamped rather than assigned, so a
|
||||||
|
// texture whose GL_TEXTURE_MAX_LOD really is 0 keeps magnifying as GL says it must.
|
||||||
|
Float ResolveSingleLevelMaxLod(const MG_State::GLState::SamplerObject& sampler, Bool singleLevelView) {
|
||||||
|
const Float maxLod = ResolveEffectiveMaxLod(sampler);
|
||||||
|
return singleLevelView ? std::min(maxLod, 0.25f) : maxLod;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Bool VkSamplerManager::Initialize(const InitInfo& initInfo) {
|
Bool VkSamplerManager::Initialize(const InitInfo& initInfo) {
|
||||||
@@ -58,11 +70,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
m_device = initInfo.device;
|
m_device = initInfo.device;
|
||||||
m_config = initInfo.config;
|
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,
|
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_config != nullptr,
|
||||||
"VkSamplerManager::Initialize failed: invalid initialization info");
|
"VkSamplerManager::Initialize failed: invalid initialization info");
|
||||||
return true;
|
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() {
|
void VkSamplerManager::Shutdown() {
|
||||||
for (auto& [_, sampler] : m_samplers) {
|
for (auto& [_, sampler] : m_samplers) {
|
||||||
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
|
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
|
||||||
@@ -74,13 +101,44 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
m_device = VK_NULL_HANDLE;
|
m_device = VK_NULL_HANDLE;
|
||||||
m_config = nullptr;
|
m_config = nullptr;
|
||||||
|
m_frameBoundaryCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkSamplerManager::OnFrameBoundary() {
|
||||||
|
++m_frameBoundaryCounter;
|
||||||
|
|
||||||
|
// Sweep occasionally; destroy samplers whose last use is far past every
|
||||||
|
// in-flight frame. Destroy and erase must stay atomic, or Shutdown would
|
||||||
|
// double-free the handle; an evicted key that recurs simply re-creates
|
||||||
|
// its sampler on the next miss.
|
||||||
|
constexpr Uint64 kSweepInterval = 256;
|
||||||
|
constexpr Uint64 kRetireAgeBoundaries = 1024;
|
||||||
|
if ((m_frameBoundaryCounter % kSweepInterval) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = m_samplers.begin(); it != m_samplers.end();) {
|
||||||
|
auto& entry = it->second;
|
||||||
|
if (m_frameBoundaryCounter - entry.lastUsedFrameBoundary > kRetireAgeBoundaries) {
|
||||||
|
if (m_device != VK_NULL_HANDLE && entry.handle != VK_NULL_HANDLE) {
|
||||||
|
vkDestroySampler(m_device, entry.handle, nullptr);
|
||||||
|
}
|
||||||
|
it = m_samplers.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture) const {
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
|
Bool forceNearestFiltering, Bool singleLevelView) const {
|
||||||
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config->CacheVersion));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config->CacheVersion));
|
||||||
|
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &forceNearestFiltering, sizeof(forceNearestFiltering)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &singleLevelView, sizeof(singleLevelView)));
|
||||||
|
|
||||||
const auto minFilter = sampler.GetMinFilter();
|
const auto minFilter = sampler.GetMinFilter();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minFilter, sizeof(minFilter)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &minFilter, sizeof(minFilter)));
|
||||||
const auto magFilter = sampler.GetMagFilter();
|
const auto magFilter = sampler.GetMagFilter();
|
||||||
@@ -93,15 +151,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapT, sizeof(wrapT)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapT, sizeof(wrapT)));
|
||||||
const auto wrapR = sampler.GetWrapR();
|
const auto wrapR = sampler.GetWrapR();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapR, sizeof(wrapR)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapR, sizeof(wrapR)));
|
||||||
const auto maxLod = ResolveEffectiveMaxLod(sampler);
|
const auto maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
|
||||||
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
||||||
const auto lodBias = sampler.GetLodBias();
|
const auto lodBias = sampler.GetLodBias();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
|
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();
|
const auto compareMode = sampler.GetCompareMode();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
||||||
const auto compareFunc = ResolveCompareFunc(sampler, texture);
|
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
||||||
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
||||||
@@ -109,27 +172,44 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture) {
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
const Uint64 key = BuildSamplerKey(sampler, texture);
|
Bool forceNearestFiltering, Uint32 viewLevelCount) {
|
||||||
|
// A view that exposes a single mip level has no second level to blend with, so GL's
|
||||||
|
// *_MIPMAP_* minification filters degenerate to plain filtering on the base level -
|
||||||
|
// sampling is unchanged by pinning the Vulkan sampler to NEAREST mip mode at LOD 0.
|
||||||
|
// It is not cosmetic: MobileGL backs such a view with a fully allocated mip chain whose
|
||||||
|
// tail is never written, and a LINEAR mip mode lets the texture unit issue the level+1
|
||||||
|
// fetch anyway. On Adreno that fetch lands in uninitialized UBWC pages (or past the
|
||||||
|
// allocation for a genuinely single-level image) and faults the GPU - the same failure
|
||||||
|
// the default-framebuffer blit shader had to work around with an explicit-LOD sample.
|
||||||
|
const Bool singleLevelView = viewLevelCount == 1;
|
||||||
|
const Uint64 key = BuildSamplerKey(sampler, texture, forceNearestFiltering, singleLevelView);
|
||||||
auto it = m_samplers.find(key);
|
auto it = m_samplers.find(key);
|
||||||
if (it != m_samplers.end()) {
|
if (it != m_samplers.end()) {
|
||||||
|
it->second.lastUsedFrameBoundary = m_frameBoundaryCounter;
|
||||||
return it->second.handle;
|
return it->second.handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkSamplerCreateInfo samplerInfo{};
|
VkSamplerCreateInfo samplerInfo{};
|
||||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||||
samplerInfo.magFilter = ToVkFilter(sampler.GetMagFilter());
|
samplerInfo.magFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMagFilter());
|
||||||
samplerInfo.minFilter = ToVkFilter(sampler.GetMinFilter());
|
samplerInfo.minFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMinFilter());
|
||||||
samplerInfo.mipmapMode = ToVkMipmapMode(sampler.GetMipmapMode());
|
samplerInfo.mipmapMode = (forceNearestFiltering || singleLevelView)
|
||||||
|
? VK_SAMPLER_MIPMAP_MODE_NEAREST
|
||||||
|
: ToVkMipmapMode(sampler.GetMipmapMode());
|
||||||
samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS());
|
samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS());
|
||||||
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
||||||
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
||||||
samplerInfo.mipLodBias = sampler.GetLodBias();
|
samplerInfo.mipLodBias = sampler.GetLodBias();
|
||||||
samplerInfo.anisotropyEnable = VK_FALSE;
|
// Must use the same resolver as BuildSamplerKey - a divergence would either collide two
|
||||||
samplerInfo.maxAnisotropy = 1.0f;
|
// 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.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
||||||
samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
|
samplerInfo.compareOp = ToVkCompareOp(sampler.GetSamplerCompareFunc());
|
||||||
samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler);
|
// Must match BuildSamplerKey's resolution exactly.
|
||||||
|
samplerInfo.maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
|
||||||
samplerInfo.minLod = ResolveEffectiveMinLod(sampler, samplerInfo.maxLod);
|
samplerInfo.minLod = ResolveEffectiveMinLod(sampler, samplerInfo.maxLod);
|
||||||
samplerInfo.borderColor = ResolveVkBorderColor(sampler, texture);
|
samplerInfo.borderColor = ResolveVkBorderColor(sampler, texture);
|
||||||
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||||
@@ -141,6 +221,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
entry.handle = vkSampler;
|
entry.handle = vkSampler;
|
||||||
entry.externalIndex = sampler.GetExternalIndex();
|
entry.externalIndex = sampler.GetExternalIndex();
|
||||||
entry.version = sampler.GetVersion();
|
entry.version = sampler.GetVersion();
|
||||||
|
entry.lastUsedFrameBoundary = m_frameBoundaryCounter;
|
||||||
m_samplers[key] = entry;
|
m_samplers[key] = entry;
|
||||||
return vkSampler;
|
return vkSampler;
|
||||||
}
|
}
|
||||||
@@ -200,24 +281,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
VkBorderColor VkSamplerManager::ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture) {
|
const MG_State::GLState::ITextureObject& texture) {
|
||||||
if (!UsesBorderColor(sampler)) {
|
if (!UsesBorderColor(sampler)) {
|
||||||
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& borderColor = texture.GetBorderColor();
|
// Border colour is sampler state: a bound sampler object supplies its own, and a texture
|
||||||
|
// with none reaches the very same value through the sampler object it owns.
|
||||||
|
const auto& borderColor = sampler.GetBorderColor();
|
||||||
const Bool isDepthTexture = IsDepthTextureFormat(texture.GetFormat());
|
const Bool isDepthTexture = IsDepthTextureFormat(texture.GetFormat());
|
||||||
|
|
||||||
if (isDepthTexture) {
|
if (isDepthTexture) {
|
||||||
|
|||||||
@@ -24,35 +24,67 @@ public:
|
|||||||
struct InitInfo {
|
struct InitInfo {
|
||||||
VkDevice device = VK_NULL_HANDLE;
|
VkDevice device = VK_NULL_HANDLE;
|
||||||
const VulkanRendererConfig* config = nullptr;
|
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);
|
Bool Initialize(const InitInfo& initInfo);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
|
// viewLevelCount is the mip-level count of the image view this sampler will be paired
|
||||||
|
// with; 0 means "unknown, do not narrow". See GetOrCreateSampler for why it matters.
|
||||||
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture);
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
|
Bool forceNearestFiltering = false,
|
||||||
|
Uint32 viewLevelCount = 0);
|
||||||
|
// Frame boundary hook: ages the sampler cache and destroys samplers not used
|
||||||
|
// for many frames. The key hashes continuous float state (lodBias, LOD clamps,
|
||||||
|
// anisotropy), so an app animating those would otherwise mint an unbounded
|
||||||
|
// stream of never-destroyed VkSamplers and eventually exhaust the device's
|
||||||
|
// maxSamplerAllocationCount. A sampler idle for over a thousand frame
|
||||||
|
// boundaries cannot be referenced by any in-flight command buffer (frames in
|
||||||
|
// flight are single digits), and every descriptor set the GPU consumes is
|
||||||
|
// written that same frame with live handles (the per-binding resolve memo and
|
||||||
|
// descriptor-set reuse are both frame-reset), so destruction here needs no
|
||||||
|
// fence wait. Self-gated: one counter bump and compare except on sweep
|
||||||
|
// boundaries.
|
||||||
|
void OnFrameBoundary();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct SamplerCacheEntry {
|
struct SamplerCacheEntry {
|
||||||
VkSampler handle = VK_NULL_HANDLE;
|
VkSampler handle = VK_NULL_HANDLE;
|
||||||
Uint externalIndex = 0;
|
Uint externalIndex = 0;
|
||||||
Uint16 version = 0;
|
Uint16 version = 0;
|
||||||
|
// Frame boundary of the last cache hit; entries idle past the
|
||||||
|
// OnFrameBoundary retirement age have their VkSampler destroyed.
|
||||||
|
Uint64 lastUsedFrameBoundary = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture) const;
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
|
Bool forceNearestFiltering, Bool singleLevelView) const;
|
||||||
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
||||||
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
||||||
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
||||||
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
|
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,
|
static VkBorderColor ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture);
|
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;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
const VulkanRendererConfig* m_config = nullptr;
|
const VulkanRendererConfig* m_config = nullptr;
|
||||||
|
Bool m_samplerAnisotropySupported = false;
|
||||||
|
Float m_maxSamplerAnisotropy = 1.0f;
|
||||||
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
||||||
|
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
||||||
|
Uint64 m_frameBoundaryCounter = 0;
|
||||||
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
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,14 +12,43 @@
|
|||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace MobileGL::MG_State::GLState {
|
namespace MobileGL::MG_State::GLState {
|
||||||
class ITextureObject;
|
class ITextureObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
enum class SamplerNumericDomain : Uint8;
|
||||||
|
|
||||||
class VkTextureManager {
|
class VkTextureManager {
|
||||||
public:
|
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; }
|
||||||
|
// Bumped whenever any tracked texture resource is erased; cached
|
||||||
|
// TextureResource pointers are valid only while this is unchanged.
|
||||||
|
Uint64 GetResourceEraseEpoch() const { return m_resourceEraseEpoch; }
|
||||||
|
|
||||||
|
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 {
|
struct InitInfo {
|
||||||
VkDevice device = VK_NULL_HANDLE;
|
VkDevice device = VK_NULL_HANDLE;
|
||||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||||
@@ -27,15 +56,107 @@ public:
|
|||||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||||
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
||||||
Uint32 frameCount = 0;
|
Uint32 frameCount = 0;
|
||||||
|
// VK_KHR_image_format_list is enabled: MUTABLE_FORMAT images can name the exact set of
|
||||||
|
// formats they will be viewed as, which is what lets a tiler keep them compressed.
|
||||||
|
Bool imageFormatListSupported = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextureResource {
|
struct TextureResource {
|
||||||
|
struct AttachmentViewKey {
|
||||||
|
Uint32 mipLevel = 0;
|
||||||
|
Uint32 baseArrayLayer = 0;
|
||||||
|
Uint32 layerCount = 1;
|
||||||
|
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
// May differ from the image format: sRGB images attach through their UNORM
|
||||||
|
// twin while GL_FRAMEBUFFER_SRGB is disabled.
|
||||||
|
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
|
||||||
|
|
||||||
|
Bool operator==(const AttachmentViewKey& other) const {
|
||||||
|
return mipLevel == other.mipLevel &&
|
||||||
|
baseArrayLayer == other.baseArrayLayer &&
|
||||||
|
layerCount == other.layerCount &&
|
||||||
|
viewType == other.viewType &&
|
||||||
|
viewFormat == other.viewFormat;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewFormat)) +
|
||||||
|
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;
|
VkImage image = VK_NULL_HANDLE;
|
||||||
VmaAllocation allocation = nullptr;
|
VmaAllocation allocation = nullptr;
|
||||||
VkImageView fullView = VK_NULL_HANDLE;
|
VkImageView fullView = VK_NULL_HANDLE;
|
||||||
VkImageView sampledView = VK_NULL_HANDLE;
|
VkImageView sampledView = VK_NULL_HANDLE;
|
||||||
Vector<VkImageView> perMipViews;
|
Vector<VkImageView> perMipViews;
|
||||||
Vector<VkImageView> perMipSampledViews;
|
Vector<VkImageView> perMipSampledViews;
|
||||||
|
UnorderedMap<AttachmentViewKey, VkImageView, AttachmentViewKeyHash> attachmentViews;
|
||||||
|
UnorderedMap<SampledImageViewKey, VkImageView, SampledImageViewKeyHash> alternateSampledViews;
|
||||||
|
UnorderedMap<StorageImageViewKey, VkImageView, StorageImageViewKeyHash> storageImageViews;
|
||||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
VkExtent2D extent = {0, 0};
|
VkExtent2D extent = {0, 0};
|
||||||
Uint32 depth = 1;
|
Uint32 depth = 1;
|
||||||
@@ -46,7 +167,34 @@ public:
|
|||||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
||||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
VkImageCreateFlags imageCreateFlags = 0;
|
||||||
|
// Usage the live image was created with. STORAGE is only requested for textures that
|
||||||
|
// have actually been bound to a GL image unit, because on Adreno a storage-capable
|
||||||
|
// image loses UBWC bandwidth compression; a later image binding upgrades the usage
|
||||||
|
// and recreates the image, so the resolved usage has to be part of the compatibility
|
||||||
|
// check that decides whether the existing image can be kept.
|
||||||
|
VkImageUsageFlags usageFlags = 0;
|
||||||
|
// True once this image was (re)resolved while the texture was already marked as an
|
||||||
|
// image-unit texture. Distinguishes "not upgraded yet" from "cannot be upgraded"
|
||||||
|
// (a format whose optimalTilingFeatures lack STORAGE_IMAGE never gains the bit), so
|
||||||
|
// NeedsStorageImagePreparation cannot ask for a recreate that will never happen.
|
||||||
|
Bool storageUsageResolved = false;
|
||||||
Uint16 syncedTextureParamsVersion = 0;
|
Uint16 syncedTextureParamsVersion = 0;
|
||||||
|
// Recording generation (VkTextureManager::GetRecordingGeneration) of the last
|
||||||
|
// command referencing this image that was recorded into the CURRENT frame
|
||||||
|
// command buffer. An image untouched by the open recording may have its
|
||||||
|
// out-of-pass work (deferred clears, sampled-layout transitions) recorded
|
||||||
|
// into the frame's PRE command buffer - which executes strictly before the
|
||||||
|
// frame's commands - instead of splitting the active render pass.
|
||||||
|
Uint64 lastRecordingGeneration = 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() = default;
|
||||||
TextureResource(const TextureResource&) = delete;
|
TextureResource(const TextureResource&) = delete;
|
||||||
@@ -57,6 +205,9 @@ public:
|
|||||||
std::swap(this->sampledView, that.sampledView);
|
std::swap(this->sampledView, that.sampledView);
|
||||||
std::swap(this->perMipViews, that.perMipViews);
|
std::swap(this->perMipViews, that.perMipViews);
|
||||||
std::swap(this->perMipSampledViews, that.perMipSampledViews);
|
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->layout, that.layout);
|
||||||
std::swap(this->extent, that.extent);
|
std::swap(this->extent, that.extent);
|
||||||
std::swap(this->depth, that.depth);
|
std::swap(this->depth, that.depth);
|
||||||
@@ -67,7 +218,14 @@ public:
|
|||||||
std::swap(this->format, that.format);
|
std::swap(this->format, that.format);
|
||||||
std::swap(this->aspect, that.aspect);
|
std::swap(this->aspect, that.aspect);
|
||||||
std::swap(this->viewType, that.viewType);
|
std::swap(this->viewType, that.viewType);
|
||||||
|
std::swap(this->sampleCount, that.sampleCount);
|
||||||
|
std::swap(this->imageCreateFlags, that.imageCreateFlags);
|
||||||
|
std::swap(this->usageFlags, that.usageFlags);
|
||||||
|
std::swap(this->storageUsageResolved, that.storageUsageResolved);
|
||||||
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
||||||
|
std::swap(this->lastRecordingGeneration, that.lastRecordingGeneration);
|
||||||
|
std::swap(this->syncedContentVersion, that.syncedContentVersion);
|
||||||
|
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reset() {
|
void Reset() {
|
||||||
@@ -87,6 +245,21 @@ public:
|
|||||||
vkDestroyImageView(s_device, sampledView, nullptr);
|
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) {
|
if (image != VK_NULL_HANDLE && allocation != nullptr) {
|
||||||
vmaDestroyImage(s_allocator, image, allocation);
|
vmaDestroyImage(s_allocator, image, allocation);
|
||||||
}
|
}
|
||||||
@@ -94,6 +267,9 @@ public:
|
|||||||
sampledView = VK_NULL_HANDLE;
|
sampledView = VK_NULL_HANDLE;
|
||||||
perMipViews.clear();
|
perMipViews.clear();
|
||||||
perMipSampledViews.clear();
|
perMipSampledViews.clear();
|
||||||
|
attachmentViews.clear();
|
||||||
|
alternateSampledViews.clear();
|
||||||
|
storageImageViews.clear();
|
||||||
image = VK_NULL_HANDLE;
|
image = VK_NULL_HANDLE;
|
||||||
allocation = nullptr;
|
allocation = nullptr;
|
||||||
layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
@@ -106,7 +282,13 @@ public:
|
|||||||
format = VK_FORMAT_UNDEFINED;
|
format = VK_FORMAT_UNDEFINED;
|
||||||
aspect = VK_IMAGE_ASPECT_NONE;
|
aspect = VK_IMAGE_ASPECT_NONE;
|
||||||
viewType = VK_IMAGE_VIEW_TYPE_2D;
|
viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
imageCreateFlags = 0;
|
||||||
|
usageFlags = 0;
|
||||||
|
storageUsageResolved = false;
|
||||||
syncedTextureParamsVersion = 0;
|
syncedTextureParamsVersion = 0;
|
||||||
|
syncedContentVersion = 0;
|
||||||
|
syncedMipLevelCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
~TextureResource() {
|
~TextureResource() {
|
||||||
@@ -120,11 +302,21 @@ public:
|
|||||||
Bool Initialize(const InitInfo& initInfo);
|
Bool Initialize(const InitInfo& initInfo);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
void BeginFrame(Uint32 frameIndex);
|
void BeginFrame(Uint32 frameIndex);
|
||||||
|
// Drains every frame slot's deferred image/view releases. Only valid when
|
||||||
|
// the caller has proven every queue submission complete; used by the
|
||||||
|
// present-less frame-boundary drain.
|
||||||
|
void CollectAllDeferredReleases();
|
||||||
|
|
||||||
TextureResource* SyncTextureAndGetDescriptor(
|
TextureResource* SyncTextureAndGetDescriptor(
|
||||||
MG_State::GLState::ITextureObject& texture);
|
MG_State::GLState::ITextureObject& texture);
|
||||||
VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
|
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 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 UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout);
|
||||||
void UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer,
|
void UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer,
|
||||||
MG_State::GLState::ITextureObject* texture,
|
MG_State::GLState::ITextureObject* texture,
|
||||||
@@ -133,7 +325,45 @@ public:
|
|||||||
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||||
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture);
|
||||||
|
|
||||||
|
// Recording-generation bookkeeping for the pre-pass command stream. The
|
||||||
|
// generation advances every time the frame command buffer (re)begins
|
||||||
|
// recording; a resource whose stamp does not match was not referenced by
|
||||||
|
// any command in the open recording, so its out-of-pass work may safely
|
||||||
|
// execute ahead of the whole recording (in the pre command buffer).
|
||||||
|
void AdvanceRecordingGeneration() { ++m_recordingGeneration; }
|
||||||
|
void StampResourceRecordingUse(TextureResource& resource) const {
|
||||||
|
resource.lastRecordingGeneration = m_recordingGeneration;
|
||||||
|
}
|
||||||
|
// Map-lookup variant for callers that only hold the GL texture object.
|
||||||
|
void StampTextureRecordingUse(MG_State::GLState::ITextureObject* texture);
|
||||||
|
Bool WasTouchedThisRecording(const TextureResource& resource) const {
|
||||||
|
return resource.lastRecordingGeneration == m_recordingGeneration;
|
||||||
|
}
|
||||||
|
// Records that this texture is bound to a GL image unit, so its image must carry
|
||||||
|
// VK_IMAGE_USAGE_STORAGE_BIT. Must be called before NeedsStorageImagePreparation, and
|
||||||
|
// therefore before the render pass is committed: an image that has to be upgraded is
|
||||||
|
// recreated, which is illegal inside a render pass. Sticky for the texture's lifetime -
|
||||||
|
// GL lets an image binding come and go, and re-creating the image every time it does
|
||||||
|
// would cost far more than the compression it wins back.
|
||||||
|
void MarkStorageImageTexture(MG_State::GLState::ITextureObject& texture);
|
||||||
|
// True when this texture is marked but its live image predates the mark, i.e. the next sync
|
||||||
|
// will recreate it with STORAGE usage and copy the old contents forward. Callers use this to
|
||||||
|
// submit their pending recording first, so that copy cannot read pre-flush content.
|
||||||
|
Bool NeedsStorageUsageUpgrade(MG_State::GLState::ITextureObject& texture) const;
|
||||||
|
// The same ordering question for the other recreate-and-preserve trigger: true when this
|
||||||
|
// texture's live image carries a shorter mip chain than a full one, so defining the missing
|
||||||
|
// levels recreates it and copies the old contents forward.
|
||||||
|
Bool NeedsMipChainGrowth(MG_State::GLState::ITextureObject& texture) const;
|
||||||
|
// 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 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,
|
static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout,
|
||||||
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
|
VkImageLayout newLayout, VkPipelineStageFlags srcStageMask,
|
||||||
@@ -143,7 +373,36 @@ public:
|
|||||||
Uint32 layerCount = 1);
|
Uint32 layerCount = 1);
|
||||||
|
|
||||||
SizeT CollectGarbage();
|
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:
|
private:
|
||||||
|
// Bumped in SyncTextureResource right after vmaCreateImage(texture). See GetTextureImageEpoch().
|
||||||
|
Uint64 m_textureImageEpoch = 1;
|
||||||
|
// See AdvanceRecordingGeneration. Starts above every resource's default
|
||||||
|
// stamp of 0 so a fresh resource counts as untouched.
|
||||||
|
Uint64 m_recordingGeneration = 1;
|
||||||
|
|
||||||
Bool SyncTexture(MG_State::GLState::ITextureObject &texture,
|
Bool SyncTexture(MG_State::GLState::ITextureObject &texture,
|
||||||
TextureResource &outResource);
|
TextureResource &outResource);
|
||||||
@@ -154,8 +413,10 @@ private:
|
|||||||
Bool SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource);
|
Bool SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource);
|
||||||
VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect,
|
VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect,
|
||||||
VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount,
|
VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount,
|
||||||
|
Uint32 baseArrayLayer,
|
||||||
Uint32 layerCount,
|
Uint32 layerCount,
|
||||||
const VkComponentMapping* components = nullptr) const;
|
const VkComponentMapping* components = nullptr,
|
||||||
|
VkImageUsageFlags viewUsage = 0) const;
|
||||||
Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture,
|
Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture,
|
||||||
TextureUploadTarget uploadTarget,
|
TextureUploadTarget uploadTarget,
|
||||||
TextureResource &outResource);
|
TextureResource &outResource);
|
||||||
@@ -172,18 +433,83 @@ private:
|
|||||||
void DeferViewRelease(VkImageView view);
|
void DeferViewRelease(VkImageView view);
|
||||||
void CollectDeferredReleases(Uint32 frameIndex);
|
void CollectDeferredReleases(Uint32 frameIndex);
|
||||||
void DestroyDeferredReleases();
|
void DestroyDeferredReleases();
|
||||||
|
// Frees the fence/command buffer/staging buffer of every in-flight texture
|
||||||
|
// upload whose fence has signaled (submission order = completion order on
|
||||||
|
// the single queue, so the scan stops at the first still-pending entry).
|
||||||
|
// waitAll blocks on every entry - Shutdown's drain.
|
||||||
|
void ReclaimCompletedUploads(Bool waitAll = false);
|
||||||
|
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
||||||
|
void EraseTrackedTexture(const TextureIdentity& identity);
|
||||||
|
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
|
||||||
|
SizeT PruneDeadTextures();
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||||
VmaAllocator m_allocator = nullptr;
|
VmaAllocator m_allocator = nullptr;
|
||||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||||
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
|
||||||
|
Bool m_imageFormatListSupported = false;
|
||||||
Uint32 m_currentFrameIndex = 0;
|
Uint32 m_currentFrameIndex = 0;
|
||||||
|
|
||||||
Uint8 m_gcCounter = 0;
|
Uint8 m_gcCounter = 0;
|
||||||
UnorderedMap<MG_State::GLState::ITextureObject*, WeakPtr<MG_State::GLState::ITextureObject>> m_aliveObjects;
|
// Frame-boundary GC gate: counts BeginFrame calls, not draws, so texture churn
|
||||||
UnorderedMap<MG_State::GLState::ITextureObject*, TextureResource> m_textureResources;
|
// through non-draw paths (FBO clears, readbacks) still reaches the prune.
|
||||||
|
Uint32 m_gcFrameCounter = 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;
|
||||||
|
// Cross-draw sampled-texture memo: the same few textures (atlas, lightmap)
|
||||||
|
// are resolved on every draw, so cache their resource pointers and skip the
|
||||||
|
// alive/resource map lookups. Node-based std::unordered_map keeps the
|
||||||
|
// pointees stable across inserts; erases bump m_resourceEraseEpoch, which
|
||||||
|
// every memo entry must match. SyncTexture still runs on memo hits, so
|
||||||
|
// content/param freshness is unaffected. A dead-then-reused texture address
|
||||||
|
// cannot false-hit: the new object carries a new lifetime id.
|
||||||
|
struct SyncedTextureMemoEntry {
|
||||||
|
const MG_State::GLState::ITextureObject* texture = nullptr;
|
||||||
|
Uint64 lifetimeId = 0;
|
||||||
|
Uint64 eraseEpoch = 0;
|
||||||
|
TextureResource* resource = nullptr;
|
||||||
|
};
|
||||||
|
static constexpr Uint32 kSyncedTextureMemoSize = 8;
|
||||||
|
SyncedTextureMemoEntry m_syncedTextureMemo[kSyncedTextureMemoSize];
|
||||||
|
Uint32 m_syncedTextureMemoNext = 0;
|
||||||
|
Uint64 m_resourceEraseEpoch = 1;
|
||||||
|
// 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;
|
||||||
|
// Formats whose 3D images refused VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT. Per format+usage,
|
||||||
|
// exactly like the mutable-format verdict above, so it is answered at image creation and
|
||||||
|
// remembered rather than probed once globally.
|
||||||
|
std::unordered_set<VkFormat> m_2dArrayCompatibleUnsupported;
|
||||||
|
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||||
|
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
||||||
|
// Textures that have been bound to a GL image unit (see MarkStorageImageTexture).
|
||||||
|
std::unordered_set<TextureIdentity, TextureIdentityHash> m_storageImageTextures;
|
||||||
|
// Supported multisample counts per format, so repeat texture syncs do not
|
||||||
|
// re-query vkGetPhysicalDeviceImageFormatProperties.
|
||||||
|
std::unordered_map<VkFormat, VkSampleCountFlags> m_multisampleCountsByFormat;
|
||||||
Vector<Vector<TextureResource>> m_deferredReleases;
|
Vector<Vector<TextureResource>> m_deferredReleases;
|
||||||
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
||||||
|
// Texture uploads are submitted out-of-band but NOT waited on (waiting
|
||||||
|
// behind the queue serialized the CPU against the previous frame's GPU
|
||||||
|
// work every time an animated atlas re-uploaded). Their transient objects
|
||||||
|
// are parked here and reclaimed once the upload fence signals.
|
||||||
|
struct PendingUploadReclaim {
|
||||||
|
VkFence fence = VK_NULL_HANDLE;
|
||||||
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||||
|
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||||
|
VmaAllocation stagingAllocation = nullptr;
|
||||||
|
};
|
||||||
|
Vector<PendingUploadReclaim> m_pendingUploadReclaims;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // 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
@@ -20,6 +20,7 @@
|
|||||||
#include "VkRenderPassManager.h"
|
#include "VkRenderPassManager.h"
|
||||||
#include "VkSamplerManager.h"
|
#include "VkSamplerManager.h"
|
||||||
#include "VkTextureManager.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>
|
||||||
@@ -50,6 +51,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 instanceCount = 1;
|
Uint32 instanceCount = 1;
|
||||||
Uint32 firstVertex = 0;
|
Uint32 firstVertex = 0;
|
||||||
Uint32 firstInstance = 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 {
|
struct DrawIndexedCmdParam {
|
||||||
@@ -69,6 +76,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLenum indexType = GL_UNSIGNED_SHORT;
|
GLenum indexType = GL_UNSIGNED_SHORT;
|
||||||
SizeT indexByteOffset = 0;
|
SizeT indexByteOffset = 0;
|
||||||
SizeT indexByteSize = 0;
|
SizeT indexByteSize = 0;
|
||||||
|
// Interpret indexByteOffset as a raw client pointer even when an element
|
||||||
|
// array buffer is bound (backend-synthesized index lists, e.g. the
|
||||||
|
// GL_LINE_LOOP -> LINE_STRIP rewrite).
|
||||||
|
Bool forceClientMemory = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawIndexedCmd {
|
struct DrawIndexedCmd {
|
||||||
@@ -86,6 +97,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
DrawIndexedCmdParam* pParams = nullptr;
|
DrawIndexedCmdParam* pParams = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MultiDrawCmd {
|
||||||
|
GLenum mode = GL_TRIANGLES;
|
||||||
|
Uint32 drawCount = 0;
|
||||||
|
DrawCmdParam* pParams = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
struct QueueFamilyIndices {
|
struct QueueFamilyIndices {
|
||||||
Int32 graphicsFamily = -1;
|
Int32 graphicsFamily = -1;
|
||||||
Int32 presentFamily = -1;
|
Int32 presentFamily = -1;
|
||||||
@@ -101,7 +118,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class VulkanRenderer {
|
class VulkanRenderer : public IBufferCopyCommandProvider,
|
||||||
|
public FrameContext::IRecordingObserver,
|
||||||
|
public VkRenderPassManager::IEvictionObserver,
|
||||||
|
public ProgramFactory::IEvictionObserver {
|
||||||
public:
|
public:
|
||||||
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
||||||
~VulkanRenderer();
|
~VulkanRenderer();
|
||||||
@@ -109,12 +129,51 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void Initialize();
|
void Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
|
// IBufferCopyCommandProvider: recording command buffer, outside any
|
||||||
|
// render pass, for immediate staged buffer copies.
|
||||||
|
VkCommandBuffer AcquireBufferCopyCommandBuffer() override;
|
||||||
|
|
||||||
|
// FrameContext::IRecordingObserver: prepares the frame's timer-query
|
||||||
|
// pool (harvest + reset) right after the frame command buffer begins
|
||||||
|
// recording, before any render pass.
|
||||||
|
void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) override;
|
||||||
|
|
||||||
|
// VkRenderPassManager::IEvictionObserver: the render-pass aging sweep just
|
||||||
|
// destroyed these VkRenderPasses; evict every graphics pipeline hashed on a
|
||||||
|
// dying handle (they share its >1024-boundary idleness, so immediate
|
||||||
|
// destruction is safe) and drop the last-pipeline memo if any went.
|
||||||
|
void OnRenderPassesDestroyed(const Vector<VkRenderPass>& renderPasses) override;
|
||||||
|
|
||||||
|
// ProgramFactory::IEvictionObserver: an aged-out program entry was
|
||||||
|
// destroyed; evict its compute pipeline and graphics pipelines (same
|
||||||
|
// idleness guarantee - they are only bound through draws/dispatches that
|
||||||
|
// stamp the program entry) and purge the descriptor-set cache entries
|
||||||
|
// keyed by its now-recyclable VkDescriptorSetLayout handle.
|
||||||
|
void OnProgramEvicted(ProgramFactory::HashType programHash,
|
||||||
|
VkDescriptorSetLayout descriptorSetLayout) override;
|
||||||
|
|
||||||
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||||
const DrawCmdParam& drawParams,
|
const DrawCmdParam& drawParams,
|
||||||
const IndexBufferView* pIndexBufferView = nullptr);
|
const IndexBufferView* pIndexBufferView = nullptr);
|
||||||
|
// ANGLE-style consecutive-draw fast path: SetupDraw snapshots the fully
|
||||||
|
// resolved draw configuration; the next draw whose cheap version/identity
|
||||||
|
// checks all match skips the resolution half (LOD probe, sampled-set
|
||||||
|
// walk, render-pass and pipeline resolution) and jumps straight to the
|
||||||
|
// per-draw tail. Returns false (leaving no side effects that the full
|
||||||
|
// path cannot redo idempotently) whenever anything might have changed.
|
||||||
|
Bool TrySetupDrawFastPath(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||||
|
const DrawCmdParam& drawParams, const IndexBufferView* pIndexBufferView);
|
||||||
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
|
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
|
||||||
const RenderPassEntry& compatibleRenderPassEntry);
|
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 Clear(GLbitfield mask);
|
||||||
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);
|
||||||
@@ -122,6 +181,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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,
|
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
|
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
|
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
@@ -134,8 +197,37 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLbitfield mask, GLenum filter);
|
GLbitfield mask, GLenum filter);
|
||||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||||
GLint x, GLint y, GLsizei width, GLsizei height);
|
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 GenerateMipmap(GLenum target);
|
||||||
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);
|
||||||
|
// GL_DEPTH_COMPONENT / GL_DEPTH_STENCIL / GL_STENCIL_INDEX readback from the
|
||||||
|
// read framebuffer's depth/stencil attachment (per-aspect buffer copies with
|
||||||
|
// CPU repacking into the requested client layout).
|
||||||
|
void ReadDepthStencilPixels(MG_State::GLState::FramebufferObject& readFbo, GLint x, GLint y, GLsizei width,
|
||||||
|
GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||||
|
// Copy-and-repack core shared by depth-stencil ReadPixels and GetTexImage;
|
||||||
|
// expects command recording to be active and any render pass already ended.
|
||||||
|
void ReadDepthStencilImageToClient(VkImage image, VkFormat vkFormat, VkImageLayout* trackedLayout,
|
||||||
|
VkImageAspectFlags imageAspect, Uint32 mipLevel, Uint32 baseArrayLayer,
|
||||||
|
GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||||
|
void* pixels);
|
||||||
|
// Same-extent depth blit between images of different depth formats: host
|
||||||
|
// round-trip with a per-texel re-encode (see BlitNamedFramebuffer).
|
||||||
|
Bool BlitDepthAcrossFormats(FrameContext::FrameData& frame, VkImage srcImage, VkFormat srcFormat,
|
||||||
|
VkImageLayout* srcTrackedLayout, Uint32 srcMipLevel, Uint32 srcBaseArrayLayer,
|
||||||
|
VkImage dstImage, VkFormat dstFormat, VkImageLayout* dstTrackedLayout,
|
||||||
|
Uint32 dstMipLevel, Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX,
|
||||||
|
GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout,
|
||||||
|
VkImageLayout dstRestoreLayout, Bool stencilAspect);
|
||||||
|
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 GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||||
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||||
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
||||||
@@ -146,7 +238,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static VkMemoryBarrier BuildMemoryBarrierForGlBarriers(GLbitfield barriers);
|
static VkMemoryBarrier BuildMemoryBarrierForGlBarriers(GLbitfield barriers);
|
||||||
void DrawArrays(const DrawCmd& payload);
|
void DrawArrays(const DrawCmd& payload);
|
||||||
void DrawElements(const DrawIndexedCmd& payload);
|
void DrawElements(const DrawIndexedCmd& payload);
|
||||||
|
void MultiDrawArrays(const MultiDrawCmd& payload);
|
||||||
void MultiDrawElements(const MultiDrawIndexedCmd& payloads);
|
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,
|
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||||
GLsizei maxdrawcount, GLsizei stride);
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
void Present();
|
void Present();
|
||||||
@@ -155,7 +251,80 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkInstance GetInstance() 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;
|
||||||
|
|
||||||
|
// GL_SAMPLES_PASSED occlusion queries: every app draw between Start and Stop is
|
||||||
|
// wrapped in a Vulkan occlusion query slot; the result is the slot sum. Requires
|
||||||
|
// hostQueryReset for slot recycling - Start fails (frontend keeps the query
|
||||||
|
// unsupported) when the device lacks it.
|
||||||
|
Bool StartOcclusionQueryCapture();
|
||||||
|
void StopOcclusionQueryCapture(Vector<Uint32>& outSlots);
|
||||||
|
// Flushes pending commands, waits, sums the slots, and recycles them.
|
||||||
|
Bool ResolveOcclusionQueryResult(const Vector<Uint32>& slots, Uint64& outSamples);
|
||||||
|
|
||||||
|
void RequestSwapchainResize(Uint32 width, Uint32 height);
|
||||||
|
// Re-query the surface and report whether the live swapchain no longer matches it
|
||||||
|
// (size or orientation). This - not a VK_SUBOPTIMAL_KHR result - is what decides a
|
||||||
|
// rebuild, so a surface the driver merely considers suboptimal cannot thrash.
|
||||||
|
Bool SwapchainIsOutOfDate();
|
||||||
|
// 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 BlitUniformData {
|
struct BlitUniformData {
|
||||||
@@ -195,18 +364,101 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||||
GLenum buffer, GLint drawbuffer,
|
GLenum buffer, GLint drawbuffer,
|
||||||
const ClearAttachmentPayload& clearPayload);
|
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;
|
||||||
|
// Frame-boundary housekeeping for paths that never reach Present's
|
||||||
|
// tail (present-less readback loops, suspended presentation, blocking
|
||||||
|
// sync waits): runs the same per-frame drains Present performs, but
|
||||||
|
// only when every queue submission has been observed complete AND no
|
||||||
|
// recorded-but-unsubmitted commands exist - i.e. when CPU-GPU overlap
|
||||||
|
// is provably already zero. Never blocks (non-blocking fence poll
|
||||||
|
// only), so the presenting path's frames-in-flight pipelining is
|
||||||
|
// untouched. Returns true when the drain ran.
|
||||||
|
Bool TryDrainFrameTransients();
|
||||||
|
|
||||||
|
Vector<SubmitRecord> m_inFlightSubmits;
|
||||||
|
Vector<VkFence> m_freeSubmitFences;
|
||||||
|
Uint64 m_submitCounter = 0;
|
||||||
|
Uint64 m_completedSubmitCounter = 0;
|
||||||
|
// Drains since the last Present, gating the drain's frame-boundary-equivalent
|
||||||
|
// work (arena rewind + cache aging): a presenting app's mid-frame
|
||||||
|
// readbacks/waits must neither churn the transient caches nor accelerate the
|
||||||
|
// aging clocks, while present-less loops still cross a boundary every few
|
||||||
|
// iterations. Reset in Present.
|
||||||
|
Uint32 m_drainsSinceLastPresent = 0;
|
||||||
|
|
||||||
NativeWindowType m_window = 0;
|
NativeWindowType m_window = 0;
|
||||||
void* m_platformDisplay = nullptr;
|
void* m_platformDisplay = nullptr;
|
||||||
void* m_platformLibrary = nullptr;
|
void* m_platformLibrary = nullptr;
|
||||||
void* m_platformCloseDisplay = nullptr;
|
void* m_platformCloseDisplay = nullptr;
|
||||||
|
// Some real ICDs (e.g. NVIDIA's proprietary Linux driver) don't implement
|
||||||
|
// VK_EXT_headless_surface at all. Detected once in CreateInstance() from the
|
||||||
|
// enumerated instance extensions; when false, CreateSurface() falls back to a
|
||||||
|
// hidden Xlib window instead of vkCreateHeadlessSurfaceEXT.
|
||||||
|
Bool m_headlessSurfaceSupported = true;
|
||||||
|
// Set when CreateSurface() had to create its own Xlib window for the fallback
|
||||||
|
// above (rather than being handed one by the caller), so Shutdown() knows it
|
||||||
|
// owns that window and must destroy it.
|
||||||
|
Bool m_ownsFallbackXlibWindow = false;
|
||||||
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;
|
||||||
Vector<VkExtensionProperties> m_extensions;
|
Vector<VkExtensionProperties> m_extensions;
|
||||||
VkInstance m_instance = VK_NULL_HANDLE;
|
VkInstance m_instance = VK_NULL_HANDLE;
|
||||||
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
|
||||||
|
// Fallback reporting channel for drivers that ship the validation layers but
|
||||||
|
// only expose the older VK_EXT_debug_report (Adreno 650 / Vulkan 1.1.128).
|
||||||
|
VkDebugReportCallbackEXT m_debugReportCallback = VK_NULL_HANDLE;
|
||||||
PhysicalDevice m_physicalDevice;
|
PhysicalDevice m_physicalDevice;
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
VmaAllocator m_allocator = nullptr;
|
VmaAllocator m_allocator = nullptr;
|
||||||
@@ -217,28 +469,161 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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_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);
|
||||||
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
||||||
|
|
||||||
|
// VK_EXT_transform_feedback (GL transform feedback capture)
|
||||||
|
Bool m_transformFeedbackFeatureEnabled = false;
|
||||||
|
// VK_EXT_provoking_vertex. Vulkan's built-in convention is "provoking vertex first"; GL's
|
||||||
|
// default is LAST_VERTEX_CONVENTION, and GL derives BOTH flat shading and the transform
|
||||||
|
// feedback vertex order from it. provokingVertexLast alone fixes flat shading and the
|
||||||
|
// input-assembler capture order and has no dependency on transform feedback; only
|
||||||
|
// transformFeedbackPreservesProvokingVertex does.
|
||||||
|
Bool m_provokingVertexLastEnabled = false;
|
||||||
|
// transformFeedbackPreservesProvokingVertex was actually enabled at device creation. Kept
|
||||||
|
// separate because it is the only thing that arms
|
||||||
|
// VUID-VkGraphicsPipelineCreateInfo-topology-04884, the rule that forbids a TRIANGLE_FAN
|
||||||
|
// pipeline from asking for LAST on a device that cannot preserve a fan's provoking vertex.
|
||||||
|
Bool m_provokingVertexXfbPreserveEnabled = false;
|
||||||
|
// provokingVertexModePerPipeline: when VK_FALSE every pipeline in one render pass instance
|
||||||
|
// must agree on the mode, so glProvokingVertex(GL_FIRST_VERTEX_CONVENTION) cannot be honoured
|
||||||
|
// per draw and every pipeline takes GL's default (LAST) instead.
|
||||||
|
Bool m_provokingVertexModePerPipeline = false;
|
||||||
|
// transformFeedbackPreservesTriangleFanProvokingVertex.
|
||||||
|
Bool m_provokingVertexFanPreserved = false;
|
||||||
|
// Per-pipeline provoking-vertex mode. capturesXfbFromGeometryStage must be a LINK-TIME
|
||||||
|
// property of the program, never the dynamic "is transform feedback active" flag: the
|
||||||
|
// 8-entry m_pipelineMemo and the SetupDrawSnapshot fast path key on programObj.hash and
|
||||||
|
// GetRenderStateParametersVersion(), neither of which moves when glBeginTransformFeedback is
|
||||||
|
// called, so a dynamic input here would hand back a stale VkPipeline.
|
||||||
|
VkProvokingVertexModeEXT SelectProvokingVertexMode(VkPrimitiveTopology topology,
|
||||||
|
Bool capturesXfbFromGeometryStage) const;
|
||||||
|
// VK_EXT_vertex_attribute_divisor: without it every non-zero glVertexAttribDivisor
|
||||||
|
// behaves as 1, because that is all Vulkan's instance input rate can express.
|
||||||
|
Bool m_vertexAttributeDivisorEnabled = false;
|
||||||
|
static inline PFN_vkCmdBindTransformFeedbackBuffersEXT s_vkCmdBindTransformFeedbackBuffersEXT = nullptr;
|
||||||
|
static inline PFN_vkCmdBeginTransformFeedbackEXT s_vkCmdBeginTransformFeedbackEXT = nullptr;
|
||||||
|
static inline PFN_vkCmdEndTransformFeedbackEXT s_vkCmdEndTransformFeedbackEXT = nullptr;
|
||||||
|
// Counter buffers (one 4-byte slot per capture binding) let consecutive
|
||||||
|
// draws within one glBeginTransformFeedback append GL-style. Transform feedback
|
||||||
|
// objects can each hold an open, paused span at the same time, so the counters are
|
||||||
|
// per object: one group of four slots each, handed out on first use.
|
||||||
|
static constexpr SizeT kXfbCounterObjectSlots = 16;
|
||||||
|
VkBufferObject m_xfbCounterBuffer;
|
||||||
|
UnorderedMap<Uint, Uint32> m_xfbCounterSlotByObject;
|
||||||
|
Uint32 m_xfbNextCounterSlot = 0;
|
||||||
|
// Set for a slot once a captured draw has been recorded into its span; selects
|
||||||
|
// counter-buffer resume on the next captured draw of the same span.
|
||||||
|
Array<Bool, kXfbCounterObjectSlots> m_xfbCountersValid{};
|
||||||
|
Array<Uint64, kXfbCounterObjectSlots> m_xfbLastSeenGeneration{};
|
||||||
|
// Counter slot group of the bound transform feedback object.
|
||||||
|
Uint32 CurrentXfbCounterSlot();
|
||||||
|
// Wraps a recorded draw with BeginTransformFeedbackEXT/EndTransformFeedbackEXT
|
||||||
|
// when GL transform feedback is active; binds capture buffers on demand.
|
||||||
|
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
|
||||||
|
void EndXfbCaptureForDraw(FrameContext::FrameData& frame, Bool began);
|
||||||
|
// Makes the captured bytes visible to whatever reads them next. Deferred rather than
|
||||||
|
// recorded next to the capture, because the capturing draw runs inside a render pass
|
||||||
|
// that declares no self-dependency.
|
||||||
|
void MakeXfbWritesVisible();
|
||||||
|
Bool m_xfbWritesPendingVisibility = false;
|
||||||
|
// Wrap one app draw in an occlusion-query slot while a GL_SAMPLES_PASSED
|
||||||
|
// query is active. Returns whether a slot was begun (End must mirror it).
|
||||||
|
Bool BeginOcclusionForDraw(VkCommandBuffer commandBuffer);
|
||||||
|
void EndOcclusionForDraw(VkCommandBuffer commandBuffer, Bool began);
|
||||||
|
Bool m_occlusionQueryPreciseEnabled = false;
|
||||||
|
Bool m_hostQueryResetEnabled = false;
|
||||||
|
PFN_vkResetQueryPool s_vkResetQueryPool = nullptr;
|
||||||
|
VkQueryPool m_occlusionQueryPool = VK_NULL_HANDLE;
|
||||||
|
static constexpr Uint32 kOcclusionQuerySlots = 8192;
|
||||||
|
Uint32 m_occlusionSlotCursor = 0;
|
||||||
|
Bool m_occlusionCaptureActive = false;
|
||||||
|
Vector<Uint32> m_occlusionActiveSlots;
|
||||||
|
// Transform feedback primitive queries: one pool slot per captured draw yields
|
||||||
|
// the (written, needed) pair; GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN sums the
|
||||||
|
// first, GL_PRIMITIVES_GENERATED the second - exact with geometry shaders,
|
||||||
|
// unlike the CPU fallback accounting.
|
||||||
|
Bool m_xfbQueriesSupported = false;
|
||||||
|
PFN_vkCmdBeginQueryIndexedEXT s_vkCmdBeginQueryIndexedEXT = nullptr;
|
||||||
|
PFN_vkCmdEndQueryIndexedEXT s_vkCmdEndQueryIndexedEXT = nullptr;
|
||||||
|
VkQueryPool m_xfbQueryPool = VK_NULL_HANDLE;
|
||||||
|
static constexpr Uint32 kXfbQuerySlots = 8192;
|
||||||
|
Uint32 m_xfbQuerySlotCursor = 0;
|
||||||
|
Bool m_xfbQueryCaptureActive[2] = {false, false}; // [0]=written, [1]=generated
|
||||||
|
Vector<Uint32> m_xfbQueryActiveSlots[2];
|
||||||
|
Bool m_xfbQuerySlotOpen = false;
|
||||||
|
Uint32 m_xfbQueryOpenSlot = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// kind: 0 = PRIMITIVES_WRITTEN, 1 = PRIMITIVES_GENERATED.
|
||||||
|
Bool StartXfbQueryCapture(Uint32 kind);
|
||||||
|
void StopXfbQueryCapture(Uint32 kind, Vector<Uint32>& outSlots);
|
||||||
|
Bool ResolveXfbQueryResult(const Vector<Uint32>& slots, Bool wantGenerated, Uint64& outPrimitives);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void BeginXfbQueryForDraw(VkCommandBuffer commandBuffer);
|
||||||
|
void EndXfbQueryForDraw(VkCommandBuffer commandBuffer);
|
||||||
|
|
||||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||||
|
|
||||||
struct TransientBufferSliceCacheEntry {
|
|
||||||
BufferSlice slice;
|
|
||||||
Uint64 changeSerial = 0;
|
|
||||||
SizeT size = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
VkBufferManager m_bufferManager;
|
VkBufferManager m_bufferManager;
|
||||||
UnorderedMap<const MG_State::GLState::BufferObject*, TransientBufferSliceCacheEntry>
|
|
||||||
m_transientVertexIndexBufferSlicesThisFrame;
|
|
||||||
|
|
||||||
Uint m_imageIndexAcquired = 0;
|
Uint m_imageIndexAcquired = 0;
|
||||||
FrameContext m_frameContext;
|
FrameContext m_frameContext;
|
||||||
|
|
||||||
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.
|
||||||
|
// Small N-way pipeline-resolution memo (round-robin replacement). A
|
||||||
|
// single-entry memo thrashed on draw sequences that alternate a few
|
||||||
|
// pipelines (GUI text/quad program ping-pong), paying the full
|
||||||
|
// payload-hash lookup per draw; eight entries cover such working sets
|
||||||
|
// while keeping the hit path a trivial linear scan.
|
||||||
|
struct PipelineMemoEntry {
|
||||||
|
GLenum mode = 0;
|
||||||
|
Uint64 programHash = 0;
|
||||||
|
Uint64 vertexInputHash = 0;
|
||||||
|
Uint64 renderPassHash = 0;
|
||||||
|
Uint renderStateVersion = 0;
|
||||||
|
ProgramFactory::CompileOptionFlags transformFlags = {};
|
||||||
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
static constexpr Uint32 kPipelineMemoSize = 8;
|
||||||
|
PipelineMemoEntry m_pipelineMemo[kPipelineMemoSize];
|
||||||
|
Uint32 m_pipelineMemoCount = 0;
|
||||||
|
Uint32 m_pipelineMemoNext = 0;
|
||||||
|
// Drops every memoized pipeline handle. Required at command-buffer
|
||||||
|
// boundaries and whenever any pipeline may have been destroyed.
|
||||||
|
void InvalidatePipelineMemo() {
|
||||||
|
m_pipelineMemoCount = 0;
|
||||||
|
m_pipelineMemoNext = 0;
|
||||||
|
}
|
||||||
UnorderedMap<ProgramFactory::HashType, VkPipeline> m_computePipelines;
|
UnorderedMap<ProgramFactory::HashType, VkPipeline> m_computePipelines;
|
||||||
UniquePtr<ProgramFactory> m_programFactory;
|
UniquePtr<ProgramFactory> m_programFactory;
|
||||||
UniquePtr<UniformManager> m_uniformManager;
|
UniquePtr<UniformManager> m_uniformManager;
|
||||||
@@ -247,13 +632,145 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
UniquePtr<VkRenderPassManager> m_renderPassManager;
|
UniquePtr<VkRenderPassManager> m_renderPassManager;
|
||||||
UniquePtr<VkTextureManager> m_textureManager;
|
UniquePtr<VkTextureManager> m_textureManager;
|
||||||
UniquePtr<VkSamplerManager> m_samplerManager;
|
UniquePtr<VkSamplerManager> m_samplerManager;
|
||||||
|
UniquePtr<VkTimerQueryManager> m_timerQueryManager;
|
||||||
BlitResources m_blitResources;
|
BlitResources m_blitResources;
|
||||||
DepthMipmapResources m_depthMipmapResources;
|
DepthMipmapResources m_depthMipmapResources;
|
||||||
Vector<DeferredDepthMipmapCleanup> m_deferredDepthMipmapCleanup;
|
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;
|
||||||
|
|
||||||
|
// Memo for the per-draw explicit-LOD-0 eligibility probe
|
||||||
|
// (ProgramSamplesOnlySingleLevelTextures): same key family as the
|
||||||
|
// sampled-set memo, plus the sampled textures' params-version sum so a
|
||||||
|
// level-range or filter change re-probes. On a hit the resolved
|
||||||
|
// transform flags are reused, which also collapses the two
|
||||||
|
// GetOrCreateProgram lookups into one.
|
||||||
|
Bool m_lastLodDecisionValid = false;
|
||||||
|
Uint64 m_lastLodProgramLifetimeId = 0;
|
||||||
|
Uint32 m_lastLodProgramVersion = 0;
|
||||||
|
Uint64 m_lastLodBindGeneration = 0;
|
||||||
|
Uint64 m_lastLodParamsSum = 0;
|
||||||
|
ProgramFactory::CompileOptionFlags m_lastLodBaseFlags = {};
|
||||||
|
ProgramFactory::CompileOptionFlags m_lastLodResultFlags = {};
|
||||||
|
|
||||||
|
// Snapshot behind TrySetupDrawFastPath. Values only: the program and
|
||||||
|
// render-pass caches are open-addressing maps whose entries move on
|
||||||
|
// insert, so no pointers into them are cached; the pipeline handle is
|
||||||
|
// protected by the command-buffer-boundary reset plus the mid-frame
|
||||||
|
// pipeline-destruction resets, and monotonic epochs guard everything
|
||||||
|
// that can be destroyed or recreated between draws.
|
||||||
|
struct SetupDrawSnapshot {
|
||||||
|
Bool valid = false;
|
||||||
|
Uint8 aspects = 0;
|
||||||
|
GLenum mode = 0;
|
||||||
|
Uint64 programLifetimeId = 0;
|
||||||
|
Uint32 programVersion = 0;
|
||||||
|
const void* vao = nullptr;
|
||||||
|
Uint32 vaoConfigVersion = 0;
|
||||||
|
const void* drawFbo = nullptr;
|
||||||
|
Uint16 fboVersion = 0;
|
||||||
|
Bool drawFboIsDefault = false;
|
||||||
|
Uint renderStateVersion = 0;
|
||||||
|
Uint64 bindGeneration = 0;
|
||||||
|
Uint32 baseTransformFlags = 0;
|
||||||
|
Uint32 resolvedTransformFlags = 0;
|
||||||
|
Uint64 renderPassHash = 0;
|
||||||
|
Uint32 imageIndex = 0;
|
||||||
|
Uint64 textureEraseEpoch = 0;
|
||||||
|
Uint64 textureImageEpoch = 0;
|
||||||
|
Uint64 renderbufferImageEpoch = 0;
|
||||||
|
Uint64 sampledContentSum = 0;
|
||||||
|
Uint64 sampledParamsSum = 0;
|
||||||
|
IntVec2 renderPassExtent = {0, 0};
|
||||||
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
SetupDrawSnapshot m_setupDrawSnapshot;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// Parallel to m_sampledTexturesScratch, refilled by every SetupDraw's
|
||||||
|
// first sampled-texture loop: the resolved backend resources, so the
|
||||||
|
// post-transition loop can skip re-resolving textures whose layout is
|
||||||
|
// already sampleable.
|
||||||
|
Vector<VkTextureManager::TextureResource*> m_sampledResourcesScratch;
|
||||||
|
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();
|
||||||
VkResult DestroyDebugMessenger();
|
VkResult DestroyDebugMessenger();
|
||||||
|
VkResult SetupDebugReportCallback();
|
||||||
|
void DestroyDebugReportCallback();
|
||||||
VkDebugUtilsMessengerCreateInfoEXT PopulateDebugMessengerCreateInfo();
|
VkDebugUtilsMessengerCreateInfoEXT PopulateDebugMessengerCreateInfo();
|
||||||
void CreateSurface();
|
void CreateSurface();
|
||||||
void PickPhysicalDevice();
|
void PickPhysicalDevice();
|
||||||
@@ -272,9 +789,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const RenderPassEntry& renderPassEntry);
|
const RenderPassEntry& renderPassEntry);
|
||||||
VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj);
|
VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj);
|
||||||
void DestroyComputePipelines();
|
void DestroyComputePipelines();
|
||||||
|
// Takes the frame rather than a command buffer: a first-time storage-usage upgrade has to
|
||||||
|
// flush the pending recording (see the body), which retires the current command buffer.
|
||||||
|
Bool PrepareStorageImageTextures(
|
||||||
|
FrameContext::FrameData& frame,
|
||||||
|
const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj);
|
||||||
|
|
||||||
Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao,
|
Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao,
|
||||||
const DrawCmdParam& drawParams);
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
|
const DrawCmdParam& drawParams,
|
||||||
|
const IndexBufferView* pIndexBufferView);
|
||||||
Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame,
|
Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame,
|
||||||
const MG_State::GLState::VertexArrayObject& vao,
|
const MG_State::GLState::VertexArrayObject& vao,
|
||||||
const IndexBufferView* pIndexBufferView = nullptr);
|
const IndexBufferView* pIndexBufferView = nullptr);
|
||||||
@@ -290,8 +815,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||||
GLenum filter);
|
GLenum filter);
|
||||||
|
// Clears one z slice of a VK_IMAGE_TYPE_3D colour image. See the call site in
|
||||||
|
// MaterializePendingClearForTexture for why a transfer clear cannot do this.
|
||||||
|
Bool ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
||||||
|
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||||
|
Uint32 depthSlice, const VkClearValue& clearValue);
|
||||||
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||||
MG_State::GLState::ITextureObject& texture);
|
MG_State::GLState::ITextureObject& texture);
|
||||||
|
Bool MaterializePendingClearForRenderbuffer(
|
||||||
|
VkCommandBuffer commandBuffer,
|
||||||
|
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||||
VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry);
|
VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry);
|
||||||
Bool GenerateDepthMipmapWithShader(FrameContext::FrameData& frame,
|
Bool GenerateDepthMipmapWithShader(FrameContext::FrameData& frame,
|
||||||
MG_State::GLState::ITextureObject& texture,
|
MG_State::GLState::ITextureObject& texture,
|
||||||
@@ -303,6 +836,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkImageLayout finalLayout);
|
VkImageLayout finalLayout);
|
||||||
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Submits whatever is recorded and waits for it. The CPU is about to read memory
|
||||||
|
// a shader wrote (a mapped shader storage buffer), and coherent host-visible
|
||||||
|
// storage only guarantees visibility once the work that produced it has retired.
|
||||||
|
Bool FinishPendingGpuWork();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
void ShutdownSwapchain();
|
void ShutdownSwapchain();
|
||||||
|
|
||||||
// Static functions
|
// Static functions
|
||||||
@@ -326,6 +867,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const PhysicalDevice& compareWithDevice,
|
const PhysicalDevice& compareWithDevice,
|
||||||
PhysicalDevice& outBetterDevice);
|
PhysicalDevice& outBetterDevice);
|
||||||
static constexpr const char* s_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"};
|
static constexpr const char* s_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"};
|
||||||
|
// VK_KHR_image_format_list: lets MUTABLE_FORMAT images declare their exact view-format
|
||||||
|
// set so the driver can keep bandwidth compression (see CreateLogicalDeviceAndQueues).
|
||||||
|
Bool m_imageFormatListExtensionEnabled = false;
|
||||||
|
|
||||||
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,90 @@
|
|||||||
|
|
||||||
#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
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
// GL renders into sRGB color attachments RAW while GL_FRAMEBUFFER_SRGB is disabled
|
||||||
|
// (the core-profile default); Vulkan sRGB attachments always encode on write. The
|
||||||
|
// attachment view (and render pass format) therefore drops to the UNORM twin
|
||||||
|
// whenever the capability is off. Sampled views keep the sRGB format (decode on
|
||||||
|
// sample is unconditional in GL).
|
||||||
|
inline VkFormat ResolveSrgbAttachmentWriteFormat(VkFormat format, bool framebufferSrgbEnabled) {
|
||||||
|
if (framebufferSrgbEnabled) return format;
|
||||||
|
switch (format) {
|
||||||
|
case VK_FORMAT_R8G8B8A8_SRGB:
|
||||||
|
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
case VK_FORMAT_B8G8R8A8_SRGB:
|
||||||
|
return VK_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
default:
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|
||||||
|
// The context line (__VA_ARGS__ = its own format string + args) must be a SEPARATE log
|
||||||
|
// call: appending its format to the base format while its arguments precede the base
|
||||||
|
// arguments makes every conversion read the wrong slot (a %s pulling an int crashes).
|
||||||
#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) { \
|
||||||
|
__VA_OPT__(MGLOG_F(__VA_ARGS__);) \
|
||||||
|
MGLOG_F("Vulkan error %s (%d) at %s:%d", \
|
||||||
|
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", \
|
||||||
|
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__); \
|
if (_xxh_verify_result != XXH_OK) { \
|
||||||
} while (0)
|
__VA_OPT__(MGLOG_F(__VA_ARGS__);) \
|
||||||
|
} \
|
||||||
|
MOBILEGL_ASSERT(_xxh_verify_result == XXH_OK, "XXHash error %d at %s:%d", _xxh_verify_result, __FILE__, \
|
||||||
|
__LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|||||||
@@ -11,10 +11,18 @@
|
|||||||
|
|
||||||
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";
|
||||||
MobileGL::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
|
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||||
Bool EnableValidationLayers = true;
|
Bool EnableValidationLayers = true;
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -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,733 @@
|
|||||||
|
// 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;
|
||||||
|
GLint DisplayMask = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
case kCGLPFADisplayMask:
|
||||||
|
pixelFormat.DisplayMask = 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 kCGLPFADisplayMask:
|
||||||
|
*value = pixelFormat->DisplayMask;
|
||||||
|
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 SetVirtualScreen(CGLContextObj ctx, GLint screen) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
if (screen != 0) {
|
||||||
|
return kCGLBadValue;
|
||||||
|
}
|
||||||
|
object->VirtualScreen = screen;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError GetVirtualScreen(CGLContextObj ctx, GLint* screen) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
if (!screen) {
|
||||||
|
return kCGLBadAddress;
|
||||||
|
}
|
||||||
|
*screen = object->VirtualScreen;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,51 @@
|
|||||||
|
// 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 SetVirtualScreen(CGLContextObj ctx, GLint screen);
|
||||||
|
CGLError GetVirtualScreen(CGLContextObj ctx, GLint* screen);
|
||||||
|
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,118 @@
|
|||||||
|
// 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 CGLSetVirtualScreen(CGLContextObj ctx, GLint screen) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::SetVirtualScreen(ctx, screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLGetVirtualScreen(CGLContextObj ctx, GLint* screen) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::GetVirtualScreen(ctx, screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,102 @@
|
|||||||
|
// 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/CGLImpl/CGLImpl.h"
|
||||||
|
#include "MG_Impl/GetProcAddress.h"
|
||||||
|
|
||||||
|
#include <CoreGraphics/CoreGraphics.h>
|
||||||
|
#include <CoreVideo/CVDisplayLink.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
CGDirectDisplayID DisplayForMask(GLint displayMask) {
|
||||||
|
constexpr std::uint32_t MaxDisplays = sizeof(CGOpenGLDisplayMask) * 8;
|
||||||
|
CGDirectDisplayID displays[MaxDisplays] = {};
|
||||||
|
std::uint32_t displayCount = 0;
|
||||||
|
if (displayMask != 0 &&
|
||||||
|
CGGetActiveDisplayList(MaxDisplays, displays, &displayCount) == kCGErrorSuccess) {
|
||||||
|
const auto mask = static_cast<CGOpenGLDisplayMask>(displayMask);
|
||||||
|
for (std::uint32_t i = 0; i < displayCount; ++i) {
|
||||||
|
if ((CGDisplayIDToOpenGLDisplayMask(displays[i]) & mask) != 0) {
|
||||||
|
return displays[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CGMainDisplayID();
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
CVReturn MobileGLCVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(
|
||||||
|
CVDisplayLinkRef displayLink,
|
||||||
|
CGLContextObj context,
|
||||||
|
CGLPixelFormatObj pixelFormat) {
|
||||||
|
GLint virtualScreen = 0;
|
||||||
|
if (MobileGL::MG_Impl::CGLImpl::GetVirtualScreen(context, &virtualScreen) == kCGLNoError) {
|
||||||
|
GLint displayMask = 0;
|
||||||
|
if (!displayLink ||
|
||||||
|
MobileGL::MG_Impl::CGLImpl::DescribePixelFormat(
|
||||||
|
pixelFormat, virtualScreen, kCGLPFADisplayMask, &displayMask) != kCGLNoError) {
|
||||||
|
return kCVReturnInvalidArgument;
|
||||||
|
}
|
||||||
|
return CVDisplayLinkSetCurrentCGDisplay(displayLink, DisplayForMask(displayMask));
|
||||||
|
}
|
||||||
|
|
||||||
|
using OriginalFunction = CVReturn (*)(CVDisplayLinkRef, CGLContextObj, CGLPixelFormatObj);
|
||||||
|
static const auto original = reinterpret_cast<OriginalFunction>(
|
||||||
|
dlsym(RTLD_NEXT, "CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext"));
|
||||||
|
return original ? original(displayLink, context, pixelFormat) : kCVReturnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((used)) static const DyldInterposeEntry kMobileGLDyldInterpose[]
|
||||||
|
__attribute__((section("__DATA,__interpose"))) = {
|
||||||
|
{reinterpret_cast<const void*>(MobileGLDlsym), reinterpret_cast<const void*>(dlsym)},
|
||||||
|
{reinterpret_cast<const void*>(MobileGLCVDisplayLinkSetCurrentCGDisplayFromOpenGLContext),
|
||||||
|
reinterpret_cast<const void*>(CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext)},
|
||||||
|
};
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Public CGL entry points.
|
||||||
|
_CGL*
|
||||||
|
|
||||||
|
# Public EGL entry points.
|
||||||
|
_egl*
|
||||||
|
|
||||||
|
# Public OpenGL and GLX entry points. OpenGL function names always use an
|
||||||
|
# uppercase letter or digit after the "gl" prefix; excluding lowercase here
|
||||||
|
# deliberately prevents glslang_* from matching this pattern.
|
||||||
|
_gl[A-Z0-9]*
|
||||||
@@ -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 {
|
||||||
@@ -23,6 +26,17 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return MG_State::pEGLContext.get();
|
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) {
|
||||||
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
||||||
if (!backendObject && state) {
|
if (!backendObject && state) {
|
||||||
@@ -31,9 +45,24 @@ 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__)
|
#elif defined(__linux__)
|
||||||
return MG_Backend::WindowBackend::X11;
|
return MG_Backend::WindowBackend::X11;
|
||||||
#else
|
#else
|
||||||
@@ -53,6 +82,18 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return defaultValue;
|
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>) {
|
||||||
@@ -91,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;
|
||||||
@@ -125,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;
|
||||||
}
|
}
|
||||||
@@ -149,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;
|
||||||
}
|
}
|
||||||
@@ -170,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;
|
||||||
}
|
}
|
||||||
@@ -186,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;
|
||||||
@@ -195,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,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) {
|
||||||
@@ -250,6 +327,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||||
backendObject->ReleaseEGLResources();
|
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;
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,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;
|
||||||
}
|
}
|
||||||
@@ -279,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;
|
||||||
}
|
}
|
||||||
@@ -312,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;
|
||||||
}
|
}
|
||||||
@@ -330,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;
|
||||||
@@ -342,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) {
|
||||||
@@ -361,7 +466,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!backendObject) {
|
if (!backendObject) {
|
||||||
return EGL_NO_SURFACE;
|
return EGL_NO_SURFACE;
|
||||||
}
|
}
|
||||||
if (!backendObject->CreateEGLPbufferSurface(width, height)) {
|
if (!backendObject->CreateEGLPbufferSurface(surface, width, height)) {
|
||||||
state->DestroySurface(dpy, surface);
|
state->DestroySurface(dpy, surface);
|
||||||
state->SetError(EGL_BAD_ALLOC);
|
state->SetError(EGL_BAD_ALLOC);
|
||||||
return EGL_NO_SURFACE;
|
return EGL_NO_SURFACE;
|
||||||
@@ -558,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;
|
||||||
}
|
}
|
||||||
@@ -584,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,
|
||||||
@@ -623,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,
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
#include "GL_Buffer.h"
|
#include "GL_Buffer.h"
|
||||||
#include "Validators.h"
|
#include "Validators.h"
|
||||||
|
#include "../Texture/GL_Texture.h"
|
||||||
|
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
||||||
|
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||||
|
#include <Config.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>
|
||||||
@@ -37,6 +41,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GetNamedBufferParameteriv,
|
GetNamedBufferParameteriv,
|
||||||
GetNamedBufferParameteri64v,
|
GetNamedBufferParameteri64v,
|
||||||
GetNamedBufferPointerv,
|
GetNamedBufferPointerv,
|
||||||
|
GetNamedBufferSubData,
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* GetBufferOpName(BufferOp op) {
|
const char* GetBufferOpName(BufferOp op) {
|
||||||
@@ -75,6 +80,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return "UnmapNamedBuffer";
|
return "UnmapNamedBuffer";
|
||||||
case BufferOp::FlushMappedNamedBufferRange:
|
case BufferOp::FlushMappedNamedBufferRange:
|
||||||
return "FlushMappedNamedBufferRange";
|
return "FlushMappedNamedBufferRange";
|
||||||
|
case BufferOp::GetNamedBufferSubData:
|
||||||
|
return "GetNamedBufferSubData";
|
||||||
case BufferOp::GetNamedBufferParameteriv:
|
case BufferOp::GetNamedBufferParameteriv:
|
||||||
return "GetNamedBufferParameteriv";
|
return "GetNamedBufferParameteriv";
|
||||||
case BufferOp::GetNamedBufferParameteri64v:
|
case BufferOp::GetNamedBufferParameteri64v:
|
||||||
@@ -88,25 +95,64 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
SharedPtr<MG_State::GLState::BufferObject> GetNamedBufferObject(GLuint buffer, BufferOp op);
|
SharedPtr<MG_State::GLState::BufferObject> GetNamedBufferObject(GLuint buffer, BufferOp op);
|
||||||
|
|
||||||
|
// The size of one cleared element, which is what offset and size must be multiples of
|
||||||
|
// (GL 4.6 core 6.3). `internalformat` is restricted to the buffer-texture format table, and
|
||||||
|
// `format`/`type` describe the client-side pattern, so both are validated here and the
|
||||||
|
// caller only has to know how wide an element is.
|
||||||
SizeT GetClearPatternSize(GLenum internalformat, GLenum format, GLenum type, BufferOp op) {
|
SizeT GetClearPatternSize(GLenum internalformat, GLenum format, GLenum type, BufferOp op) {
|
||||||
if (format != GL_RED_INTEGER) {
|
if (!IsBufferTextureInternalFormat(internalformat)) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidEnum,
|
ErrorCode::InvalidEnum,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
MakeUnique<GenericErrorInfo>(
|
||||||
"Only GL_RED_INTEGER buffer clears are currently supported."));
|
"MG_Impl/GLImpl", GetBufferOpName(op),
|
||||||
|
std::format("internalformat 0x{:X} is not one of the sized formats a buffer clear accepts.",
|
||||||
|
internalformat)));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (internalformat == GL_R8UI && type == GL_UNSIGNED_BYTE) return sizeof(GLubyte);
|
// Unlike internalformat, a bad format or type here is INVALID_VALUE rather than
|
||||||
if (internalformat == GL_R32UI && type == GL_UNSIGNED_INT) return sizeof(GLuint);
|
// INVALID_ENUM (GL 4.6 core 6.3) - the odd one out among the enum arguments.
|
||||||
|
const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
||||||
|
if (inputFormat == TextureInputFormat::Unknown) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||||
|
std::format("format 0x{:X} is not a pixel format.", format)));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
MG_State::pGLContext->RecordError(
|
const TexturePixelDataType pixelType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
||||||
ErrorCode::InvalidEnum,
|
if (pixelType == TexturePixelDataType::Unknown) {
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
MG_State::pGLContext->RecordError(
|
||||||
std::format("Unsupported clear format tuple: internalformat=0x{:X}, "
|
ErrorCode::InvalidValue,
|
||||||
"format=0x{:X}, type=0x{:X}",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||||
internalformat, format, type)));
|
std::format("type 0x{:X} is not a pixel type.", type)));
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextureInternalFormat internal =
|
||||||
|
MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
||||||
|
const SizeT elementSize = MG_Util::GetSizedInternalFormatSizeInBytes(internal);
|
||||||
|
if (elementSize == 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||||
|
std::format("internalformat 0x{:X} has no known element size.",
|
||||||
|
internalformat)));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The pattern is replicated verbatim, which is only the whole story while the client
|
||||||
|
// layout already matches the internal format - the case every entry point in practice
|
||||||
|
// uses, and the only one the conversion machinery here can express. Say so rather than
|
||||||
|
// quietly writing a differently-sized pattern.
|
||||||
|
const SizeT sourceSize = MG_Util::GetInputBytesPerPixel(inputFormat, pixelType);
|
||||||
|
if (sourceSize != elementSize) {
|
||||||
|
MGLOG_W("%s: clear pattern is %zu bytes but internalformat 0x%X stores %zu; "
|
||||||
|
"converting between them is not implemented",
|
||||||
|
GetBufferOpName(op), sourceSize, internalformat, elementSize);
|
||||||
|
}
|
||||||
|
return elementSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateBufferClearRange(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject, GLintptr offset,
|
Bool ValidateBufferClearRange(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject, GLintptr offset,
|
||||||
@@ -208,6 +254,26 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return bufferObject;
|
return bufferObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MOBILEGL_COHERENT_AS_FLUSH: rewrite a validated persistent FLUSH_EXPLICIT mapping
|
||||||
|
// request to coherent semantics: the map becomes coherent-persistent (eligible for
|
||||||
|
// the zero-copy backend map, otherwise synced wholesale at draw time), so its writes
|
||||||
|
// reach the GPU without glFlushMappedBufferRange; flush calls on rewritten maps are
|
||||||
|
// tolerated as no-ops by the FlushMappedBufferRange entry points. Non-persistent
|
||||||
|
// maps keep spec FLUSH_EXPLICIT behavior on purpose: the GPU cannot read them while
|
||||||
|
// mapped, so they gain nothing from the rewrite, and honoring only the app's flushed
|
||||||
|
// subranges avoids clobbering GPU-written bytes elsewhere in the mapped range.
|
||||||
|
// Runs after validation so the app's original access combination is what gets
|
||||||
|
// validated (Coherent is injected without requiring GL_MAP_COHERENT_BIT storage).
|
||||||
|
Flags<BufferMappingAccessBit> ApplyCoherentAsFlush(Flags<BufferMappingAccessBit> accessBits) {
|
||||||
|
if (!MG_Config::Features.CoherentAsFlush) return accessBits;
|
||||||
|
if (!(accessBits & BufferMappingAccessBit::FlushExplicit)) return accessBits;
|
||||||
|
if (!(accessBits & BufferMappingAccessBit::Persistent)) return accessBits;
|
||||||
|
accessBits = Flags<BufferMappingAccessBit>(
|
||||||
|
accessBits.GetRaw() & ~static_cast<Uint>(BufferMappingAccessBit::FlushExplicit));
|
||||||
|
accessBits |= BufferMappingAccessBit::Coherent;
|
||||||
|
return accessBits;
|
||||||
|
}
|
||||||
|
|
||||||
Bool ValidateStorageFlags(GLbitfield flags, BufferOp op) {
|
Bool ValidateStorageFlags(GLbitfield flags, BufferOp op) {
|
||||||
constexpr GLbitfield validFlags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT |
|
constexpr GLbitfield validFlags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT |
|
||||||
GL_MAP_COHERENT_BIT | GL_DYNAMIC_STORAGE_BIT | GL_CLIENT_STORAGE_BIT;
|
GL_MAP_COHERENT_BIT | GL_DYNAMIC_STORAGE_BIT | GL_CLIENT_STORAGE_BIT;
|
||||||
@@ -309,12 +375,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
} else if (access & BufferMappingAccessBit::Write) {
|
} else if (access & BufferMappingAccessBit::Write) {
|
||||||
*params = GL_WRITE_ONLY;
|
*params = GL_WRITE_ONLY;
|
||||||
} else {
|
} else {
|
||||||
*params = 0;
|
*params = GL_READ_WRITE;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
*params = 0;
|
// Initial value, and what glUnmapBuffer restores (GL 4.6 core table 6.2).
|
||||||
|
*params = GL_READ_WRITE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case GL_BUFFER_ACCESS_FLAGS:
|
||||||
|
// The MapBufferRange flags verbatim; glMapBuffer's access enum has already been
|
||||||
|
// normalised into the same bits. Zero while the buffer is not mapped.
|
||||||
|
*params = bufferObject->IsMapped()
|
||||||
|
? static_cast<GLint>(
|
||||||
|
MG_Util::ConvertBufferMappingAccessToGLEnum(bufferObject->GetMappingAccess()))
|
||||||
|
: 0;
|
||||||
|
break;
|
||||||
case GL_BUFFER_MAPPED:
|
case GL_BUFFER_MAPPED:
|
||||||
*params = bufferObject->IsMapped() ? GL_TRUE : GL_FALSE;
|
*params = bufferObject->IsMapped() ? GL_TRUE : GL_FALSE;
|
||||||
break;
|
break;
|
||||||
@@ -420,7 +495,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) {
|
for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) {
|
||||||
Uint bufferName = buffers[i];
|
Uint bufferName = buffers[i];
|
||||||
if (bufferName == 0) continue;
|
if (bufferName == 0) continue;
|
||||||
if (!BufferImpl::ValidateBufferName(bufferName, true)) continue;
|
// GL 3.3 core 2.9: names that do not correspond to an existing buffer are silently
|
||||||
|
// ignored here, so probe with the non-recording query - the shared validator would
|
||||||
|
// record INVALID_OPERATION, which is only correct on the bind path.
|
||||||
|
if (!MG_State::pGLContext->ValidateBufferName(bufferName)) continue;
|
||||||
MG_State::pGLContext->MarkBufferObjectForDeletion(bufferName);
|
MG_State::pGLContext->MarkBufferObjectForDeletion(bufferName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,6 +543,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
auto mappingAccess = bufferObject->GetMappingAccess();
|
auto mappingAccess = bufferObject->GetMappingAccess();
|
||||||
if (!(mappingAccess & BufferMappingAccessBit::FlushExplicit)) {
|
if (!(mappingAccess & BufferMappingAccessBit::FlushExplicit)) {
|
||||||
|
// MOBILEGL_COHERENT_AS_FLUSH strips FLUSH_EXPLICIT from persistent maps at map
|
||||||
|
// time (leaving Persistent|Coherent), so honor the app's flush on such a map as
|
||||||
|
// a no-op: its writes reach the backend without explicit flushes. Other maps
|
||||||
|
// keep the spec error.
|
||||||
|
if (MG_Config::Features.CoherentAsFlush &&
|
||||||
|
(mappingAccess & BufferMappingAccessBit::Persistent) &&
|
||||||
|
(mappingAccess & BufferMappingAccessBit::Coherent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
MakeUnique<GenericErrorInfo>(
|
MakeUnique<GenericErrorInfo>(
|
||||||
@@ -605,7 +692,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void* result = bufferObject->AcquireMemoryRange(
|
void* result = bufferObject->AcquireMemoryRange(
|
||||||
{static_cast<SizeT>(offset), static_cast<SizeT>(offset + length)}, accessBits);
|
{static_cast<SizeT>(offset), static_cast<SizeT>(offset + length)}, ApplyCoherentAsFlush(accessBits));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::OutOfMemory,
|
ErrorCode::OutOfMemory,
|
||||||
@@ -799,6 +886,94 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
bufferObject->UploadSubData({(void*)data, (SizeT)size}, offset);
|
bufferObject->UploadSubData({(void*)data, (SizeT)size}, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetBufferSubData_State(GLenum target, GLintptr offset, GLsizeiptr size, void* data) {
|
||||||
|
MGLOG_D("%s: target = %s, offset = %d, size = %d, data = %p", __func__,
|
||||||
|
MG_Util::ConvertGLEnumToString(target).c_str(), offset, size, data);
|
||||||
|
if (!data) {
|
||||||
|
// Match BufferSubData_State: a null pointer is a caller bug, not a GL-specified error.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size < 0 || offset < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBufferSubData_State",
|
||||||
|
"Offset and size must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
||||||
|
if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return;
|
||||||
|
auto& bindingSlot = GetBufferBindingSlot(bufferTarget);
|
||||||
|
|
||||||
|
auto& bufferObject = bindingSlot.GetBoundObject();
|
||||||
|
if (!bufferObject) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBufferSubData_State",
|
||||||
|
"Buffer target is bound to no buffer object."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeT bufferSize = bufferObject->GetSize();
|
||||||
|
if (static_cast<SizeT>(offset) + static_cast<SizeT>(size) > bufferSize) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBufferSubData_State",
|
||||||
|
"Offset and size exceed buffer size."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferObject->IsMapped() &&
|
||||||
|
!(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBufferSubData_State",
|
||||||
|
"Cannot read from a buffer object mapped without GL_MAP_PERSISTENT_BIT."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferObject->SyncGpuWrites();
|
||||||
|
bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetNamedBufferSubData_State(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) {
|
||||||
|
if (!data) {
|
||||||
|
// Match GetBufferSubData_State: a null pointer is a caller bug, not a GL-specified error.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size < 0 || offset < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetNamedBufferSubData_State",
|
||||||
|
"Offset and size must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bufferObject = GetNamedBufferObject(buffer, BufferOp::GetNamedBufferSubData);
|
||||||
|
if (!bufferObject) return;
|
||||||
|
|
||||||
|
if (static_cast<SizeT>(offset) + static_cast<SizeT>(size) > bufferObject->GetSize()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetNamedBufferSubData_State",
|
||||||
|
"Offset and size exceed buffer size."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferObject->IsMapped() &&
|
||||||
|
!(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetNamedBufferSubData_State",
|
||||||
|
"Cannot read from a buffer object mapped without GL_MAP_PERSISTENT_BIT."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferObject->SyncGpuWrites();
|
||||||
|
bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size));
|
||||||
|
}
|
||||||
|
|
||||||
void BufferData_State(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
|
void BufferData_State(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
|
||||||
MGLOG_D("%s: %s, size = %d, data = %p, usage = %s", __func__, MG_Util::ConvertGLEnumToString(target).c_str(),
|
MGLOG_D("%s: %s, size = %d, data = %p, usage = %s", __func__, MG_Util::ConvertGLEnumToString(target).c_str(),
|
||||||
size, data, MG_Util::ConvertGLEnumToString(usage).c_str());
|
size, data, MG_Util::ConvertGLEnumToString(usage).c_str());
|
||||||
@@ -834,10 +1009,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bufferObject->SetUsage(bufferUsage);
|
bufferObject->SetUsage(bufferUsage);
|
||||||
bufferObject->Resize(size);
|
bufferObject->Respecify(size, data);
|
||||||
if (data) {
|
|
||||||
bufferObject->UploadData({(void*)data, (SizeT)size}, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferStorage_State(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) {
|
void BufferStorage_State(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) {
|
||||||
@@ -928,10 +1100,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bufferObject->SetUsage(bufferUsage);
|
bufferObject->SetUsage(bufferUsage);
|
||||||
bufferObject->Resize(size);
|
bufferObject->Respecify(size, data);
|
||||||
if (data) {
|
|
||||||
bufferObject->UploadData({(void*)data, (SizeT)size}, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NamedBufferSubData_State(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data) {
|
void NamedBufferSubData_State(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data) {
|
||||||
@@ -1154,7 +1323,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return bufferObject->AcquireMemoryRange({static_cast<SizeT>(offset), static_cast<SizeT>(offset + length)},
|
return bufferObject->AcquireMemoryRange({static_cast<SizeT>(offset), static_cast<SizeT>(offset + length)},
|
||||||
accessBits);
|
ApplyCoherentAsFlush(accessBits));
|
||||||
}
|
}
|
||||||
|
|
||||||
GLboolean UnmapNamedBuffer_State(GLuint buffer) {
|
GLboolean UnmapNamedBuffer_State(GLuint buffer) {
|
||||||
@@ -1196,7 +1365,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"Offset and length exceed mapped range."));
|
"Offset and length exceed mapped range."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(bufferObject->GetMappingAccess() & BufferMappingAccessBit::FlushExplicit)) {
|
const auto namedMappingAccess = bufferObject->GetMappingAccess();
|
||||||
|
if (!(namedMappingAccess & BufferMappingAccessBit::FlushExplicit)) {
|
||||||
|
// See FlushMappedBufferRange_State: rewritten coherent-as-flush persistent maps
|
||||||
|
// tolerate app flushes as no-ops.
|
||||||
|
if (MG_Config::Features.CoherentAsFlush &&
|
||||||
|
(namedMappingAccess & BufferMappingAccessBit::Persistent) &&
|
||||||
|
(namedMappingAccess & BufferMappingAccessBit::Coherent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "FlushMappedNamedBufferRange_State",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "FlushMappedNamedBufferRange_State",
|
||||||
@@ -1266,23 +1443,47 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MG_Util::ConvertGLEnumToString(target).c_str(), pointIndex, buffer);
|
MG_Util::ConvertGLEnumToString(target).c_str(), pointIndex, buffer);
|
||||||
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
||||||
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
||||||
|
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, pointIndex)) return;
|
||||||
|
if (bufferTarget == BufferTarget::TransformFeedback && MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Transform feedback buffer bindings cannot change while transform "
|
||||||
|
"feedback is active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, pointIndex);
|
||||||
|
|
||||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, pointIndex);
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, pointIndex);
|
||||||
|
SharedPtr<MG_State::GLState::BufferObject> bufferObject;
|
||||||
if (buffer == 0) {
|
if (buffer == 0) {
|
||||||
point.Bind(nullptr);
|
point.Bind(nullptr);
|
||||||
point.SetRange(Range1D(0, 0));
|
point.SetRange(Range1D(0, 0));
|
||||||
|
GetBufferBindingSlot(bufferTarget).Bind(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!BufferImpl::ValidateBufferName(buffer, true)) return;
|
||||||
|
|
||||||
Bool doesBufferObjectCreated = MG_State::pGLContext->ValidateBufferObject(buffer);
|
Bool doesBufferObjectCreated = MG_State::pGLContext->ValidateBufferObject(buffer);
|
||||||
if (!doesBufferObjectCreated) {
|
if (!doesBufferObjectCreated) {
|
||||||
MG_State::pGLContext->CreateBufferObject(buffer);
|
MG_State::pGLContext->CreateBufferObject(buffer);
|
||||||
}
|
}
|
||||||
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
|
||||||
point.Bind(bufferObject);
|
point.Bind(bufferObject);
|
||||||
point.SetRange(Range1D(0, bufferObject->GetSize()));
|
if (bufferObject) {
|
||||||
MGLOG_D("%s: set range (0, %d)", __func__, bufferObject->GetSize());
|
point.SetRange(Range1D(0, bufferObject->GetSize()), false);
|
||||||
|
MGLOG_D("%s: set range (0, %d)", __func__, bufferObject->GetSize());
|
||||||
|
} else {
|
||||||
|
point.ClearRange();
|
||||||
|
}
|
||||||
|
// The indexed bind also binds to the generic binding point of the same target
|
||||||
|
// (GL 4.6 core 6.1.1). Callers rely on it: the texture_gather tests set up their
|
||||||
|
// SSBO with BindBufferBase and then size it through glBufferData on the generic
|
||||||
|
// target alone, which would otherwise raise GL_INVALID_OPERATION and leave the
|
||||||
|
// buffer with no storage.
|
||||||
|
GetBufferBindingSlot(bufferTarget).Bind(bufferObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindBufferRange_State(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
void BindBufferRange_State(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||||
@@ -1290,22 +1491,42 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MG_Util::ConvertGLEnumToString(target).c_str(), index, buffer, offset, size);
|
MG_Util::ConvertGLEnumToString(target).c_str(), index, buffer, offset, size);
|
||||||
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
||||||
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
||||||
|
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, index)) return;
|
||||||
|
if (bufferTarget == BufferTarget::TransformFeedback && MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Transform feedback buffer bindings cannot change while transform "
|
||||||
|
"feedback is active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, index);
|
||||||
|
|
||||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, index);
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, index);
|
||||||
|
SharedPtr<MG_State::GLState::BufferObject> bufferObject;
|
||||||
if (buffer == 0) {
|
if (buffer == 0) {
|
||||||
point.Bind(nullptr);
|
point.Bind(nullptr);
|
||||||
point.SetRange(Range1D(0, 0));
|
point.SetRange(Range1D(0, 0));
|
||||||
|
GetBufferBindingSlot(bufferTarget).Bind(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!BufferImpl::ValidateBufferName(buffer, true)) return;
|
||||||
|
|
||||||
Bool doesBufferObjectCreated = MG_State::pGLContext->ValidateBufferObject(buffer);
|
Bool doesBufferObjectCreated = MG_State::pGLContext->ValidateBufferObject(buffer);
|
||||||
if (!doesBufferObjectCreated) {
|
if (!doesBufferObjectCreated) {
|
||||||
MG_State::pGLContext->CreateBufferObject(buffer);
|
MG_State::pGLContext->CreateBufferObject(buffer);
|
||||||
}
|
}
|
||||||
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
|
||||||
point.Bind(bufferObject);
|
point.Bind(bufferObject);
|
||||||
point.SetRange(Range1D(offset, offset + size));
|
if (bufferObject) {
|
||||||
|
point.SetRange(Range1D(offset, offset + size));
|
||||||
|
} else {
|
||||||
|
point.ClearRange();
|
||||||
|
}
|
||||||
|
// Also the generic binding point, exactly as BindBufferBase (GL 4.6 core 6.1.1).
|
||||||
|
GetBufferBindingSlot(bufferTarget).Bind(bufferObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||||
@@ -1313,14 +1534,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GetBufferParameteriv_State(target, pname, params);
|
GetBufferParameteriv_State(target, pname, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetBufferParameteri64v(GLenum target, GLenum pname, GLint64* params) {
|
|
||||||
GetBufferParameteri64v_State(target, pname, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GetBufferPointerv(GLenum target, GLenum pname, void** params) {
|
void GetBufferPointerv(GLenum target, GLenum pname, void** params) {
|
||||||
GetBufferPointerv_State(target, pname, params);
|
GetBufferPointerv_State(target, pname, params);
|
||||||
}
|
}
|
||||||
|
void GetBufferParameteri64v(GLenum target, GLenum pname, GLint64* params) {
|
||||||
|
GetBufferParameteri64v_State(target, pname, params);
|
||||||
|
}
|
||||||
GLboolean IsBuffer(GLuint buffer) {
|
GLboolean IsBuffer(GLuint buffer) {
|
||||||
return IsBuffer_State(buffer);
|
return IsBuffer_State(buffer);
|
||||||
}
|
}
|
||||||
@@ -1417,6 +1636,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BufferSubData_State(target, offset, size, data);
|
BufferSubData_State(target, offset, size, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetNamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) {
|
||||||
|
GetNamedBufferSubData_State(buffer, offset, size, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data) {
|
||||||
|
GetBufferSubData_State(target, offset, size, data);
|
||||||
|
}
|
||||||
|
|
||||||
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
|
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
|
||||||
BufferData_State(target, size, data, usage);
|
BufferData_State(target, size, data, usage);
|
||||||
}
|
}
|
||||||
@@ -1436,4 +1663,23 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void BindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
void BindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||||
BindBufferRange_State(target, index, buffer, offset, size);
|
BindBufferRange_State(target, index, buffer, offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ARB_multi_bind: defined by the spec as equivalent to a loop over the single-bind entry
|
||||||
|
// points (with buffer 0 resetting the binding point).
|
||||||
|
void BindBuffersBase(GLenum target, GLuint first, GLsizei count, const GLuint* buffers) {
|
||||||
|
for (GLsizei i = 0; i < count; ++i) {
|
||||||
|
BindBufferBase_State(target, first + i, buffers ? buffers[i] : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindBuffersRange(GLenum target, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets,
|
||||||
|
const GLsizeiptr* sizes) {
|
||||||
|
for (GLsizei i = 0; i < count; ++i) {
|
||||||
|
if (!buffers || buffers[i] == 0) {
|
||||||
|
BindBufferBase_State(target, first + i, 0);
|
||||||
|
} else {
|
||||||
|
BindBufferRange_State(target, first + i, buffers[i], offsets ? offsets[i] : 0, sizes ? sizes[i] : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -40,10 +40,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset,
|
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset,
|
||||||
GLsizeiptr size);
|
GLsizeiptr size);
|
||||||
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||||
|
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data);
|
||||||
|
void GetNamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data);
|
||||||
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||||
void BindBuffer(GLenum target, GLuint buffer);
|
void BindBuffer(GLenum target, GLuint buffer);
|
||||||
void GenBuffers(GLsizei n, GLuint* buffers);
|
void GenBuffers(GLsizei n, GLuint* buffers);
|
||||||
void BindBufferBase(GLenum target, GLuint index, GLuint buffer);
|
void BindBufferBase(GLenum target, GLuint index, GLuint buffer);
|
||||||
void BindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
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 MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// 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>
|
||||||
@@ -52,6 +53,31 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
|||||||
return true;
|
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 (target == BufferTarget::TransformFeedback) {
|
||||||
|
// GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS bounds the indexed capture
|
||||||
|
// binding points in GL 3.3 (no ARB_transform_feedback3).
|
||||||
|
pointCount = std::min<SizeT>(pointCount, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
Bool ValidateBufferName(Uint index, Bool allowZero) {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
if (allowZero) return true;
|
if (allowZero) return true;
|
||||||
@@ -86,14 +112,10 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
||||||
if (accessBits == BufferMappingAccessBit::Null) {
|
// An empty mask is a legal value for a bitfield - it just fails the rule that a mapping
|
||||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
// must ask for read or write access, which is INVALID_OPERATION and belongs to the callers
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
// (both of them check it immediately after this). Rejecting it here as INVALID_ENUM
|
||||||
"ValidateBufferMappingAccess",
|
// reported the wrong error and hid theirs.
|
||||||
"Access bits cannot be null."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto validBits = BufferMappingAccessBit::Read | BufferMappingAccessBit::Write |
|
const auto validBits = BufferMappingAccessBit::Read | BufferMappingAccessBit::Write |
|
||||||
BufferMappingAccessBit::InvalidateRange | BufferMappingAccessBit::InvalidateBuffer |
|
BufferMappingAccessBit::InvalidateRange | BufferMappingAccessBit::InvalidateBuffer |
|
||||||
BufferMappingAccessBit::FlushExplicit | BufferMappingAccessBit::Unsynchronized |
|
BufferMappingAccessBit::FlushExplicit | BufferMappingAccessBit::Unsynchronized |
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
|||||||
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 MobileGL::MG_Impl::GLImpl::BufferImpl
|
} // namespace MobileGL::MG_Impl::GLImpl::BufferImpl
|
||||||
|
|||||||
@@ -9,9 +9,293 @@
|
|||||||
#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>
|
||||||
|
#include "../Getter/GL_Getter.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
||||||
|
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
||||||
|
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->GetProgramForDraw();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primitives a draw of `count` vertices in `mode` assembles (0 for
|
||||||
|
// incomplete primitives). Used for the CPU-side transform feedback
|
||||||
|
// primitive accounting.
|
||||||
|
static Uint64 CountPrimitivesForDraw(GLenum mode, GLsizei count) {
|
||||||
|
if (count <= 0) return 0;
|
||||||
|
switch (mode) {
|
||||||
|
case GL_POINTS: return static_cast<Uint64>(count);
|
||||||
|
case GL_LINES: return static_cast<Uint64>(count / 2);
|
||||||
|
case GL_LINE_STRIP: return count >= 2 ? static_cast<Uint64>(count - 1) : 0;
|
||||||
|
case GL_LINE_LOOP: return count >= 2 ? static_cast<Uint64>(count) : 0;
|
||||||
|
case GL_TRIANGLES: return static_cast<Uint64>(count / 3);
|
||||||
|
case GL_TRIANGLE_STRIP:
|
||||||
|
case GL_TRIANGLE_FAN: return count >= 3 ? static_cast<Uint64>(count - 2) : 0;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate the transform feedback primitive counter for a captured draw.
|
||||||
|
// Draws without a geometry stage write exactly the primitives they assemble,
|
||||||
|
// clamped by the capture buffers' remaining capacity (a full buffer stops
|
||||||
|
// recording whole primitives, which is what PRIMITIVES_WRITTEN reports).
|
||||||
|
// Geometry amplification is not modelled here.
|
||||||
|
static void AccountTransformFeedbackPrimitives(GLenum mode, GLsizei count) {
|
||||||
|
if (!MG_State::pGLContext->IsTransformFeedbackActive()) return;
|
||||||
|
// A paused span captures nothing, so a draw made while paused contributes to
|
||||||
|
// PRIMITIVES_GENERATED but not to TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN.
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||||
|
MG_State::pGLContext->AddTransformFeedbackPausedPrimitives(CountPrimitivesForDraw(mode, count));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Uint64 primitives = CountPrimitivesForDraw(mode, count);
|
||||||
|
if (primitives == 0) return;
|
||||||
|
MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives);
|
||||||
|
|
||||||
|
Uint64 verticesPerPrimitive = 1;
|
||||||
|
switch (mode) {
|
||||||
|
case GL_LINES:
|
||||||
|
case GL_LINE_STRIP:
|
||||||
|
case GL_LINE_LOOP:
|
||||||
|
verticesPerPrimitive = 2;
|
||||||
|
break;
|
||||||
|
case GL_TRIANGLES:
|
||||||
|
case GL_TRIANGLE_STRIP:
|
||||||
|
case GL_TRIANGLE_FAN:
|
||||||
|
verticesPerPrimitive = 3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||||
|
if (program != nullptr) {
|
||||||
|
// Capacity in captured vertices = the tightest bound buffer.
|
||||||
|
Uint64 capacityVertices = ~0ull;
|
||||||
|
for (SizeT i = 0; i < program->GetTransformFeedbackBufferCount(); ++i) {
|
||||||
|
const Uint32 stride = program->GetTransformFeedbackStride(static_cast<Uint32>(i));
|
||||||
|
if (stride == 0) continue;
|
||||||
|
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
||||||
|
static_cast<Uint>(i));
|
||||||
|
const Range1D range = point.GetRange();
|
||||||
|
const Uint64 bytes = range.end > range.start ? static_cast<Uint64>(range.end - range.start) : 0;
|
||||||
|
capacityVertices = std::min<Uint64>(capacityVertices, bytes / stride);
|
||||||
|
}
|
||||||
|
if (capacityVertices != ~0ull) {
|
||||||
|
const Uint64 usedVertices = MG_State::pGLContext->GetTransformFeedbackCapturedVertices();
|
||||||
|
const Uint64 remainingVertices = capacityVertices > usedVertices ? capacityVertices - usedVertices : 0;
|
||||||
|
primitives = std::min<Uint64>(primitives, remainingVertices / verticesPerPrimitive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->AddTransformFeedbackPrimitives(primitives);
|
||||||
|
MG_State::pGLContext->AddTransformFeedbackCapturedVertices(primitives * verticesPerPrimitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every primitive mode a draw command accepts (GL 4.6 core table 10.1, plus
|
||||||
|
// GL_PATCHES for the tessellation pipeline). Anything else is GL_INVALID_ENUM.
|
||||||
|
static Bool IsAcceptedPrimitiveMode(GLenum mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case GL_POINTS:
|
||||||
|
case GL_LINES:
|
||||||
|
case GL_LINE_LOOP:
|
||||||
|
case GL_LINE_STRIP:
|
||||||
|
case GL_LINES_ADJACENCY:
|
||||||
|
case GL_LINE_STRIP_ADJACENCY:
|
||||||
|
case GL_TRIANGLES:
|
||||||
|
case GL_TRIANGLE_STRIP:
|
||||||
|
case GL_TRIANGLE_FAN:
|
||||||
|
case GL_TRIANGLES_ADJACENCY:
|
||||||
|
case GL_TRIANGLE_STRIP_ADJACENCY:
|
||||||
|
case GL_PATCHES:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) {
|
||||||
|
if (!IsAcceptedPrimitiveMode(mode)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "mode is not an accepted primitive type."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A geometry stage only accepts the primitive types that decompose into its declared
|
||||||
|
// input primitive (GL 4.6 core 11.3.1); anything else is INVALID_OPERATION. GL_PATCHES
|
||||||
|
// is the tessellation pipeline's input and reaches the geometry stage already
|
||||||
|
// converted, so it is not constrained here.
|
||||||
|
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
||||||
|
const GLenum gsInput = currentProgram ? currentProgram->GetGeometryInputType() : GL_NONE;
|
||||||
|
if (gsInput != GL_NONE && mode != GL_PATCHES) {
|
||||||
|
Bool compatible = false;
|
||||||
|
switch (gsInput) {
|
||||||
|
case GL_POINTS:
|
||||||
|
compatible = mode == GL_POINTS;
|
||||||
|
break;
|
||||||
|
case GL_LINES:
|
||||||
|
compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP;
|
||||||
|
break;
|
||||||
|
case GL_LINES_ADJACENCY:
|
||||||
|
compatible = mode == GL_LINES_ADJACENCY || mode == GL_LINE_STRIP_ADJACENCY;
|
||||||
|
break;
|
||||||
|
case GL_TRIANGLES:
|
||||||
|
compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN;
|
||||||
|
break;
|
||||||
|
case GL_TRIANGLES_ADJACENCY:
|
||||||
|
compatible = mode == GL_TRIANGLES_ADJACENCY || mode == GL_TRIANGLE_STRIP_ADJACENCY;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!compatible) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", functionName,
|
||||||
|
"Primitive mode is incompatible with the geometry shader's input primitive type."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// While transform feedback is active the draw's primitive type must match
|
||||||
|
// the feedback primitive mode (GL 3.3 core 13.2.2). With a geometry shader
|
||||||
|
// the constraint moves to the shader's output primitive type instead, so
|
||||||
|
// the draw mode itself is unconstrained here. A paused span is exempt: it
|
||||||
|
// captures nothing, so there is nothing for the mode to be incompatible with
|
||||||
|
// (GL 4.6 core 13.2.3).
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||||
|
!MG_State::pGLContext->IsTransformFeedbackPaused() &&
|
||||||
|
!(MG_State::pGLContext->GetTransformFeedbackProgram() &&
|
||||||
|
MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) {
|
||||||
|
const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode();
|
||||||
|
Bool compatible = false;
|
||||||
|
switch (feedbackMode) {
|
||||||
|
case GL_POINTS:
|
||||||
|
compatible = mode == GL_POINTS;
|
||||||
|
break;
|
||||||
|
case GL_LINES:
|
||||||
|
compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP;
|
||||||
|
break;
|
||||||
|
case GL_TRIANGLES:
|
||||||
|
compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!compatible) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", functionName,
|
||||||
|
"Primitive mode is incompatible with the active transform feedback primitive mode."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte size of the command structures the indirect draws read (GL 4.6 core 10.3.10).
|
||||||
|
constexpr SizeT kDrawArraysIndirectCommandBytes = 4 * sizeof(Uint32);
|
||||||
|
constexpr SizeT kDrawElementsIndirectCommandBytes = 5 * sizeof(Uint32);
|
||||||
|
|
||||||
|
// Shared preconditions of every *Indirect draw: `indirect` is a byte offset into the
|
||||||
|
// buffer bound to GL_DRAW_INDIRECT_BUFFER, must be 4-byte aligned, and the whole
|
||||||
|
// command has to lie inside that buffer.
|
||||||
|
static Bool ValidateIndirectDrawSource(const char* functionName, const void* indirect, SizeT commandBytes) {
|
||||||
|
const auto offset = reinterpret_cast<uintptr_t>(indirect);
|
||||||
|
if (offset % 4 != 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"indirect offset must be a multiple of 4."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& buffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||||
|
if (!buffer) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"No buffer is bound to GL_DRAW_INDIRECT_BUFFER."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + commandBytes > buffer->GetSize()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"The indirect command extends past the end of the bound "
|
||||||
|
"GL_DRAW_INDIRECT_BUFFER."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index type accepted by the DrawElements family (GL 4.6 core 10.3.9).
|
||||||
|
static Bool ValidateDrawElementsIndexType(const char* functionName, GLenum type) {
|
||||||
|
switch (type) {
|
||||||
|
case GL_UNSIGNED_BYTE:
|
||||||
|
case GL_UNSIGNED_SHORT:
|
||||||
|
case GL_UNSIGNED_INT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "type is not an accepted index type."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 +334,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
|
||||||
@@ -182,6 +473,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Backend does not support compute dispatch."));
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Backend does not support compute dispatch."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||||
dispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
|
dispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,9 +486,32 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"Backend does not support indirect compute dispatch."));
|
"Backend does not support indirect compute dispatch."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||||
dispatchComputeIndirect(indirect);
|
dispatchComputeIndirect(indirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PatchParameteri(GLenum pname, GLint value) {
|
||||||
|
if (pname != GL_PATCH_VERTICES) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pname must be GL_PATCH_VERTICES."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GLint maxPatchVertices = 32;
|
||||||
|
GetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
|
||||||
|
if (value <= 0 || value > maxPatchVertices) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"value must be in [1, GL_MAX_PATCH_VERTICES]."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetPatchVertices(static_cast<Uint>(value));
|
||||||
|
if (const auto patchParameteri = MG_Backend::gBackendFunctionsTable.GL.PatchParameteri) {
|
||||||
|
patchParameteri(pname, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MemoryBarrier(GLbitfield barriers) {
|
void MemoryBarrier(GLbitfield barriers) {
|
||||||
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
|
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
|
||||||
if (!memoryBarrier) {
|
if (!memoryBarrier) {
|
||||||
@@ -221,10 +536,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,65 +575,110 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
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;
|
||||||
|
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
||||||
|
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawElementsIndirectCommandBytes)) 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;
|
||||||
|
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawArraysIndirectCommandBytes)) 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;
|
||||||
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
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;
|
||||||
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +687,489 @@ 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;
|
||||||
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
DrawElements_Backend(mode, count, type, indices);
|
DrawElements_Backend(mode, count, type, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BeginTransformFeedback(GLenum primitiveMode) {
|
||||||
|
if (primitiveMode != GL_POINTS && primitiveMode != GL_LINES && primitiveMode != GL_TRIANGLES) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"primitiveMode must be GL_POINTS, GL_LINES or GL_TRIANGLES."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is already active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& program = MG_State::pGLContext->GetProgramForDraw();
|
||||||
|
if (!program || !program->GetLinkStatus() || program->GetTransformFeedbackVaryingCount() == 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", __func__,
|
||||||
|
"No program with transform feedback varyings is active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Every capture buffer slot the program's mode uses must have a buffer bound. A slot
|
||||||
|
// of stride 0 - two consecutive gl_NextBuffer entries - captures nothing and so needs
|
||||||
|
// no binding.
|
||||||
|
const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount();
|
||||||
|
for (SizeT i = 0; i < usedBufferCount; ++i) {
|
||||||
|
if (program->GetTransformFeedbackStride(static_cast<Uint32>(i)) == 0) continue;
|
||||||
|
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
||||||
|
static_cast<Uint>(i));
|
||||||
|
if (point.GetBoundObject() == nullptr) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", __func__,
|
||||||
|
"Transform feedback buffer binding point " + std::to_string(i) + " has no buffer bound."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->BeginTransformFeedback(primitiveMode, program);
|
||||||
|
if (const auto beginXfb = MG_Backend::gBackendFunctionsTable.GL.BeginTransformFeedback) {
|
||||||
|
beginXfb(primitiveMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vulkan transform feedback captures triangle strips in plain (i, i+1, i+2)
|
||||||
|
// vertex order, but GL decomposes odd strip triangles as (i+1, i, i+2)
|
||||||
|
// (GL 4.6 table 10.1). With the geometry stage's statically-known strip
|
||||||
|
// lengths the captured records are reordered in place: swap the first two
|
||||||
|
// vertex records of every odd triangle within each emitted strip.
|
||||||
|
static void FixupGsStripCaptureOrder(const SharedPtr<MG_State::GLState::ProgramObject>& program,
|
||||||
|
Uint64 inputPrimitives) {
|
||||||
|
// Only Vulkan-order captures need this. A backend that runs the capture on its
|
||||||
|
// own GL/ES driver (it owns the span, hence the EndTransformFeedback entry) has
|
||||||
|
// already produced GL's vertex order, and reordering it again would corrupt it.
|
||||||
|
if (MG_Backend::gBackendFunctionsTable.GL.EndTransformFeedback != nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (program == nullptr || !program->HasGsTriangleStripCaptureFixup() || inputPrimitives == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& stripTriangles = program->GetGsStripTriangles();
|
||||||
|
|
||||||
|
// Global triangle indices whose leading vertex pair must swap.
|
||||||
|
Vector<Uint64> swapTriangles;
|
||||||
|
Uint64 triangleBase = 0;
|
||||||
|
for (Uint64 input = 0; input < inputPrimitives; ++input) {
|
||||||
|
for (const Uint32 stripLength : stripTriangles) {
|
||||||
|
for (Uint32 t = 1; t < stripLength; t += 2) {
|
||||||
|
swapTriangles.push_back(triangleBase + t);
|
||||||
|
}
|
||||||
|
triangleBase += stripLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (swapTriangles.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SizeT bufferIndex = 0; bufferIndex < program->GetTransformFeedbackBufferCount(); ++bufferIndex) {
|
||||||
|
const Uint32 stride = program->GetTransformFeedbackStride(static_cast<Uint32>(bufferIndex));
|
||||||
|
if (stride == 0) continue;
|
||||||
|
const auto& bindingPoint =
|
||||||
|
MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
||||||
|
static_cast<Uint>(bufferIndex));
|
||||||
|
const auto& buffer = bindingPoint.GetBoundObject();
|
||||||
|
if (buffer == nullptr) continue;
|
||||||
|
const Range1D range = bindingPoint.GetRange();
|
||||||
|
const Uint8* mapped = buffer->MappedData();
|
||||||
|
if (mapped == nullptr) continue;
|
||||||
|
// The geometry stage amplifies, so the CPU vertex counter does not bound
|
||||||
|
// the capture; the binding range's whole-triangle capacity does.
|
||||||
|
const Uint64 rangeBytes = range.end > range.start ? static_cast<Uint64>(range.end - range.start) : 0;
|
||||||
|
const Uint64 capturedTriangles = std::min<Uint64>(triangleBase, (rangeBytes / stride) / 3);
|
||||||
|
|
||||||
|
// Observed Vulkan capture order for odd strip triangles is (i, i+2, i+1)
|
||||||
|
// (winding preserved by swapping the trailing pair); GL wants
|
||||||
|
// (i+1, i, i+2), which is one rotation away: (a,b,c) -> (c,a,b).
|
||||||
|
Vector<Uint8> scratch(stride);
|
||||||
|
for (const Uint64 triangle : swapTriangles) {
|
||||||
|
if (triangle >= capturedTriangles) break;
|
||||||
|
const SizeT v0Offset = static_cast<SizeT>(range.start) + static_cast<SizeT>(triangle * 3) * stride;
|
||||||
|
const SizeT v1Offset = v0Offset + stride;
|
||||||
|
const SizeT v2Offset = v1Offset + stride;
|
||||||
|
Memcpy(scratch.data(), mapped + v2Offset, stride);
|
||||||
|
buffer->WritebackFromBackend({const_cast<Uint8*>(mapped) + v1Offset, stride}, v2Offset);
|
||||||
|
buffer->WritebackFromBackend({const_cast<Uint8*>(mapped) + v0Offset, stride}, v1Offset);
|
||||||
|
buffer->WritebackFromBackend({scratch.data(), stride}, v0Offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndTransformFeedback(void) {
|
||||||
|
if (!MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is not active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto capturedProgram = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||||
|
const Uint64 inputPrimitives = MG_State::pGLContext->GetTransformFeedbackInputPrimitives();
|
||||||
|
// Closed while the capture state is still active: a backend that captures
|
||||||
|
// through its own driver reads the capture program and buffer bindings here.
|
||||||
|
if (const auto endXfb = MG_Backend::gBackendFunctionsTable.GL.EndTransformFeedback) {
|
||||||
|
endXfb();
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->EndTransformFeedback();
|
||||||
|
// Captured results must be visible to MapBuffer/GetBufferSubData after
|
||||||
|
// End; the capture targets are host-coherent GPU memory, so completing
|
||||||
|
// the GPU work is all that is required.
|
||||||
|
auto& backendGL = MG_Backend::gBackendFunctionsTable.GL;
|
||||||
|
if (backendGL.FenceSync && backendGL.ClientWaitSync) {
|
||||||
|
if (auto sync = backendGL.FenceSync()) {
|
||||||
|
backendGL.ClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, ~0ull);
|
||||||
|
if (backendGL.DeleteSync) {
|
||||||
|
backendGL.DeleteSync(sync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FixupGsStripCaptureOrder(capturedProgram, inputPrimitives);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PauseTransformFeedback(void) {
|
||||||
|
if (!MG_State::pGLContext->IsTransformFeedbackActive() ||
|
||||||
|
MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Transform feedback is not active, or is already paused."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetTransformFeedbackPaused(true);
|
||||||
|
if (const auto pauseXfb = MG_Backend::gBackendFunctionsTable.GL.PauseTransformFeedback) {
|
||||||
|
pauseXfb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResumeTransformFeedback(void) {
|
||||||
|
if (!MG_State::pGLContext->IsTransformFeedbackActive() ||
|
||||||
|
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is not paused."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetTransformFeedbackPaused(false);
|
||||||
|
if (const auto resumeXfb = MG_Backend::gBackendFunctionsTable.GL.ResumeTransformFeedback) {
|
||||||
|
resumeXfb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenTransformFeedbacks(GLsizei n, GLuint* ids) {
|
||||||
|
if (n < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n == 0 || ids == nullptr) return;
|
||||||
|
Vector<Uint> names;
|
||||||
|
MG_State::pGLContext->GenTransformFeedbackNames(static_cast<Uint>(n), names);
|
||||||
|
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateTransformFeedbacks(GLsizei n, GLuint* ids) {
|
||||||
|
if (n < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n == 0 || ids == nullptr) return;
|
||||||
|
Vector<Uint> names;
|
||||||
|
MG_State::pGLContext->GenTransformFeedbackNames(static_cast<Uint>(n), names);
|
||||||
|
// Unlike glGenTransformFeedbacks, the names are objects immediately: there is no bind step
|
||||||
|
// to create them from (GL 4.6 core 13.2.1).
|
||||||
|
for (const Uint name : names) {
|
||||||
|
MG_State::pGLContext->CreateTransformFeedbackObject(name);
|
||||||
|
}
|
||||||
|
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Shared front half of the by-name transform feedback entry points: the object has to exist
|
||||||
|
// (INVALID_OPERATION otherwise) before anything else about the call is looked at.
|
||||||
|
Bool ValidateNamedTransformFeedback(GLuint xfb, const char* functionName) {
|
||||||
|
if (!MG_State::pGLContext->IsTransformFeedbackObject(xfb)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
std::to_string(xfb) + " is not a transform feedback object."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ValidateTransformFeedbackBufferIndex(GLuint index, const char* functionName) {
|
||||||
|
if (index >= MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"index exceeds GL_MAX_TRANSFORM_FEEDBACK_BUFFERS."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A capture binding may not be changed while the object is capturing (GL 4.6 core 13.2.2).
|
||||||
|
Bool ValidateNamedTransformFeedbackNotActive(GLuint xfb, const char* functionName) {
|
||||||
|
if (MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"The transform feedback object is capturing."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPtr<MG_State::GLState::BufferObject> ResolveTransformFeedbackBuffer(GLuint buffer,
|
||||||
|
const char* functionName) {
|
||||||
|
if (buffer == 0) return nullptr;
|
||||||
|
if (!MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
std::to_string(buffer) + " is not a buffer object."));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||||
|
if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return;
|
||||||
|
if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
std::to_string(buffer) + " is not a buffer object."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index,
|
||||||
|
ResolveTransformFeedbackBuffer(buffer, __func__), {},
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||||
|
if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return;
|
||||||
|
if (offset < 0 || size <= 0 || (offset % 4) != 0 || (size % 4) != 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"offset and size must be non-negative multiples of 4."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
std::to_string(buffer) + " is not a buffer object."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto bufferObject = ResolveTransformFeedbackBuffer(buffer, __func__);
|
||||||
|
const Range1D range{static_cast<SizeT>(offset), static_cast<SizeT>(offset) + static_cast<SizeT>(size)};
|
||||||
|
MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index, bufferObject, range,
|
||||||
|
bufferObject != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (!param) return;
|
||||||
|
switch (pname) {
|
||||||
|
case GL_TRANSFORM_FEEDBACK_ACTIVE:
|
||||||
|
*param = MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb) ? GL_TRUE : GL_FALSE;
|
||||||
|
return;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_PAUSED:
|
||||||
|
*param = MG_State::pGLContext->IsNamedTransformFeedbackPaused(xfb) ? GL_TRUE : GL_FALSE;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"pname must be GL_TRANSFORM_FEEDBACK_ACTIVE or _PAUSED."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_BINDING) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"pname must be GL_TRANSFORM_FEEDBACK_BUFFER_BINDING."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||||
|
if (!param) return;
|
||||||
|
const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index);
|
||||||
|
*param = binding.Buffer ? static_cast<GLint>(binding.Buffer->GetExternalIndex()) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_START && pname != GL_TRANSFORM_FEEDBACK_BUFFER_SIZE) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"pname must be GL_TRANSFORM_FEEDBACK_BUFFER_START or _SIZE."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||||
|
if (!param) return;
|
||||||
|
const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index);
|
||||||
|
// glTransformFeedbackBufferBase leaves both at zero; only the range form sets them
|
||||||
|
// (GL 4.6 core table 23.48).
|
||||||
|
if (!binding.Buffer || !binding.HasExplicitRange) {
|
||||||
|
*param = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*param = (pname == GL_TRANSFORM_FEEDBACK_BUFFER_START)
|
||||||
|
? static_cast<GLint64>(binding.Range.start)
|
||||||
|
: static_cast<GLint64>(binding.Range.end - binding.Range.start);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) {
|
||||||
|
if (n < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ids == nullptr) return;
|
||||||
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
|
const GLuint id = ids[i];
|
||||||
|
// Unknown names and 0 are silently ignored; an object whose capture span is
|
||||||
|
// still open is not (GL 4.6 core 13.2.1).
|
||||||
|
if (id == 0 || !MG_State::pGLContext->ValidateTransformFeedbackName(id)) continue;
|
||||||
|
if (id == MG_State::pGLContext->GetBoundTransformFeedbackName() &&
|
||||||
|
MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Cannot delete a transform feedback object whose capture is active."));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const auto deleteXfb = MG_Backend::gBackendFunctionsTable.GL.DeleteTransformFeedback) {
|
||||||
|
deleteXfb(id);
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->MarkTransformFeedbackObjectForDeletion(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindTransformFeedback(GLenum target, GLuint id) {
|
||||||
|
if (target != GL_TRANSFORM_FEEDBACK) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "target must be GL_TRANSFORM_FEEDBACK."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// A running capture pins its object; only a paused one may be swapped out.
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||||
|
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Transform feedback is active and not paused."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
std::to_string(id) + " is not a transform feedback object name."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->BindTransformFeedbackObject(id);
|
||||||
|
if (const auto bindXfb = MG_Backend::gBackendFunctionsTable.GL.BindTransformFeedback) {
|
||||||
|
bindXfb(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLboolean IsTransformFeedback(GLuint id) {
|
||||||
|
// Name 0 is the default object, and a name glGenTransformFeedbacks handed out only
|
||||||
|
// becomes the name of an object once it has been bound.
|
||||||
|
return MG_State::pGLContext->IsTransformFeedbackObject(id) ? GL_TRUE : GL_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// glDrawTransformFeedback[Stream][Instanced]: replays the vertices the named object
|
||||||
|
// captured in its last completed span, as if by glDrawArraysInstanced with that count
|
||||||
|
// (GL 4.6 core 10.3.7).
|
||||||
|
static void DrawTransformFeedbackImpl(const char* functionName, GLenum mode, GLuint id, GLuint stream,
|
||||||
|
GLsizei instancecount) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(functionName)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(functionName, mode)) return;
|
||||||
|
if (instancecount < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "instancecount must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
std::to_string(id) + " is not a transform feedback object name."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// GL_MAX_VERTEX_STREAMS is 1, so stream 0 is the only one that exists.
|
||||||
|
if (stream != 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"stream must be less than GL_MAX_VERTEX_STREAMS."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Drawing from an object whose capture is currently open is legal and deliberate:
|
||||||
|
// it is how a transform feedback result is fed straight back into the next span
|
||||||
|
// (ARB_transform_feedback2 lists no such restriction).
|
||||||
|
if (!MG_State::pGLContext->HasTransformFeedbackCompletedSpan(id)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"glEndTransformFeedback has never been called for this object."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Uint64 vertices = MG_State::pGLContext->GetTransformFeedbackRecordedVertices(id);
|
||||||
|
if (vertices == 0) return;
|
||||||
|
const auto count = static_cast<GLsizei>(vertices);
|
||||||
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
|
if (instancecount == 1) {
|
||||||
|
DrawArrays_Backend(mode, 0, count);
|
||||||
|
} else {
|
||||||
|
DrawArraysInstanced_Backend(mode, 0, count, instancecount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawTransformFeedback(GLenum mode, GLuint id) {
|
||||||
|
DrawTransformFeedbackImpl(__func__, mode, id, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount) {
|
||||||
|
DrawTransformFeedbackImpl(__func__, mode, id, 0, instancecount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream) {
|
||||||
|
DrawTransformFeedbackImpl(__func__, mode, id, stream, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) {
|
||||||
|
DrawTransformFeedbackImpl(__func__, mode, id, stream, instancecount);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -11,8 +11,27 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
|
void BeginTransformFeedback(GLenum primitiveMode);
|
||||||
|
void EndTransformFeedback(void);
|
||||||
|
void PauseTransformFeedback(void);
|
||||||
|
void ResumeTransformFeedback(void);
|
||||||
|
void GenTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||||
|
void CreateTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||||
|
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids);
|
||||||
|
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer);
|
||||||
|
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||||
|
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param);
|
||||||
|
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param);
|
||||||
|
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param);
|
||||||
|
void BindTransformFeedback(GLenum target, GLuint id);
|
||||||
|
GLboolean IsTransformFeedback(GLuint id);
|
||||||
|
void DrawTransformFeedback(GLenum mode, GLuint id);
|
||||||
|
void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount);
|
||||||
|
void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream);
|
||||||
|
void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount);
|
||||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
void DispatchComputeIndirect(GLintptr indirect);
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
|
void PatchParameteri(GLenum pname, GLint value);
|
||||||
void MemoryBarrier(GLbitfield barriers);
|
void MemoryBarrier(GLbitfield barriers);
|
||||||
void MemoryBarrierByRegion(GLbitfield barriers);
|
void MemoryBarrierByRegion(GLbitfield 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);
|
||||||
@@ -38,6 +57,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
void DrawArraysIndirect(GLenum mode, const void* 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);
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||||
|
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||||
GLsizei drawcount);
|
GLsizei 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,
|
||||||
|
|||||||
@@ -11,12 +11,16 @@
|
|||||||
#include "../Getter/GL_Getter.h"
|
#include "../Getter/GL_Getter.h"
|
||||||
#include "../Sampler/GL_Sampler.h"
|
#include "../Sampler/GL_Sampler.h"
|
||||||
#include "../Sync/GL_Sync.h"
|
#include "../Sync/GL_Sync.h"
|
||||||
|
#include "../Query/GL_Query.h"
|
||||||
#include "../Texture/GL_Texture.h"
|
#include "../Texture/GL_Texture.h"
|
||||||
#include "../Drawing/GL_Drawing.h"
|
#include "../Drawing/GL_Drawing.h"
|
||||||
#include "../Program/GL_Program.h"
|
#include "../Program/GL_Program.h"
|
||||||
|
#include "../Program/GL_ProgramPipeline.h"
|
||||||
#include "../RenderState/GL_RenderState.h"
|
#include "../RenderState/GL_RenderState.h"
|
||||||
#include "../Framebuffer/GL_Framebuffer.h"
|
#include "../Framebuffer/GL_Framebuffer.h"
|
||||||
#include "../VertexArray/GL_VertexArray.h"
|
#include "../VertexArray/GL_VertexArray.h"
|
||||||
|
#include "../Sync/GL_Sync.h"
|
||||||
|
#include <MG_State/GLState/Core.h>
|
||||||
|
|
||||||
#define DECLARE_GL_FUNCTION_STUB_HEAD(type, name, ...) MOBILEGL_GL_API type gl##name(__VA_ARGS__) {
|
#define DECLARE_GL_FUNCTION_STUB_HEAD(type, name, ...) MOBILEGL_GL_API type gl##name(__VA_ARGS__) {
|
||||||
|
|
||||||
@@ -76,7 +80,7 @@ DECLARE_GL_FUNCTION_HEAD(void, BufferSubData, GLenum target, GLintptr offset, GL
|
|||||||
DECLARE_GL_FUNCTION_HEAD(GLenum, CheckFramebufferStatus, GLenum target) DECLARE_GL_FUNCTION_END(GLenum, CheckFramebufferStatus, target)
|
DECLARE_GL_FUNCTION_HEAD(GLenum, CheckFramebufferStatus, GLenum target) DECLARE_GL_FUNCTION_END(GLenum, CheckFramebufferStatus, target)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Clear, GLbitfield mask) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Clear, mask)
|
DECLARE_GL_FUNCTION_HEAD(void, Clear, GLbitfield mask) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Clear, mask)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearColor, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearColor, red, green, blue, alpha)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearColor, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearColor, red, green, blue, alpha)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearDepthf, GLfloat d) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearDepthf, d)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearDepthf, GLfloat d) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearDepth, static_cast<GLclampd>(d))
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearStencil, GLint s) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearStencil, s)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearStencil, GLint s) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearStencil, s)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ColorMask, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ColorMask, red, green, blue, alpha)
|
DECLARE_GL_FUNCTION_HEAD(void, ColorMask, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ColorMask, red, green, blue, alpha)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CompileShader, GLuint shader) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompileShader, shader)
|
DECLARE_GL_FUNCTION_HEAD(void, CompileShader, GLuint shader) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompileShader, shader)
|
||||||
@@ -95,7 +99,7 @@ DECLARE_GL_FUNCTION_HEAD(void, DeleteShader, GLuint shader) DECLARE_GL_FUNCTION_
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, DeleteTextures, GLsizei n, const GLuint* textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteTextures, n, textures)
|
DECLARE_GL_FUNCTION_HEAD(void, DeleteTextures, GLsizei n, const GLuint* textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteTextures, n, textures)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DepthFunc, GLenum func) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DepthFunc, func)
|
DECLARE_GL_FUNCTION_HEAD(void, DepthFunc, GLenum func) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DepthFunc, func)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DepthMask, GLboolean flag) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DepthMask, flag)
|
DECLARE_GL_FUNCTION_HEAD(void, DepthMask, GLboolean flag) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DepthMask, flag)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DepthRangef, GLfloat n, GLfloat f) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DepthRangef, n, f)
|
DECLARE_GL_FUNCTION_HEAD(void, DepthRangef, GLfloat n, GLfloat f) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DepthRange, static_cast<GLclampd>(n), static_cast<GLclampd>(f))
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DetachShader, GLuint program, GLuint shader) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DetachShader, program, shader)
|
DECLARE_GL_FUNCTION_HEAD(void, DetachShader, GLuint program, GLuint shader) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DetachShader, program, shader)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Disable, GLenum cap) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Disable, cap)
|
DECLARE_GL_FUNCTION_HEAD(void, Disable, GLenum cap) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Disable, cap)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DisableVertexAttribArray, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DisableVertexAttribArray, index)
|
DECLARE_GL_FUNCTION_HEAD(void, DisableVertexAttribArray, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DisableVertexAttribArray, index)
|
||||||
@@ -117,10 +121,10 @@ DECLARE_GL_FUNCTION_HEAD(void, GetActiveAttrib, GLuint program, GLuint index, GL
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniform, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniform, program, index, bufSize, length, size, type, name)
|
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniform, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniform, program, index, bufSize, length, size, type, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetAttachedShaders, GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetAttachedShaders, program, maxCount, count, shaders)
|
DECLARE_GL_FUNCTION_HEAD(void, GetAttachedShaders, GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetAttachedShaders, program, maxCount, count, shaders)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLint, GetAttribLocation, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetAttribLocation, program, name)
|
DECLARE_GL_FUNCTION_HEAD(GLint, GetAttribLocation, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetAttribLocation, program, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetBooleanv, GLenum pname, GLboolean* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetBooleanv, pname, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetBooleanv, GLenum pname, GLboolean* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBooleanv, pname, data)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetBufferParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBufferParameteriv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetBufferParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBufferParameteriv, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLenum, GetError) DECLARE_GL_FUNCTION_END(GLenum, GetError)
|
DECLARE_GL_FUNCTION_HEAD(GLenum, GetError) DECLARE_GL_FUNCTION_END(GLenum, GetError)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetFloatv, GLenum pname, GLfloat* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetFloatv, pname, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetFloatv, GLenum pname, GLfloat* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetFloatv, pname, data)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetFramebufferAttachmentParameteriv, GLenum target, GLenum attachment, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetFramebufferAttachmentParameteriv, target, attachment, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetFramebufferAttachmentParameteriv, GLenum target, GLenum attachment, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetFramebufferAttachmentParameteriv, target, attachment, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetIntegerv, GLenum pname, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegerv, pname, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetIntegerv, GLenum pname, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegerv, pname, data)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramiv, GLuint program, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramiv, program, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetProgramiv, GLuint program, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramiv, program, pname, params)
|
||||||
@@ -135,9 +139,9 @@ DECLARE_GL_FUNCTION_HEAD(void, GetTexParameteriv, GLenum target, GLenum pname, G
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GetUniformfv, GLuint program, GLint location, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformfv, program, location, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetUniformfv, GLuint program, GLint location, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformfv, program, location, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetUniformiv, GLuint program, GLint location, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformiv, program, location, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetUniformiv, GLuint program, GLint location, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformiv, program, location, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLint, GetUniformLocation, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetUniformLocation, program, name)
|
DECLARE_GL_FUNCTION_HEAD(GLint, GetUniformLocation, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetUniformLocation, program, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexAttribfv, GLuint index, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexAttribfv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribfv, GLuint index, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribfv, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexAttribiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexAttribiv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribiv, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexAttribPointerv, GLuint index, GLenum pname, void** pointer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexAttribPointerv, index, pname, pointer)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribPointerv, GLuint index, GLenum pname, void** pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribPointerv, index, pname, pointer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Hint, GLenum target, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Hint, target, mode)
|
DECLARE_GL_FUNCTION_HEAD(void, Hint, GLenum target, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Hint, target, mode)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsBuffer, GLuint buffer) DECLARE_GL_FUNCTION_END(GLboolean, IsBuffer, buffer)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsBuffer, GLuint buffer) DECLARE_GL_FUNCTION_END(GLboolean, IsBuffer, buffer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsEnabled, GLenum cap) DECLARE_GL_FUNCTION_END(GLboolean, IsEnabled, cap)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsEnabled, GLenum cap) DECLARE_GL_FUNCTION_END(GLboolean, IsEnabled, cap)
|
||||||
@@ -190,14 +194,14 @@ DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3fv, GLint location, GLsizei count,
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4fv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4fv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, UseProgram, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UseProgram, program)
|
DECLARE_GL_FUNCTION_HEAD(void, UseProgram, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UseProgram, program)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ValidateProgram, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ValidateProgram, program)
|
DECLARE_GL_FUNCTION_HEAD(void, ValidateProgram, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ValidateProgram, program)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1f, GLuint index, GLfloat x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1f, index, x)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1f, GLuint index, GLfloat x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1f, index, x)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1fv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1fv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1fv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1fv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2f, GLuint index, GLfloat x, GLfloat y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2f, index, x, y)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2f, GLuint index, GLfloat x, GLfloat y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2f, index, x, y)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2fv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2fv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2fv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2fv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3f, GLuint index, GLfloat x, GLfloat y, GLfloat z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3f, index, x, y, z)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3f, GLuint index, GLfloat x, GLfloat y, GLfloat z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3f, index, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3fv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3fv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3fv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3fv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4f, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4f, index, x, y, z, w)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4f, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4f, index, x, y, z, w)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4fv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4fv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4fv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4fv, index, v)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribPointer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribPointer, index, size, type, normalized, stride, pointer)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribPointer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribPointer, index, size, type, normalized, stride, pointer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Viewport, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Viewport, x, y, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, Viewport, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Viewport, x, y, width, height)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ReadBuffer, GLenum src) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ReadBuffer, src)
|
DECLARE_GL_FUNCTION_HEAD(void, ReadBuffer, GLenum src) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ReadBuffer, src)
|
||||||
@@ -207,22 +211,22 @@ DECLARE_GL_FUNCTION_HEAD(void, TexSubImage3D, GLenum target, GLint level, GLint
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, CopyTexSubImage3D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTexSubImage3D, target, level, xoffset, yoffset, zoffset, x, y, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, CopyTexSubImage3D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTexSubImage3D, target, level, xoffset, yoffset, zoffset, x, y, width, height)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CompressedTexImage3D, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompressedTexImage3D, target, level, internalformat, width, height, depth, border, imageSize, data)
|
DECLARE_GL_FUNCTION_HEAD(void, CompressedTexImage3D, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompressedTexImage3D, target, level, internalformat, width, height, depth, border, imageSize, data)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CompressedTexSubImage3D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompressedTexSubImage3D, target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data)
|
DECLARE_GL_FUNCTION_HEAD(void, CompressedTexSubImage3D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompressedTexSubImage3D, target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenQueries, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenQueries, n, ids)
|
DECLARE_GL_FUNCTION_HEAD(void, GenQueries, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenQueries, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteQueries, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteQueries, n, ids)
|
DECLARE_GL_FUNCTION_HEAD(void, DeleteQueries, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteQueries, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsQuery, GLuint id) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsQuery, id)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsQuery, GLuint id) DECLARE_GL_FUNCTION_END(GLboolean, IsQuery, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginQuery, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginQuery, target, id)
|
DECLARE_GL_FUNCTION_HEAD(void, BeginQuery, GLenum target, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginQuery, target, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndQuery, GLenum target) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndQuery, target)
|
DECLARE_GL_FUNCTION_HEAD(void, EndQuery, GLenum target) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndQuery, target)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryiv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryiv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryiv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryiv, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryObjectuiv, GLuint id, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryObjectuiv, id, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjectuiv, GLuint id, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjectuiv, id, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, UnmapBuffer, GLenum target) DECLARE_GL_FUNCTION_END(GLboolean, UnmapBuffer, target)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, UnmapBuffer, GLenum target) DECLARE_GL_FUNCTION_END(GLboolean, UnmapBuffer, target)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetBufferPointerv, GLenum target, GLenum pname, void** params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBufferPointerv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetBufferPointerv, GLenum target, GLenum pname, void** params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBufferPointerv, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawBuffers, GLsizei n, const GLenum* bufs) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawBuffers, n, bufs)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawBuffers, GLsizei n, const GLenum* bufs) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawBuffers, n, bufs)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2x3fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2x3fv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x3fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2x3fv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3x2fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3x2fv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x2fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x2fv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2x4fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2x4fv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x4fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2x4fv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4x2fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4x2fv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x2fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x2fv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3x4fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3x4fv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x4fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x4fv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4x3fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4x3fv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x3fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x3fv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlitFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter)
|
DECLARE_GL_FUNCTION_HEAD(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlitFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter)
|
||||||
//DECLARE_GL_FUNCTION_HEAD(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlitFramebuffer, srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter)
|
//DECLARE_GL_FUNCTION_HEAD(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlitFramebuffer, srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, RenderbufferStorageMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, RenderbufferStorageMultisample, target, samples, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, RenderbufferStorageMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, RenderbufferStorageMultisample, target, samples, internalformat, width, height)
|
||||||
@@ -233,36 +237,36 @@ DECLARE_GL_FUNCTION_HEAD(void, DeleteVertexArrays, GLsizei n, const GLuint* arra
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GenVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenVertexArrays, n, arrays)
|
DECLARE_GL_FUNCTION_HEAD(void, GenVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenVertexArrays, n, arrays)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsVertexArray, GLuint array) DECLARE_GL_FUNCTION_END(GLboolean, IsVertexArray, array)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsVertexArray, GLuint array) DECLARE_GL_FUNCTION_END(GLboolean, IsVertexArray, array)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetIntegeri_v, GLenum target, GLuint index, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegeri_v, target, index, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetIntegeri_v, GLenum target, GLuint index, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegeri_v, target, index, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginTransformFeedback, GLenum primitiveMode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginTransformFeedback, primitiveMode)
|
DECLARE_GL_FUNCTION_HEAD(void, BeginTransformFeedback, GLenum primitiveMode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginTransformFeedback, primitiveMode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndTransformFeedback)
|
DECLARE_GL_FUNCTION_HEAD(void, EndTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndTransformFeedback)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindBufferRange, GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferRange, target, index, buffer, offset, size)
|
DECLARE_GL_FUNCTION_HEAD(void, BindBufferRange, GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferRange, target, index, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindBufferBase, GLenum target, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferBase, target, index, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, BindBufferBase, GLenum target, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferBase, target, index, buffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackVaryings, GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackVaryings, program, count, varyings, bufferMode)
|
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackVaryings, GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackVaryings, program, count, varyings, bufferMode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackVarying, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackVarying, program, index, bufSize, length, size, type, name)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackVarying, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackVarying, program, index, bufSize, length, size, type, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribIPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribIPointer, index, size, type, stride, pointer)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribIPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribIPointer, index, size, type, stride, pointer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexAttribIiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexAttribIiv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIiv, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexAttribIuiv, GLuint index, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexAttribIuiv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIuiv, GLuint index, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIuiv, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4i, GLuint index, GLint x, GLint y, GLint z, GLint w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4i, index, x, y, z, w)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4i, GLuint index, GLint x, GLint y, GLint z, GLint w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4i, index, x, y, z, w)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4ui, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4ui, index, x, y, z, w)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4ui, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4ui, index, x, y, z, w)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4iv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4iv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4uiv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4uiv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformuiv, GLuint program, GLint location, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformuiv, program, location, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetUniformuiv, GLuint program, GLint location, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformuiv, program, location, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLint, GetFragDataLocation, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetFragDataLocation, program, name)
|
DECLARE_GL_FUNCTION_HEAD(GLint, GetFragDataLocation, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetFragDataLocation, program, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform1ui, GLint location, GLuint v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1ui, location, v0)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform1ui, GLint location, GLuint v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1ui, location, v0)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform2ui, GLint location, GLuint v0, GLuint v1) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform2ui, location, v0, v1)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform2ui, GLint location, GLuint v0, GLuint v1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform2ui, location, v0, v1)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform3ui, GLint location, GLuint v0, GLuint v1, GLuint v2) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform3ui, location, v0, v1, v2)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform3ui, GLint location, GLuint v0, GLuint v1, GLuint v2) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform3ui, location, v0, v1, v2)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform4ui, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform4ui, location, v0, v1, v2, v3)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform4ui, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform4ui, location, v0, v1, v2, v3)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform1uiv, GLint location, GLsizei count, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1uiv, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform1uiv, GLint location, GLsizei count, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1uiv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform2uiv, GLint location, GLsizei count, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform2uiv, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform2uiv, GLint location, GLsizei count, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform2uiv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform3uiv, GLint location, GLsizei count, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform3uiv, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform3uiv, GLint location, GLsizei count, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform3uiv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform4uiv, GLint location, GLsizei count, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform4uiv, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform4uiv, GLint location, GLsizei count, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform4uiv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferiv, GLenum buffer, GLint drawbuffer, const GLint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferiv, buffer, drawbuffer, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferiv, GLenum buffer, GLint drawbuffer, const GLint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferiv, buffer, drawbuffer, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferuiv, GLenum buffer, GLint drawbuffer, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferuiv, buffer, drawbuffer, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferuiv, GLenum buffer, GLint drawbuffer, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferuiv, buffer, drawbuffer, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferfv, GLenum buffer, GLint drawbuffer, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferfv, buffer, drawbuffer, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferfv, GLenum buffer, GLint drawbuffer, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferfv, buffer, drawbuffer, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferfi, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferfi, buffer, drawbuffer, depth, stencil)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferfi, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferfi, buffer, drawbuffer, depth, stencil)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CopyBufferSubData, GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyBufferSubData, readTarget, writeTarget, readOffset, writeOffset, size)
|
DECLARE_GL_FUNCTION_HEAD(void, CopyBufferSubData, GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyBufferSubData, readTarget, writeTarget, readOffset, writeOffset, size)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformIndices, GLuint program, GLsizei uniformCount, const GLchar* const* uniformNames, GLuint* uniformIndices) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformIndices, program, uniformCount, uniformNames, uniformIndices)
|
DECLARE_GL_FUNCTION_HEAD(void, GetUniformIndices, GLuint program, GLsizei uniformCount, const GLchar* const* uniformNames, GLuint* uniformIndices) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformIndices, program, uniformCount, uniformNames, uniformIndices)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveUniformsiv, GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveUniformsiv, program, uniformCount, uniformIndices, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniformsiv, GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniformsiv, program, uniformCount, uniformIndices, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLuint, GetUniformBlockIndex, GLuint program, const GLchar* uniformBlockName) DECLARE_GL_FUNCTION_END(GLuint, GetUniformBlockIndex, program, uniformBlockName)
|
DECLARE_GL_FUNCTION_HEAD(GLuint, GetUniformBlockIndex, GLuint program, const GLchar* uniformBlockName) DECLARE_GL_FUNCTION_END(GLuint, GetUniformBlockIndex, program, uniformBlockName)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniformBlockiv, GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniformBlockiv, program, uniformBlockIndex, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniformBlockiv, GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniformBlockiv, program, uniformBlockIndex, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniformBlockName, GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformBlockName) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniformBlockName, program, uniformBlockIndex, bufSize, length, uniformBlockName)
|
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniformBlockName, GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformBlockName) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniformBlockName, program, uniformBlockIndex, bufSize, length, uniformBlockName)
|
||||||
@@ -289,39 +293,39 @@ DECLARE_GL_FUNCTION_HEAD(void, SamplerParameterfv, GLuint sampler, GLenum pname,
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameteriv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameteriv, sampler, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameteriv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameteriv, sampler, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterfv, GLuint sampler, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterfv, sampler, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterfv, GLuint sampler, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterfv, sampler, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribDivisor, GLuint index, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribDivisor, index, divisor)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribDivisor, GLuint index, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribDivisor, index, divisor)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedback, target, id)
|
DECLARE_GL_FUNCTION_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindTransformFeedback, target, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids)
|
DECLARE_GL_FUNCTION_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
|
DECLARE_GL_FUNCTION_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsTransformFeedback, GLuint id) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsTransformFeedback, id)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsTransformFeedback, GLuint id) DECLARE_GL_FUNCTION_END(GLboolean, IsTransformFeedback, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedback)
|
DECLARE_GL_FUNCTION_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PauseTransformFeedback)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedback)
|
DECLARE_GL_FUNCTION_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ResumeTransformFeedback)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary)
|
DECLARE_GL_FUNCTION_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramBinary, GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramBinary, program, binaryFormat, binary, length)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramBinary, GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramBinary, program, binaryFormat, binary, length)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramParameteri, GLuint program, GLenum pname, GLint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramParameteri, program, pname, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramParameteri, GLuint program, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramParameteri, program, pname, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateFramebuffer, target, numAttachments, attachments)
|
DECLARE_GL_FUNCTION_HEAD(void, InvalidateFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_END_NO_RETURN(void, InvalidateFramebuffer, target, numAttachments, attachments)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateSubFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateSubFramebuffer, target, numAttachments, attachments, x, y, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, InvalidateSubFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, InvalidateSubFramebuffer, target, numAttachments, attachments, x, y, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexStorage2D, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexStorage2D, target, levels, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, TexStorage2D, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage2D, target, levels, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexStorage3D, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexStorage3D, target, levels, internalformat, width, height, depth)
|
DECLARE_GL_FUNCTION_HEAD(void, TexStorage3D, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage3D, target, levels, internalformat, width, height, depth)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformativ, GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformativ, target, internalformat, pname, bufSize, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetInternalformativ, GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetInternalformativ, target, internalformat, pname, bufSize, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DispatchCompute, GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DispatchCompute, num_groups_x, num_groups_y, num_groups_z)
|
DECLARE_GL_FUNCTION_HEAD(void, DispatchCompute, GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DispatchCompute, num_groups_x, num_groups_y, num_groups_z)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DispatchComputeIndirect, GLintptr indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DispatchComputeIndirect, indirect)
|
DECLARE_GL_FUNCTION_HEAD(void, DispatchComputeIndirect, GLintptr indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DispatchComputeIndirect, indirect)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawArraysIndirect, GLenum mode, const void* indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawArraysIndirect, mode, indirect)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawArraysIndirect, GLenum mode, const void* indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawArraysIndirect, mode, indirect)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsIndirect, GLenum mode, GLenum type, const void* indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsIndirect, mode, type, indirect)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsIndirect, GLenum mode, GLenum type, const void* indirect) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsIndirect, mode, type, indirect)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferParameteri, GLenum target, GLenum pname, GLint param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferParameteri, target, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, FramebufferParameteri, GLenum target, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferParameteri, target, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetFramebufferParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetFramebufferParameteriv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetFramebufferParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetFramebufferParameteriv, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramInterfaceiv, GLuint program, GLenum programInterface, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramInterfaceiv, program, programInterface, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetProgramInterfaceiv, GLuint program, GLenum programInterface, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramInterfaceiv, program, programInterface, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLuint, GetProgramResourceIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLuint, GetProgramResourceIndex, program, programInterface, name)
|
DECLARE_GL_FUNCTION_HEAD(GLuint, GetProgramResourceIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLuint, GetProgramResourceIndex, program, programInterface, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceName, GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramResourceName, program, programInterface, index, bufSize, length, name)
|
DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceName, GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramResourceName, program, programInterface, index, bufSize, length, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceiv, GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramResourceiv, program, programInterface, index, propCount, props, bufSize, length, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceiv, GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramResourceiv, program, programInterface, index, propCount, props, bufSize, length, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocation, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocation, program, programInterface, name)
|
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocation, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocation, program, programInterface, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UseProgramStages, GLuint pipeline, GLbitfield stages, GLuint program) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UseProgramStages, pipeline, stages, program)
|
DECLARE_GL_FUNCTION_HEAD(void, UseProgramStages, GLuint pipeline, GLbitfield stages, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UseProgramStages, pipeline, stages, program)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ActiveShaderProgram, GLuint pipeline, GLuint program) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ActiveShaderProgram, pipeline, program)
|
DECLARE_GL_FUNCTION_HEAD(void, ActiveShaderProgram, GLuint pipeline, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ActiveShaderProgram, pipeline, program)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, CreateShaderProgramv, GLenum type, GLsizei count, const GLchar* const* strings) DECLARE_GL_FUNCTION_STUB_END(GLuint, CreateShaderProgramv, type, count, strings)
|
DECLARE_GL_FUNCTION_HEAD(GLuint, CreateShaderProgramv, GLenum type, GLsizei count, const GLchar* const* strings) DECLARE_GL_FUNCTION_END(GLuint, CreateShaderProgramv, type, count, strings)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindProgramPipeline, pipeline)
|
DECLARE_GL_FUNCTION_HEAD(void, BindProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindProgramPipeline, pipeline)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteProgramPipelines, GLsizei n, const GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteProgramPipelines, n, pipelines)
|
DECLARE_GL_FUNCTION_HEAD(void, DeleteProgramPipelines, GLsizei n, const GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteProgramPipelines, n, pipelines)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenProgramPipelines, n, pipelines)
|
DECLARE_GL_FUNCTION_HEAD(void, GenProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenProgramPipelines, n, pipelines)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsProgramPipeline, pipeline)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END(GLboolean, IsProgramPipeline, pipeline)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramPipelineiv, GLuint pipeline, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramPipelineiv, pipeline, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetProgramPipelineiv, GLuint pipeline, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramPipelineiv, pipeline, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1i, GLuint program, GLint location, GLint v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1i, program, location, v0)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1i, GLuint program, GLint location, GLint v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1i, program, location, v0)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2i, GLuint program, GLint location, GLint v0, GLint v1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2i, program, location, v0, v1)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2i, GLuint program, GLint location, GLint v0, GLint v1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2i, program, location, v0, v1)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3i, GLuint program, GLint location, GLint v0, GLint v1, GLint v2) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3i, program, location, v0, v1, v2)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3i, GLuint program, GLint location, GLint v0, GLint v1, GLint v2) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3i, program, location, v0, v1, v2)
|
||||||
@@ -349,38 +353,52 @@ DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform4fv, GLuint program, GLint location
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2x3fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2x3fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x3fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2x3fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x2fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3x2fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x2fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x2fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2x4fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2x4fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x4fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2x4fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x2fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x2fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x2fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x2fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x4fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3x4fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x4fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x4fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x3fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x3fv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x3fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x3fv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ValidateProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ValidateProgramPipeline, pipeline)
|
DECLARE_GL_FUNCTION_HEAD(void, ValidateProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ValidateProgramPipeline, pipeline)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramPipelineInfoLog, GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramPipelineInfoLog, pipeline, bufSize, length, infoLog)
|
DECLARE_GL_FUNCTION_HEAD(void, GetProgramPipelineInfoLog, GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramPipelineInfoLog, pipeline, bufSize, length, infoLog)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindImageTexture, GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindImageTexture, unit, texture, level, layered, layer, access, format)
|
DECLARE_GL_FUNCTION_HEAD(void, BindImageTexture, GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindImageTexture, unit, texture, level, layered, layer, access, format)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetBooleani_v, GLenum target, GLuint index, GLboolean* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetBooleani_v, target, index, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetBooleani_v, GLenum target, GLuint index, GLboolean* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBooleani_v, target, index, data)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, MemoryBarrier, GLbitfield barriers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MemoryBarrier, barriers)
|
DECLARE_GL_FUNCTION_HEAD(void, MemoryBarrier, GLbitfield barriers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MemoryBarrier, barriers)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, MemoryBarrierByRegion, GLbitfield barriers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MemoryBarrierByRegion, barriers)
|
DECLARE_GL_FUNCTION_HEAD(void, MemoryBarrierByRegion, GLbitfield barriers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MemoryBarrierByRegion, barriers)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexStorage2DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexStorage2DMultisample, target, samples, internalformat, width, height, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TexStorage2DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage2DMultisample, target, samples, internalformat, width, height, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMultisamplefv, GLenum pname, GLuint index, GLfloat* val) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMultisamplefv, pname, index, val)
|
DECLARE_GL_FUNCTION_HEAD(void, GetMultisamplefv, GLenum pname, GLuint index, GLfloat* val) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetMultisamplefv, pname, index, val)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, SampleMaski, GLuint maskNumber, GLbitfield mask) DECLARE_GL_FUNCTION_END_NO_RETURN(void, SampleMaski, maskNumber, mask)
|
DECLARE_GL_FUNCTION_HEAD(void, SampleMaski, GLuint maskNumber, GLbitfield mask) DECLARE_GL_FUNCTION_END_NO_RETURN(void, SampleMaski, maskNumber, mask)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTexLevelParameteriv, GLenum target, GLint level, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexLevelParameteriv, target, level, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTexLevelParameteriv, GLenum target, GLint level, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexLevelParameteriv, target, level, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTexLevelParameterfv, GLenum target, GLint level, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexLevelParameterfv, target, level, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTexLevelParameterfv, GLenum target, GLint level, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexLevelParameterfv, target, level, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindVertexBuffer, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindVertexBuffer, bindingindex, buffer, offset, stride)
|
DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffer, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffer, bindingindex, buffer, offset, stride)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribFormat, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribFormat, attribindex, size, type, normalized, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribFormat, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribFormat, attribindex, size, type, normalized, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribIFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribIFormat, attribindex, size, type, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribIFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribIFormat, attribindex, size, type, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribBinding, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribBinding, attribindex, bindingindex)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribBinding, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribBinding, attribindex, bindingindex)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexBindingDivisor, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexBindingDivisor, bindingindex, divisor)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexBindingDivisor, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexBindingDivisor, bindingindex, divisor)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BlendBarrier) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BlendBarrier)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BlendBarrier) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BlendBarrier)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyImageSubData, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyImageSubData, srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth)
|
DECLARE_GL_FUNCTION_HEAD(void, CopyImageSubData, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyImageSubData, srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageControl, GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageControl, source, type, severity, count, ids, enabled)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageControl, GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageControl, source, type, severity, count, ids, enabled)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageInsert, GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* buf) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageInsert, source, type, id, severity, length, buf)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageInsert, GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* buf) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageInsert, source, type, id, severity, length, buf)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageCallback, GLDEBUGPROC callback, const void* userParam) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageCallback, callback, userParam)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DebugMessageCallback, GLDEBUGPROC callback, const void* userParam) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DebugMessageCallback, callback, userParam)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, GetDebugMessageLog, GLuint count, GLsizei bufSize, GLenum* sources, GLenum* types, GLuint* ids, GLenum* severities, GLsizei* lengths, GLchar* messageLog) DECLARE_GL_FUNCTION_STUB_END(GLuint, GetDebugMessageLog, count, bufSize, sources, types, ids, severities, lengths, messageLog)
|
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, GetDebugMessageLog, GLuint count, GLsizei bufSize, GLenum* sources, GLenum* types, GLuint* ids, GLenum* severities, GLsizei* lengths, GLchar* messageLog) DECLARE_GL_FUNCTION_STUB_END(GLuint, GetDebugMessageLog, count, bufSize, sources, types, ids, severities, lengths, messageLog)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushDebugGroup, GLenum source, GLuint id, GLsizei length, const GLchar* message) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushDebugGroup, source, id, length, message)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushDebugGroup, GLenum source, GLuint id, GLsizei length, const GLchar* message) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushDebugGroup, source, id, length, message)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PopDebugGroup) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PopDebugGroup)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PopDebugGroup) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PopDebugGroup)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ObjectLabel, GLenum identifier, GLuint name, GLsizei length, const GLchar* label) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ObjectLabel, identifier, name, length, label)
|
MOBILEGL_GL_API void glObjectLabel(GLenum identifier, GLuint name, GLsizei length, const GLchar* label) {
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetObjectLabel, GLenum identifier, GLuint name, GLsizei bufSize, GLsizei* length, GLchar* label) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetObjectLabel, identifier, name, bufSize, length, label)
|
(void)identifier;
|
||||||
|
(void)name;
|
||||||
|
(void)length;
|
||||||
|
(void)label;
|
||||||
|
}
|
||||||
|
MOBILEGL_GL_API void glGetObjectLabel(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei* length, GLchar* label) {
|
||||||
|
(void)identifier;
|
||||||
|
(void)name;
|
||||||
|
if (length) {
|
||||||
|
*length = 0;
|
||||||
|
}
|
||||||
|
if (label && bufSize > 0) {
|
||||||
|
label[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ObjectPtrLabel, const void* ptr, GLsizei length, const GLchar* label) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ObjectPtrLabel, ptr, length, label)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ObjectPtrLabel, const void* ptr, GLsizei length, const GLchar* label) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ObjectPtrLabel, ptr, length, label)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetObjectPtrLabel, const void* ptr, GLsizei bufSize, GLsizei* length, GLchar* label) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetObjectPtrLabel, ptr, bufSize, length, label)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetObjectPtrLabel, const void* ptr, GLsizei bufSize, GLsizei* length, GLchar* label) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetObjectPtrLabel, ptr, bufSize, length, label)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetPointerv, GLenum pname, void** params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetPointerv, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetPointerv, GLenum pname, void** params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetPointerv, pname, params)
|
||||||
@@ -394,20 +412,20 @@ DECLARE_GL_FUNCTION_HEAD(void, BlendFunci, GLuint buf, GLenum src, GLenum dst) D
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, BlendFunciARB, GLuint buf, GLenum src, GLenum dst) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFunci, buf, src, dst)
|
DECLARE_GL_FUNCTION_HEAD(void, BlendFunciARB, GLuint buf, GLenum src, GLenum dst) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFunci, buf, src, dst)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BlendFuncSeparatei, GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFuncSeparatei, buf, srcRGB, dstRGB, srcAlpha, dstAlpha)
|
DECLARE_GL_FUNCTION_HEAD(void, BlendFuncSeparatei, GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFuncSeparatei, buf, srcRGB, dstRGB, srcAlpha, dstAlpha)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BlendFuncSeparateiARB, GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFuncSeparatei, buf, srcRGB, dstRGB, srcAlpha, dstAlpha)
|
DECLARE_GL_FUNCTION_HEAD(void, BlendFuncSeparateiARB, GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFuncSeparatei, buf, srcRGB, dstRGB, srcAlpha, dstAlpha)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorMaski, GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorMaski, index, r, g, b, a)
|
DECLARE_GL_FUNCTION_HEAD(void, ColorMaski, GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ColorMaski, index, r, g, b, a)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsEnabledi, GLenum target, GLuint index) DECLARE_GL_FUNCTION_END(GLboolean, IsEnabledi, target, index)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsEnabledi, GLenum target, GLuint index) DECLARE_GL_FUNCTION_END(GLboolean, IsEnabledi, target, index)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsBaseVertex, GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsBaseVertex, mode, count, type, indices, basevertex)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsBaseVertex, GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsBaseVertex, mode, count, type, indices, basevertex)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawRangeElementsBaseVertex, GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawRangeElementsBaseVertex, mode, start, end, count, type, indices, basevertex)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawRangeElementsBaseVertex, GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawRangeElementsBaseVertex, mode, start, end, count, type, indices, basevertex)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertex, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseVertex, mode, count, type, indices, instancecount, basevertex)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertex, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseVertex, mode, count, type, indices, instancecount, basevertex)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture, GLenum target, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture, target, attachment, texture, level)
|
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture, GLenum target, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture, target, attachment, texture, level)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrimitiveBoundingBox, GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrimitiveBoundingBox, minX, minY, minZ, minW, maxX, maxY, maxZ, maxW)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrimitiveBoundingBox, GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrimitiveBoundingBox, minX, minY, minZ, minW, maxX, maxY, maxZ, maxW)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLenum, GetGraphicsResetStatus) DECLARE_GL_FUNCTION_STUB_END(GLenum, GetGraphicsResetStatus)
|
DECLARE_GL_FUNCTION_HEAD(GLenum, GetGraphicsResetStatus) DECLARE_GL_FUNCTION_END(GLenum, GetGraphicsResetStatus)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ReadnPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ReadnPixels, x, y, width, height, format, type, bufSize, data)
|
DECLARE_GL_FUNCTION_HEAD(void, ReadnPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ReadnPixels, x, y, width, height, format, type, bufSize, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformfv, GLuint program, GLint location, GLsizei bufSize, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformfv, program, location, bufSize, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformfv, GLuint program, GLint location, GLsizei bufSize, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformfv, program, location, bufSize, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformiv, GLuint program, GLint location, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformiv, program, location, bufSize, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformiv, GLuint program, GLint location, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformiv, program, location, bufSize, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformuiv, GLuint program, GLint location, GLsizei bufSize, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformuiv, program, location, bufSize, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformuiv, GLuint program, GLint location, GLsizei bufSize, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformuiv, program, location, bufSize, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MinSampleShading, GLfloat value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MinSampleShading, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MinSampleShading, GLfloat value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MinSampleShading, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameteri, pname, value)
|
DECLARE_GL_FUNCTION_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PatchParameteri, pname, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIiv, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIiv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIiv, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIiv, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIuiv, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIuiv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIuiv, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIuiv, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTexParameterIiv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexParameterIiv, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTexParameterIiv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexParameterIiv, target, pname, params)
|
||||||
@@ -417,8 +435,8 @@ DECLARE_GL_FUNCTION_HEAD(void, SamplerParameterIuiv, GLuint sampler, GLenum pnam
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIiv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIiv, sampler, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIiv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIiv, sampler, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIuiv, GLuint sampler, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIuiv, sampler, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIuiv, GLuint sampler, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIuiv, sampler, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexBuffer, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexBuffer, target, internalformat, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, TexBuffer, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexBuffer, target, internalformat, buffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexBufferRange, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexBufferRange, target, internalformat, buffer, offset, size)
|
DECLARE_GL_FUNCTION_HEAD(void, TexBufferRange, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexBufferRange, target, internalformat, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexStorage3DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexStorage3DMultisample, target, samples, internalformat, width, height, depth, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TexStorage3DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage3DMultisample, target, samples, internalformat, width, height, depth, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void*, MapBufferRange, GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) DECLARE_GL_FUNCTION_END(void*, MapBufferRange, target, offset, length, access)
|
DECLARE_GL_FUNCTION_HEAD(void*, MapBufferRange, GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) DECLARE_GL_FUNCTION_END(void*, MapBufferRange, target, offset, length, access)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearIndex, GLfloat c) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearIndex, c)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearIndex, GLfloat c) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearIndex, c)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, IndexMask, GLuint mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, IndexMask, mask)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, IndexMask, GLuint mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, IndexMask, mask)
|
||||||
@@ -436,7 +454,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetClipPlane, GLenum plane, GLdouble* equati
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawBuffer, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawBuffer, mode)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawBuffer, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawBuffer, mode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EnableClientState, GLenum cap) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EnableClientState, cap)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, EnableClientState, GLenum cap) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EnableClientState, cap)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DisableClientState, GLenum cap) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DisableClientState, cap)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DisableClientState, GLenum cap) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DisableClientState, cap)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetDoublev, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetDoublev, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetDoublev, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetDoublev, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushAttrib, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushAttrib, mask)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushAttrib, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushAttrib, mask)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PopAttrib) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PopAttrib)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PopAttrib) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PopAttrib)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushClientAttrib, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushClientAttrib, mask)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushClientAttrib, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushClientAttrib, mask)
|
||||||
@@ -638,7 +656,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMaterialfv, GLenum face, GLenum pname, GL
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMaterialiv, GLenum face, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMaterialiv, face, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMaterialiv, GLenum face, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMaterialiv, face, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorMaterial, GLenum face, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorMaterial, face, mode)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorMaterial, GLenum face, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorMaterial, face, mode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelZoom, GLfloat xfactor, GLfloat yfactor) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelZoom, xfactor, yfactor)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelZoom, GLfloat xfactor, GLfloat yfactor) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelZoom, xfactor, yfactor)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelStoref, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelStoref, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, PixelStoref, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PixelStoref, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelTransferf, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelTransferf, pname, param)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelTransferf, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelTransferf, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelTransferi, GLenum pname, GLint param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelTransferi, pname, param)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelTransferi, GLenum pname, GLint param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelTransferi, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelMapfv, GLenum map, GLsizei mapsize, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelMapfv, map, mapsize, values)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelMapfv, GLenum map, GLsizei mapsize, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelMapfv, map, mapsize, values)
|
||||||
@@ -709,22 +727,22 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, PopName) DECLARE_GL_FUNCTION_STUB_END_NO_RET
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, ClampColor, GLenum target, GLenum clamp) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClampColor, target, clamp)
|
DECLARE_GL_FUNCTION_HEAD(void, ClampColor, GLenum target, GLenum clamp) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClampColor, target, clamp)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginConditionalRender, GLuint id, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginConditionalRender, id, mode)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginConditionalRender, GLuint id, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginConditionalRender, id, mode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndConditionalRender, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndConditionalRender)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndConditionalRender, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndConditionalRender)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI1i, GLuint index, GLint x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI1i, index, x)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1i, GLuint index, GLint x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1i, index, x)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI2i, GLuint index, GLint x, GLint y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI2i, index, x, y)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2i, GLuint index, GLint x, GLint y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2i, index, x, y)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI3i, GLuint index, GLint x, GLint y, GLint z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI3i, index, x, y, z)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3i, GLuint index, GLint x, GLint y, GLint z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3i, index, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI1ui, GLuint index, GLuint x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI1ui, index, x)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1ui, GLuint index, GLuint x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1ui, index, x)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI2ui, GLuint index, GLuint x, GLuint y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI2ui, index, x, y)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2ui, GLuint index, GLuint x, GLuint y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2ui, index, x, y)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI3ui, GLuint index, GLuint x, GLuint y, GLuint z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI3ui, index, x, y, z)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3ui, GLuint index, GLuint x, GLuint y, GLuint z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3ui, index, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI1iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI1iv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1iv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI2iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI2iv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2iv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI3iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI3iv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3iv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI1uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI1uiv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1uiv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI2uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI2uiv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2uiv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI3uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI3uiv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3uiv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4bv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4bv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4bv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4bv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4sv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4sv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4ubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4ubv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4ubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4ubv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4usv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4usv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4usv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4usv, index, v)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindFragDataLocation, GLuint program, GLuint color, const GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindFragDataLocation, program, color, name)
|
DECLARE_GL_FUNCTION_HEAD(void, BindFragDataLocation, GLuint program, GLuint color, const GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindFragDataLocation, program, color, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture1D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture1D, target, attachment, textarget, texture, level)
|
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture1D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture1D, target, attachment, textarget, texture, level)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture3D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture3D, target, attachment, textarget, texture, level, zoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture3D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture3D, target, attachment, textarget, texture, level, zoffset)
|
||||||
@@ -768,12 +786,12 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadTransposeMatrixf, const GLfloat* m) DECL
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadTransposeMatrixd, const GLdouble* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, LoadTransposeMatrixd, m)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadTransposeMatrixd, const GLdouble* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, LoadTransposeMatrixd, m)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixf, const GLfloat* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultTransposeMatrixf, m)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixf, const GLfloat* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultTransposeMatrixf, m)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixd, const GLdouble* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultTransposeMatrixd, m)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixd, const GLdouble* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultTransposeMatrixd, m)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiDrawArrays, GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiDrawArrays, mode, first, count, drawcount)
|
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawArrays, GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawArrays, mode, first, count, drawcount)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElements, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElements, mode, count, type, indices, drawcount)
|
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElements, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElements, mode, count, type, indices, drawcount)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, PointParameterf, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterf, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, PointParameterf, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterf, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfv, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfv, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, PointParameterfv, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterfv, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, PointParameteri, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameteri, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, PointParameteri, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameteri, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameteriv, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameteriv, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, PointParameteriv, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameteriv, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordf, GLfloat coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordf, coord)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordf, GLfloat coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordf, coord)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordfv, const GLfloat* coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordfv, coord)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordfv, const GLfloat* coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordfv, coord)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordd, GLdouble coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordd, coord)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordd, GLdouble coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordd, coord)
|
||||||
@@ -812,56 +830,56 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3i, GLint x, GLint y, GLint z) DECL
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3iv, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3iv, v)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3iv, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3iv, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3s, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3s, x, y, z)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3s, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3s, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3sv, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3sv, v)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3sv, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3sv, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryObjectiv, GLuint id, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryObjectiv, id, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjectiv, GLuint id, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjectiv, id, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetBufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetBufferSubData, target, offset, size, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetBufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBufferSubData, target, offset, size, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexAttribdv, GLuint index, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexAttribdv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribdv, GLuint index, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribdv, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1d, index, x)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1d, index, x)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1dv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1dv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1s, GLuint index, GLshort x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1s, index, x)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1s, GLuint index, GLshort x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1s, index, x)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1sv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1sv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2d, index, x, y)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2d, index, x, y)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2dv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2dv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2s, GLuint index, GLshort x, GLshort y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2s, index, x, y)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2s, GLuint index, GLshort x, GLshort y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2s, index, x, y)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2sv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2sv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3d, index, x, y, z)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3d, index, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3dv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3dv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3s, GLuint index, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3s, index, x, y, z)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3s, GLuint index, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3s, index, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3sv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3sv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nbv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nbv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nbv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nbv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Niv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Niv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Niv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Niv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nsv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nsv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nsv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nsv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nub, GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nub, index, x, y, z, w)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nub, GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nub, index, x, y, z, w)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nubv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nubv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nuiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nuiv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nuiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nuiv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nusv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nusv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nusv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nusv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4bv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4bv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4bv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4bv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4d, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4d, index, x, y, z, w)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4d, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4d, index, x, y, z, w)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4dv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4dv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4iv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4iv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4s, GLuint index, GLshort x, GLshort y, GLshort z, GLshort w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4s, index, x, y, z, w)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4s, GLuint index, GLshort x, GLshort y, GLshort z, GLshort w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4s, index, x, y, z, w)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4sv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4sv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4ubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4ubv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4ubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4ubv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4uiv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4uiv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4usv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4usv, index, v)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4usv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4usv, index, v)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrimitiveRestartIndex, GLuint index) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrimitiveRestartIndex, index)
|
DECLARE_GL_FUNCTION_HEAD(void, PrimitiveRestartIndex, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PrimitiveRestartIndex, index)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveUniformName, GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformName) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveUniformName, program, uniformIndex, bufSize, length, uniformName)
|
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniformName, GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformName) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniformName, program, uniformIndex, bufSize, length, uniformName)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsBaseVertex, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount, const GLint* basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElementsBaseVertex, mode, count, type, indices, drawcount, basevertex)
|
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsBaseVertex, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount, const GLint* basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElementsBaseVertex, mode, count, type, indices, drawcount, basevertex)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ProvokingVertex, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProvokingVertex, mode)
|
DECLARE_GL_FUNCTION_HEAD(void, ProvokingVertex, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProvokingVertex, mode)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexImage2DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexImage2DMultisample, target, samples, internalformat, width, height, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TexImage2DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexImage2DMultisample, target, samples, internalformat, width, height, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TexImage3DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexImage3DMultisample, target, samples, internalformat, width, height, depth, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TexImage3DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexImage3DMultisample, target, samples, internalformat, width, height, depth, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindFragDataLocationIndexed, GLuint program, GLuint colorNumber, GLuint index, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindFragDataLocationIndexed, program, colorNumber, index, name)
|
DECLARE_GL_FUNCTION_HEAD(void, BindFragDataLocationIndexed, GLuint program, GLuint colorNumber, GLuint index, const GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindFragDataLocationIndexed, program, colorNumber, index, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLint, GetFragDataIndex, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLint, GetFragDataIndex, program, name)
|
DECLARE_GL_FUNCTION_HEAD(GLint, GetFragDataIndex, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetFragDataIndex, program, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, QueryCounter, GLuint id, GLenum target) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, QueryCounter, id, target)
|
DECLARE_GL_FUNCTION_HEAD(void, QueryCounter, GLuint id, GLenum target) DECLARE_GL_FUNCTION_END_NO_RETURN(void, QueryCounter, id, target)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryObjecti64v, GLuint id, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryObjecti64v, id, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjecti64v, GLuint id, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjecti64v, id, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryObjectui64v, GLuint id, GLenum pname, GLuint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryObjectui64v, id, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjectui64v, GLuint id, GLenum pname, GLuint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjectui64v, id, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP1ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP1ui, index, type, normalized, value)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP1ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP1ui, index, type, normalized, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP1uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP1uiv, index, type, normalized, value)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP1uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP1uiv, index, type, normalized, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP2ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP2ui, index, type, normalized, value)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP2ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP2ui, index, type, normalized, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP2uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP2uiv, index, type, normalized, value)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP2uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP2uiv, index, type, normalized, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP3ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP3ui, index, type, normalized, value)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP3ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP3ui, index, type, normalized, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP3uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP3uiv, index, type, normalized, value)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP3uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP3uiv, index, type, normalized, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP4ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP4ui, index, type, normalized, value)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP4ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP4ui, index, type, normalized, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP4uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP4uiv, index, type, normalized, value)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP4uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP4uiv, index, type, normalized, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP2ui, GLenum type, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP2ui, type, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP2ui, GLenum type, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP2ui, type, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP2uiv, GLenum type, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP2uiv, type, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP2uiv, GLenum type, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP2uiv, type, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP3ui, GLenum type, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP3ui, type, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP3ui, GLenum type, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP3ui, type, value)
|
||||||
@@ -892,24 +910,24 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorP4ui, GLenum type, GLuint color) DECLAR
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorP4uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorP4uiv, type, color)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorP4uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorP4uiv, type, color)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3ui, GLenum type, GLuint color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3ui, type, color)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3ui, GLenum type, GLuint color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3ui, type, color)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3uiv, type, color)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3uiv, type, color)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform1d, GLint location, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform1d, location, x)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform1d, GLint location, GLdouble x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1d, location, x)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform2d, GLint location, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform2d, location, x, y)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform2d, GLint location, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform2d, location, x, y)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform3d, GLint location, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform3d, location, x, y, z)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform3d, GLint location, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform3d, location, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform4d, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform4d, location, x, y, z, w)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform4d, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform4d, location, x, y, z, w)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform1dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform1dv, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform1dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1dv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform2dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform2dv, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform2dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform2dv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform3dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform3dv, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform3dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform3dv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform4dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform4dv, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, Uniform4dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform4dv, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2x3dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2x3dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2x4dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2x4dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3x2dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x2dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3x4dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x4dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4x2dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x2dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4x3dv, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x3dv, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformdv, GLuint program, GLint location, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformdv, program, location, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetUniformdv, GLuint program, GLint location, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformdv, program, location, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLint, GetSubroutineUniformLocation, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLint, GetSubroutineUniformLocation, program, shadertype, name)
|
DECLARE_GL_FUNCTION_STUB_HEAD(GLint, GetSubroutineUniformLocation, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLint, GetSubroutineUniformLocation, program, shadertype, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, GetSubroutineIndex, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLuint, GetSubroutineIndex, program, shadertype, name)
|
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, GetSubroutineIndex, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLuint, GetSubroutineIndex, program, shadertype, name)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveSubroutineUniformiv, GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveSubroutineUniformiv, program, shadertype, index, pname, values)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveSubroutineUniformiv, GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveSubroutineUniformiv, program, shadertype, index, pname, values)
|
||||||
@@ -919,28 +937,28 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformSubroutinesuiv, GLenum shadertype, GL
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformSubroutineuiv, GLenum shadertype, GLint location, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformSubroutineuiv, shadertype, location, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformSubroutineuiv, GLenum shadertype, GLint location, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformSubroutineuiv, shadertype, location, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramStageiv, GLuint program, GLenum shadertype, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramStageiv, program, shadertype, pname, values)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramStageiv, GLuint program, GLenum shadertype, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramStageiv, program, shadertype, pname, values)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameterfv, GLenum pname, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameterfv, pname, values)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameterfv, GLenum pname, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameterfv, pname, values)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedback, mode, id)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedback, mode, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStream, mode, id, stream)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStream, mode, id, stream)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginQueryIndexed, target, index, id)
|
DECLARE_GL_FUNCTION_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginQueryIndexed, target, index, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndQueryIndexed, target, index)
|
DECLARE_GL_FUNCTION_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndQueryIndexed, target, index)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryIndexediv, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryIndexediv, target, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryIndexediv, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryIndexediv, target, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform1d, GLuint program, GLint location, GLdouble v0) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform1d, program, location, v0)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1d, GLuint program, GLint location, GLdouble v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1d, program, location, v0)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform1dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform1dv, program, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1dv, program, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform2d, GLuint program, GLint location, GLdouble v0, GLdouble v1) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform2d, program, location, v0, v1)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2d, GLuint program, GLint location, GLdouble v0, GLdouble v1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2d, program, location, v0, v1)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform2dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform2dv, program, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2dv, program, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform3d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform3d, program, location, v0, v1, v2)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3d, program, location, v0, v1, v2)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform3dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform3dv, program, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3dv, program, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform4d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform4d, program, location, v0, v1, v2, v3)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform4d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform4d, program, location, v0, v1, v2, v3)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform4dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform4dv, program, location, count, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform4dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform4dv, program, location, count, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2x3dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2x3dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3x2dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x2dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2x4dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2x4dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x2dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x2dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3x4dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x4dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x3dv, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x3dv, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL1d, index, x)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL1d, index, x)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL2d, index, x, y)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL2d, index, x, y)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL3d, index, x, y, z)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL3d, index, x, y, z)
|
||||||
@@ -965,8 +983,8 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawArraysInstancedBaseInstance, GLenum mode, GLi
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseInstance, mode, count, type, indices, instancecount, baseinstance)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseInstance, mode, count, type, indices, instancecount, baseinstance)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseVertexBaseInstance, mode, count, type, indices, instancecount, basevertex, baseinstance)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseVertexBaseInstance, mode, count, type, indices, instancecount, basevertex, baseinstance)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params)
|
||||||
@@ -979,23 +997,23 @@ DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsIndirect, GLenum mode, GLenum ty
|
|||||||
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocationIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocationIndex, program, programInterface, name)
|
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocationIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocationIndex, program, programInterface, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShaderStorageBlockBinding, program, storageBlockIndex, storageBlockBinding)
|
DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShaderStorageBlockBinding, program, storageBlockIndex, storageBlockBinding)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BufferStorage, GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BufferStorage, target, size, data, flags)
|
DECLARE_GL_FUNCTION_HEAD(void, BufferStorage, GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BufferStorage, target, size, data, flags)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearTexImage, texture, level, format, type, data)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexImage, texture, level, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindBuffersBase, GLenum target, GLuint first, GLsizei count, const GLuint* buffers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindBuffersBase, target, first, count, buffers)
|
DECLARE_GL_FUNCTION_HEAD(void, BindBuffersBase, GLenum target, GLuint first, GLsizei count, const GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBuffersBase, target, first, count, buffers)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindBuffersRange, GLenum target, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizeiptr* sizes) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindBuffersRange, target, first, count, buffers, offsets, sizes)
|
DECLARE_GL_FUNCTION_HEAD(void, BindBuffersRange, GLenum target, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizeiptr* sizes) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBuffersRange, target, first, count, buffers, offsets, sizes)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTextures, first, count, textures)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTextures, first, count, textures)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindSamplers, GLuint first, GLsizei count, const GLuint* samplers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindSamplers, first, count, samplers)
|
DECLARE_GL_FUNCTION_HEAD(void, BindSamplers, GLuint first, GLsizei count, const GLuint* samplers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindSamplers, first, count, samplers)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides)
|
DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
|
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage)
|
||||||
@@ -1008,83 +1026,83 @@ DECLARE_GL_FUNCTION_HEAD(void, FlushMappedNamedBufferRange, GLuint buffer, GLint
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferParameteriv, GLuint buffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferParameteriv, buffer, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferParameteriv, GLuint buffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferParameteriv, buffer, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferParameteri64v, GLuint buffer, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferParameteri64v, buffer, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferParameteri64v, GLuint buffer, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferParameteri64v, buffer, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferPointerv, GLuint buffer, GLenum pname, void** params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferPointerv, buffer, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferPointerv, GLuint buffer, GLenum pname, void** params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferPointerv, buffer, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedBufferSubData, GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedBufferSubData, buffer, offset, size, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferSubData, GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedBufferSubData, buffer, offset, size, data)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateFramebuffers, GLsizei n, GLuint* framebuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateFramebuffers, n, framebuffers)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateFramebuffers, GLsizei n, GLuint* framebuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateFramebuffers, n, framebuffers)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferRenderbuffer, GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferRenderbuffer, framebuffer, attachment, renderbuffertarget, renderbuffer)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferRenderbuffer, GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferRenderbuffer, framebuffer, attachment, renderbuffertarget, renderbuffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferParameteri, GLuint framebuffer, GLenum pname, GLint param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferParameteri, framebuffer, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferParameteri, GLuint framebuffer, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferParameteri, framebuffer, pname, param)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTexture, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTexture, framebuffer, attachment, texture, level)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTexture, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTexture, framebuffer, attachment, texture, level)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferTextureLayer, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferTextureLayer, framebuffer, attachment, texture, level, layer)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTextureLayer, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTextureLayer, framebuffer, attachment, texture, level, layer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferDrawBuffer, GLuint framebuffer, GLenum buf) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferDrawBuffer, framebuffer, buf)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferDrawBuffer, GLuint framebuffer, GLenum buf) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferDrawBuffer, framebuffer, buf)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferDrawBuffers, GLuint framebuffer, GLsizei n, const GLenum* bufs) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferDrawBuffers, framebuffer, n, bufs)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferDrawBuffers, GLuint framebuffer, GLsizei n, const GLenum* bufs) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferDrawBuffers, framebuffer, n, bufs)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferReadBuffer, GLuint framebuffer, GLenum src) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferReadBuffer, framebuffer, src)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferReadBuffer, GLuint framebuffer, GLenum src) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferReadBuffer, framebuffer, src)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateNamedFramebufferData, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateNamedFramebufferData, framebuffer, numAttachments, attachments)
|
DECLARE_GL_FUNCTION_HEAD(void, InvalidateNamedFramebufferData, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_END_NO_RETURN(void, InvalidateNamedFramebufferData, framebuffer, numAttachments, attachments)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateNamedFramebufferSubData, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateNamedFramebufferSubData, framebuffer, numAttachments, attachments, x, y, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, InvalidateNamedFramebufferSubData, GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, InvalidateNamedFramebufferSubData, framebuffer, numAttachments, attachments, x, y, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearNamedFramebufferiv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearNamedFramebufferiv, framebuffer, buffer, drawbuffer, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferiv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferiv, framebuffer, buffer, drawbuffer, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearNamedFramebufferuiv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearNamedFramebufferuiv, framebuffer, buffer, drawbuffer, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferuiv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferuiv, framebuffer, buffer, drawbuffer, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferfv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferfv, framebuffer, buffer, drawbuffer, value)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferfv, GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferfv, framebuffer, buffer, drawbuffer, value)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferfi, GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferfi, framebuffer, buffer, drawbuffer, depth, stencil)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearNamedFramebufferfi, GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedFramebufferfi, framebuffer, buffer, drawbuffer, depth, stencil)
|
||||||
DECLARE_GL_FUNCTION_HEAD(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) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlitNamedFramebuffer, readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter)
|
DECLARE_GL_FUNCTION_HEAD(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) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlitNamedFramebuffer, readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLenum, CheckNamedFramebufferStatus, GLuint framebuffer, GLenum target) DECLARE_GL_FUNCTION_END(GLenum, CheckNamedFramebufferStatus, framebuffer, target)
|
DECLARE_GL_FUNCTION_HEAD(GLenum, CheckNamedFramebufferStatus, GLuint framebuffer, GLenum target) DECLARE_GL_FUNCTION_END(GLenum, CheckNamedFramebufferStatus, framebuffer, target)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedFramebufferParameteriv, GLuint framebuffer, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedFramebufferParameteriv, framebuffer, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedFramebufferParameteriv, GLuint framebuffer, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedFramebufferParameteriv, framebuffer, pname, param)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedFramebufferAttachmentParameteriv, GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedFramebufferAttachmentParameteriv, framebuffer, attachment, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedFramebufferAttachmentParameteriv, GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedFramebufferAttachmentParameteriv, framebuffer, attachment, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateRenderbuffers, GLsizei n, GLuint* renderbuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateRenderbuffers, n, renderbuffers)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateRenderbuffers, GLsizei n, GLuint* renderbuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateRenderbuffers, n, renderbuffers)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorage, GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorage, renderbuffer, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorage, GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorage, renderbuffer, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedRenderbufferStorageMultisample, GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedRenderbufferStorageMultisample, renderbuffer, samples, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorageMultisample, GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorageMultisample, renderbuffer, samples, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedRenderbufferParameteriv, GLuint renderbuffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedRenderbufferParameteriv, renderbuffer, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedRenderbufferParameteriv, GLuint renderbuffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedRenderbufferParameteriv, renderbuffer, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateTextures, GLenum target, GLsizei n, GLuint* textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTextures, target, n, textures)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateTextures, GLenum target, GLsizei n, GLuint* textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTextures, target, n, textures)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBuffer, GLuint texture, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBuffer, texture, internalformat, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureBuffer, GLuint texture, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureBuffer, texture, internalformat, buffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBufferRange, GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBufferRange, texture, internalformat, buffer, offset, size)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureBufferRange, GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureBufferRange, texture, internalformat, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage1D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage1D, texture, levels, internalformat, width)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage1D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage1D, texture, levels, internalformat, width)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage2D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage2D, texture, levels, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage2D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage2D, texture, levels, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage3D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage3D, texture, levels, internalformat, width, height, depth)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage3D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage3D, texture, levels, internalformat, width, height, depth)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage2DMultisample, GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage2DMultisample, texture, samples, internalformat, width, height, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage2DMultisample, GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage2DMultisample, texture, samples, internalformat, width, height, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage3DMultisample, GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage3DMultisample, texture, samples, internalformat, width, height, depth, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage3DMultisample, GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage3DMultisample, texture, samples, internalformat, width, height, depth, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureSubImage1D, texture, level, xoffset, width, format, type, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage1D, texture, level, xoffset, width, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, type, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage1D, texture, level, xoffset, width, format, imageSize, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage1D, texture, level, xoffset, width, format, imageSize, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, imageSize, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, imageSize, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage1D, texture, level, xoffset, x, y, width)
|
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage1D, texture, level, xoffset, x, y, width)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage2D, texture, level, xoffset, yoffset, x, y, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage2D, texture, level, xoffset, yoffset, x, y, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, x, y, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, x, y, width, height)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterf, GLuint texture, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterf, texture, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterf, GLuint texture, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterf, texture, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameterfv, GLuint texture, GLenum pname, const GLfloat* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameterfv, texture, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterfv, GLuint texture, GLenum pname, const GLfloat* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterfv, texture, pname, param)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureParameteri, GLuint texture, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameteri, texture, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameteri, GLuint texture, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameteri, texture, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameterIiv, GLuint texture, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameterIiv, texture, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterIiv, GLuint texture, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterIiv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameterIuiv, GLuint texture, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameterIuiv, texture, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterIuiv, GLuint texture, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterIuiv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, TextureParameteriv, GLuint texture, GLenum pname, const GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameteriv, texture, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameteriv, GLuint texture, GLenum pname, const GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameteriv, texture, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenerateTextureMipmap, GLuint texture) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenerateTextureMipmap, texture)
|
DECLARE_GL_FUNCTION_HEAD(void, GenerateTextureMipmap, GLuint texture) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenerateTextureMipmap, texture)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindTextureUnit, GLuint unit, GLuint texture) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindTextureUnit, unit, texture)
|
DECLARE_GL_FUNCTION_HEAD(void, BindTextureUnit, GLuint unit, GLuint texture) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindTextureUnit, unit, texture)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureImage, GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureImage, texture, level, format, type, bufSize, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureImage, GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureImage, texture, level, format, type, bufSize, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureImage, GLuint texture, GLint level, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureImage, texture, level, bufSize, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, GetCompressedTextureImage, GLuint texture, GLint level, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetCompressedTextureImage, texture, level, bufSize, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureLevelParameterfv, GLuint texture, GLint level, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureLevelParameterfv, texture, level, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureLevelParameterfv, GLuint texture, GLint level, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureLevelParameterfv, texture, level, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureLevelParameteriv, GLuint texture, GLint level, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureLevelParameteriv, texture, level, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureLevelParameteriv, GLuint texture, GLint level, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureLevelParameteriv, texture, level, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureParameterfv, GLuint texture, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureParameterfv, texture, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterfv, GLuint texture, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameterfv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureParameterIiv, GLuint texture, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureParameterIiv, texture, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterIiv, GLuint texture, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameterIiv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureParameterIuiv, GLuint texture, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureParameterIuiv, texture, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterIuiv, GLuint texture, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameterIuiv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameteriv, GLuint texture, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameteriv, texture, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameteriv, GLuint texture, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameteriv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateVertexArrays, n, arrays)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateVertexArrays, n, arrays)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DisableVertexArrayAttrib, GLuint vaobj, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DisableVertexArrayAttrib, vaobj, index)
|
DECLARE_GL_FUNCTION_HEAD(void, DisableVertexArrayAttrib, GLuint vaobj, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DisableVertexArrayAttrib, vaobj, index)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, EnableVertexArrayAttrib, GLuint vaobj, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EnableVertexArrayAttrib, vaobj, index)
|
DECLARE_GL_FUNCTION_HEAD(void, EnableVertexArrayAttrib, GLuint vaobj, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EnableVertexArrayAttrib, vaobj, index)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayElementBuffer, GLuint vaobj, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayElementBuffer, vaobj, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayElementBuffer, GLuint vaobj, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayElementBuffer, vaobj, buffer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayVertexBuffer, GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayVertexBuffer, vaobj, bindingindex, buffer, offset, stride)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayVertexBuffer, GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayVertexBuffer, vaobj, bindingindex, buffer, offset, stride)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayVertexBuffers, GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayVertexBuffers, vaobj, first, count, buffers, offsets, strides)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayVertexBuffers, GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayVertexBuffers, vaobj, first, count, buffers, offsets, strides)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayAttribBinding, GLuint vaobj, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayAttribBinding, vaobj, attribindex, bindingindex)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribBinding, GLuint vaobj, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribBinding, vaobj, attribindex, bindingindex)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribFormat, vaobj, attribindex, size, type, normalized, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribFormat, vaobj, attribindex, size, type, normalized, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribIFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribIFormat, vaobj, attribindex, size, type, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribIFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribIFormat, vaobj, attribindex, size, type, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayAttribLFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayAttribLFormat, vaobj, attribindex, size, type, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribLFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribLFormat, vaobj, attribindex, size, type, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayBindingDivisor, GLuint vaobj, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayBindingDivisor, vaobj, bindingindex, divisor)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayBindingDivisor, GLuint vaobj, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayBindingDivisor, vaobj, bindingindex, divisor)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexArrayiv, vaobj, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayiv, vaobj, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexArrayIndexediv, vaobj, index, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexediv, vaobj, index, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexArrayIndexed64iv, GLuint vaobj, GLuint index, GLenum pname, GLint64* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexArrayIndexed64iv, vaobj, index, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexed64iv, GLuint vaobj, GLuint index, GLenum pname, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexed64iv, vaobj, index, pname, param)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateSamplers, GLsizei n, GLuint* samplers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateSamplers, n, samplers)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateSamplers, GLsizei n, GLuint* samplers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateSamplers, n, samplers)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateProgramPipelines, n, pipelines)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateProgramPipelines, n, pipelines)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateQueries, GLenum target, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateQueries, target, n, ids)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateQueries, GLenum target, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateQueries, target, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryBufferObjecti64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryBufferObjecti64v, id, buffer, pname, offset)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjecti64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjecti64v, id, buffer, pname, offset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryBufferObjectiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryBufferObjectiv, id, buffer, pname, offset)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectiv, id, buffer, pname, offset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryBufferObjectui64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryBufferObjectui64v, id, buffer, pname, offset)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectui64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectui64v, id, buffer, pname, offset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryBufferObjectuiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryBufferObjectuiv, id, buffer, pname, offset)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectuiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectuiv, id, buffer, pname, offset)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, bufSize, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, bufSize, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, bufSize, pixels)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, bufSize, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnCompressedTexImage, GLenum target, GLint lod, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnCompressedTexImage, target, lod, bufSize, pixels)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnCompressedTexImage, GLenum target, GLint lod, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnCompressedTexImage, target, lod, bufSize, pixels)
|
||||||
@@ -1490,9 +1508,9 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, Vertex3xvOES, const GLfixed* coords) DECLARE
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Vertex4xOES, GLfixed x, GLfixed y, GLfixed z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Vertex4xOES, x, y, z)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Vertex4xOES, GLfixed x, GLfixed y, GLfixed z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Vertex4xOES, x, y, z)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Vertex4xvOES, const GLfixed* coords) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Vertex4xvOES, coords)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, Vertex4xvOES, const GLfixed* coords) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Vertex4xvOES, coords)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLbitfield, QueryMatrixxOES, GLfixed* mantissa, GLint* exponent) DECLARE_GL_FUNCTION_STUB_END(GLbitfield, QueryMatrixxOES, mantissa, exponent)
|
DECLARE_GL_FUNCTION_STUB_HEAD(GLbitfield, QueryMatrixxOES, GLfixed* mantissa, GLint* exponent) DECLARE_GL_FUNCTION_STUB_END(GLbitfield, QueryMatrixxOES, mantissa, exponent)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearDepthfOES, GLclampf depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearDepthfOES, depth)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearDepthfOES, GLclampf depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearDepth, static_cast<GLclampd>(depth))
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipPlanefOES, GLenum plane, const GLfloat* equation) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipPlanefOES, plane, equation)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipPlanefOES, GLenum plane, const GLfloat* equation) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipPlanefOES, plane, equation)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DepthRangefOES, GLclampf n, GLclampf f) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DepthRangefOES, n, f)
|
DECLARE_GL_FUNCTION_HEAD(void, DepthRangefOES, GLclampf n, GLclampf f) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DepthRange, static_cast<GLclampd>(n), static_cast<GLclampd>(f))
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FrustumfOES, GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FrustumfOES, l, r, b, t, n, f)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, FrustumfOES, GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FrustumfOES, l, r, b, t, n, f)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetClipPlanefOES, GLenum plane, GLfloat* equation) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetClipPlanefOES, plane, equation)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetClipPlanefOES, GLenum plane, GLfloat* equation) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetClipPlanefOES, plane, equation)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, OrthofOES, GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, OrthofOES, l, r, b, t, n, f)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, OrthofOES, GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, OrthofOES, l, r, b, t, n, f)
|
||||||
@@ -1760,25 +1778,25 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, MatrixPopEXT, GLenum mode) DECLARE_GL_FUNCTI
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MatrixPushEXT, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MatrixPushEXT, mode)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MatrixPushEXT, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MatrixPushEXT, mode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClientAttribDefaultEXT, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClientAttribDefaultEXT, mask)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClientAttribDefaultEXT, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClientAttribDefaultEXT, mask)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushClientAttribDefaultEXT, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushClientAttribDefaultEXT, mask)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushClientAttribDefaultEXT, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushClientAttribDefaultEXT, mask)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameterfEXT, GLuint texture, GLenum target, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameterfEXT, texture, target, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterfEXT, GLuint texture, GLenum target, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterf, texture, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameterfvEXT, GLuint texture, GLenum target, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameterfvEXT, texture, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterfvEXT, GLuint texture, GLenum target, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterfv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameteriEXT, GLuint texture, GLenum target, GLenum pname, GLint param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameteriEXT, texture, target, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameteriEXT, GLuint texture, GLenum target, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameteri, texture, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameterivEXT, GLuint texture, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameterivEXT, texture, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterivEXT, GLuint texture, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameteriv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureImage1DEXT, GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureImage1DEXT, texture, target, level, internalformat, width, border, format, type, pixels)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureImage1DEXT, GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureImage1DEXT, texture, target, level, internalformat, width, border, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureImage2DEXT, GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureImage2DEXT, texture, target, level, internalformat, width, height, border, format, type, pixels)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureImage2DEXT, GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureImage2DEXT, texture, target, level, internalformat, width, height, border, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureSubImage1DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureSubImage1DEXT, texture, target, level, xoffset, width, format, type, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage1DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage1D, texture, level, xoffset, width, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureSubImage2DEXT, texture, target, level, xoffset, yoffset, width, height, format, type, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureImage1DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureImage1DEXT, texture, target, level, internalformat, x, y, width, border)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureImage1DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureImage1DEXT, texture, target, level, internalformat, x, y, width, border)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureImage2DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureImage2DEXT, texture, target, level, internalformat, x, y, width, height, border)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureImage2DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureImage2DEXT, texture, target, level, internalformat, x, y, width, height, border)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage1DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage1DEXT, texture, target, level, xoffset, x, y, width)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage1DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage1DEXT, texture, target, level, xoffset, x, y, width)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage2DEXT, texture, target, level, xoffset, yoffset, x, y, width, height)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage2DEXT, texture, target, level, xoffset, yoffset, x, y, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureImageEXT, GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureImageEXT, texture, target, level, format, type, pixels)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureImageEXT, GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureImageEXT, texture, target, level, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureParameterfvEXT, GLuint texture, GLenum target, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureParameterfvEXT, texture, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterfvEXT, GLuint texture, GLenum target, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameterfv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureParameterivEXT, GLuint texture, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureParameterivEXT, texture, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterivEXT, GLuint texture, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameteriv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureLevelParameterfvEXT, GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureLevelParameterfvEXT, texture, target, level, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureLevelParameterfvEXT, GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureLevelParameterfv, texture, level, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureLevelParameterivEXT, GLuint texture, GLenum target, GLint level, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureLevelParameterivEXT, texture, target, level, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureLevelParameterivEXT, GLuint texture, GLenum target, GLint level, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureLevelParameteriv, texture, level, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureImage3DEXT, GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureImage3DEXT, texture, target, level, internalformat, width, height, depth, border, format, type, pixels)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureImage3DEXT, GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureImage3DEXT, texture, target, level, internalformat, width, height, depth, border, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureSubImage3DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureSubImage3DEXT, texture, target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage3DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage3DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage3DEXT, texture, target, level, xoffset, yoffset, zoffset, x, y, width, height)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTextureSubImage3DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyTextureSubImage3DEXT, texture, target, level, xoffset, yoffset, zoffset, x, y, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindMultiTextureEXT, GLenum texunit, GLenum target, GLuint texture) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindMultiTextureEXT, texunit, target, texture)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindMultiTextureEXT, GLenum texunit, GLenum target, GLuint texture) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindMultiTextureEXT, texunit, target, texture)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexCoordPointerEXT, GLenum texunit, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexCoordPointerEXT, texunit, size, type, stride, pointer)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexCoordPointerEXT, GLenum texunit, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexCoordPointerEXT, texunit, size, type, stride, pointer)
|
||||||
@@ -1853,10 +1871,10 @@ DECLARE_GL_FUNCTION_HEAD(void, GetNamedBufferPointervEXT, GLuint buffer, GLenum
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedBufferSubDataEXT, GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedBufferSubDataEXT, buffer, offset, size, data)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedBufferSubDataEXT, GLuint buffer, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedBufferSubDataEXT, buffer, offset, size, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBufferEXT, GLuint texture, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBufferEXT, texture, target, internalformat, buffer)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBufferEXT, GLuint texture, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBufferEXT, texture, target, internalformat, buffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexBufferEXT, GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexBufferEXT, texunit, target, internalformat, buffer)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexBufferEXT, GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexBufferEXT, texunit, target, internalformat, buffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameterIivEXT, GLuint texture, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameterIivEXT, texture, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterIivEXT, GLuint texture, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterIiv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureParameterIuivEXT, GLuint texture, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureParameterIuivEXT, texture, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureParameterIuivEXT, GLuint texture, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureParameterIuiv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureParameterIivEXT, GLuint texture, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureParameterIivEXT, texture, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterIivEXT, GLuint texture, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameterIiv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTextureParameterIuivEXT, GLuint texture, GLenum target, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTextureParameterIuivEXT, texture, target, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTextureParameterIuivEXT, GLuint texture, GLenum target, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureParameterIuiv, texture, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexParameterIivEXT, GLenum texunit, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexParameterIivEXT, texunit, target, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexParameterIivEXT, GLenum texunit, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexParameterIivEXT, texunit, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexParameterIuivEXT, GLenum texunit, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexParameterIuivEXT, texunit, target, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexParameterIuivEXT, GLenum texunit, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexParameterIuivEXT, texunit, target, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMultiTexParameterIivEXT, GLenum texunit, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMultiTexParameterIivEXT, texunit, target, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMultiTexParameterIivEXT, GLenum texunit, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMultiTexParameterIivEXT, texunit, target, pname, params)
|
||||||
@@ -1886,22 +1904,22 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedProgramivEXT, GLuint program, GLenum
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedProgramStringEXT, GLuint program, GLenum target, GLenum pname, void* string) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedProgramStringEXT, program, target, pname, string)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedProgramStringEXT, GLuint program, GLenum target, GLenum pname, void* string) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedProgramStringEXT, program, target, pname, string)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorageEXT, GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorage, renderbuffer, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorageEXT, GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorage, renderbuffer, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetNamedRenderbufferParameterivEXT, GLuint renderbuffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedRenderbufferParameteriv, renderbuffer, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetNamedRenderbufferParameterivEXT, GLuint renderbuffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedRenderbufferParameteriv, renderbuffer, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedRenderbufferStorageMultisampleEXT, GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedRenderbufferStorageMultisampleEXT, renderbuffer, samples, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorageMultisampleEXT, GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorageMultisample, renderbuffer, samples, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedRenderbufferStorageMultisampleCoverageEXT, GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedRenderbufferStorageMultisampleCoverageEXT, renderbuffer, coverageSamples, colorSamples, internalformat, width, height)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedRenderbufferStorageMultisampleCoverageEXT, GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedRenderbufferStorageMultisampleCoverageEXT, renderbuffer, coverageSamples, colorSamples, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLenum, CheckNamedFramebufferStatusEXT, GLuint framebuffer, GLenum target) DECLARE_GL_FUNCTION_STUB_END(GLenum, CheckNamedFramebufferStatusEXT, framebuffer, target)
|
DECLARE_GL_FUNCTION_HEAD(GLenum, CheckNamedFramebufferStatusEXT, GLuint framebuffer, GLenum target) DECLARE_GL_FUNCTION_END(GLenum, CheckNamedFramebufferStatus, framebuffer, target)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferTexture1DEXT, GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferTexture1DEXT, framebuffer, attachment, textarget, texture, level)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTexture1DEXT, GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTexture1D, framebuffer, attachment, textarget, texture, level)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferTexture2DEXT, GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferTexture2DEXT, framebuffer, attachment, textarget, texture, level)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTexture2DEXT, GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTexture2D, framebuffer, attachment, textarget, texture, level)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferTexture3DEXT, GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferTexture3DEXT, framebuffer, attachment, textarget, texture, level, zoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTexture3DEXT, GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTexture3D, framebuffer, attachment, textarget, texture, level, zoffset)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferRenderbufferEXT, GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferRenderbuffer, framebuffer, attachment, renderbuffertarget, renderbuffer)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferRenderbufferEXT, GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferRenderbuffer, framebuffer, attachment, renderbuffertarget, renderbuffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedFramebufferAttachmentParameterivEXT, GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedFramebufferAttachmentParameterivEXT, framebuffer, attachment, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedFramebufferAttachmentParameterivEXT, GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedFramebufferAttachmentParameterivEXT, framebuffer, attachment, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenerateTextureMipmapEXT, GLuint texture, GLenum target) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenerateTextureMipmapEXT, texture, target)
|
DECLARE_GL_FUNCTION_HEAD(void, GenerateTextureMipmapEXT, GLuint texture, GLenum target) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenerateTextureMipmap, texture)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenerateMultiTexMipmapEXT, GLenum texunit, GLenum target) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenerateMultiTexMipmapEXT, texunit, target)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenerateMultiTexMipmapEXT, GLenum texunit, GLenum target) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenerateMultiTexMipmapEXT, texunit, target)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferDrawBufferEXT, GLuint framebuffer, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferDrawBufferEXT, framebuffer, mode)
|
DECLARE_GL_FUNCTION_HEAD(void, FramebufferDrawBufferEXT, GLuint framebuffer, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferDrawBuffer, framebuffer, mode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferDrawBuffersEXT, GLuint framebuffer, GLsizei n, const GLenum* bufs) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferDrawBuffersEXT, framebuffer, n, bufs)
|
DECLARE_GL_FUNCTION_HEAD(void, FramebufferDrawBuffersEXT, GLuint framebuffer, GLsizei n, const GLenum* bufs) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferDrawBuffers, framebuffer, n, bufs)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferReadBufferEXT, GLuint framebuffer, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferReadBufferEXT, framebuffer, mode)
|
DECLARE_GL_FUNCTION_HEAD(void, FramebufferReadBufferEXT, GLuint framebuffer, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferReadBuffer, framebuffer, mode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedCopyBufferSubDataEXT, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedCopyBufferSubDataEXT, readBuffer, writeBuffer, readOffset, writeOffset, size)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedCopyBufferSubDataEXT, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedCopyBufferSubDataEXT, readBuffer, writeBuffer, readOffset, writeOffset, size)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferTextureEXT, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferTextureEXT, framebuffer, attachment, texture, level)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTextureEXT, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTexture, framebuffer, attachment, texture, level)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferTextureLayerEXT, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferTextureLayerEXT, framebuffer, attachment, texture, level, layer)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedFramebufferTextureLayerEXT, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedFramebufferTextureLayer, framebuffer, attachment, texture, level, layer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferTextureFaceEXT, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferTextureFaceEXT, framebuffer, attachment, texture, level, face)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferTextureFaceEXT, GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferTextureFaceEXT, framebuffer, attachment, texture, level, face)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureRenderbufferEXT, GLuint texture, GLenum target, GLuint renderbuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureRenderbufferEXT, texture, target, renderbuffer)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureRenderbufferEXT, GLuint texture, GLenum target, GLuint renderbuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureRenderbufferEXT, texture, target, renderbuffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexRenderbufferEXT, GLenum texunit, GLenum target, GLuint renderbuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexRenderbufferEXT, texunit, target, renderbuffer)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexRenderbufferEXT, GLenum texunit, GLenum target, GLuint renderbuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexRenderbufferEXT, texunit, target, renderbuffer)
|
||||||
@@ -1948,11 +1966,11 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x4dvEXT, GLuint program
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x2dvEXT, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x2dvEXT, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x2dvEXT, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x2dvEXT, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x3dvEXT, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x3dvEXT, program, location, count, transpose, value)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x3dvEXT, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x3dvEXT, program, location, count, transpose, value)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBufferRangeEXT, GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBufferRangeEXT, texture, target, internalformat, buffer, offset, size)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBufferRangeEXT, GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBufferRangeEXT, texture, target, internalformat, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage1DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage1DEXT, texture, target, levels, internalformat, width)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage1DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage1D, texture, levels, internalformat, width)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage2DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage2DEXT, texture, target, levels, internalformat, width, height)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage2DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage2D, texture, levels, internalformat, width, height)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage3DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage3DEXT, texture, target, levels, internalformat, width, height, depth)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage3DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage3D, texture, levels, internalformat, width, height, depth)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage2DMultisampleEXT, GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage2DMultisampleEXT, texture, target, samples, internalformat, width, height, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage2DMultisampleEXT, GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage2DMultisample, texture, samples, internalformat, width, height, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureStorage3DMultisampleEXT, GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureStorage3DMultisampleEXT, texture, target, samples, internalformat, width, height, depth, fixedsamplelocations)
|
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage3DMultisampleEXT, GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage3DMultisample, texture, samples, internalformat, width, height, depth, fixedsamplelocations)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayBindVertexBufferEXT, GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayBindVertexBufferEXT, vaobj, bindingindex, buffer, offset, stride)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayBindVertexBufferEXT, GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayBindVertexBufferEXT, vaobj, bindingindex, buffer, offset, stride)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayVertexAttribFormatEXT, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayVertexAttribFormatEXT, vaobj, attribindex, size, type, normalized, relativeoffset)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayVertexAttribFormatEXT, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayVertexAttribFormatEXT, vaobj, attribindex, size, type, normalized, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayVertexAttribIFormatEXT, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayVertexAttribIFormatEXT, vaobj, attribindex, size, type, relativeoffset)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayVertexAttribIFormatEXT, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayVertexAttribIFormatEXT, vaobj, attribindex, size, type, relativeoffset)
|
||||||
@@ -2086,9 +2104,9 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearColorIuiEXT, GLuint red, GLuint green,
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, AreTexturesResidentEXT, GLsizei n, const GLuint* textures, GLboolean* residences) DECLARE_GL_FUNCTION_STUB_END(GLboolean, AreTexturesResidentEXT, n, textures, residences)
|
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, AreTexturesResidentEXT, GLsizei n, const GLuint* textures, GLboolean* residences) DECLARE_GL_FUNCTION_STUB_END(GLboolean, AreTexturesResidentEXT, n, textures, residences)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrioritizeTexturesEXT, GLsizei n, const GLuint* textures, const GLclampf* priorities) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrioritizeTexturesEXT, n, textures, priorities)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrioritizeTexturesEXT, GLsizei n, const GLuint* textures, const GLclampf* priorities) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrioritizeTexturesEXT, n, textures, priorities)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureNormalEXT, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureNormalEXT, mode)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureNormalEXT, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureNormalEXT, mode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexStorage1DEXT, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexStorage1DEXT, target, levels, internalformat, width)
|
DECLARE_GL_FUNCTION_HEAD(void, TexStorage1DEXT, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage1D, target, levels, internalformat, width)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryObjecti64vEXT, GLuint id, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryObjecti64vEXT, id, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjecti64vEXT, GLuint id, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjecti64v, id, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryObjectui64vEXT, GLuint id, GLenum pname, GLuint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryObjectui64vEXT, id, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjectui64vEXT, GLuint id, GLenum pname, GLuint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjectui64v, id, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindBufferOffsetEXT, GLenum target, GLuint index, GLuint buffer, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindBufferOffsetEXT, target, index, buffer, offset)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindBufferOffsetEXT, GLenum target, GLuint index, GLuint buffer, GLintptr offset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindBufferOffsetEXT, target, index, buffer, offset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ArrayElementEXT, GLint i) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ArrayElementEXT, i)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ArrayElementEXT, GLint i) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ArrayElementEXT, i)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorPointerEXT, GLint size, GLenum type, GLsizei stride, GLsizei count, const void* pointer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorPointerEXT, size, type, stride, count, pointer)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorPointerEXT, GLint size, GLenum type, GLsizei stride, GLsizei count, const void* pointer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorPointerEXT, size, type, stride, count, pointer)
|
||||||
@@ -2297,7 +2315,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMapParameterfvNV, GLenum target, GLenum p
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMapAttribParameterivNV, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMapAttribParameterivNV, target, index, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMapAttribParameterivNV, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMapAttribParameterivNV, target, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMapAttribParameterfvNV, GLenum target, GLuint index, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMapAttribParameterfvNV, target, index, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMapAttribParameterfvNV, GLenum target, GLuint index, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMapAttribParameterfvNV, target, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EvalMapsNV, GLenum target, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EvalMapsNV, target, mode)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, EvalMapsNV, GLenum target, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EvalMapsNV, target, mode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMultisamplefvNV, GLenum pname, GLuint index, GLfloat* val) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMultisamplefvNV, pname, index, val)
|
DECLARE_GL_FUNCTION_HEAD(void, GetMultisamplefvNV, GLenum pname, GLuint index, GLfloat* val) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetMultisamplefv, pname, index, val)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, SampleMaskIndexedNV, GLuint index, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SampleMaskIndexedNV, index, mask)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, SampleMaskIndexedNV, GLuint index, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SampleMaskIndexedNV, index, mask)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexRenderbufferNV, GLenum target, GLuint renderbuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexRenderbufferNV, target, renderbuffer)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexRenderbufferNV, GLenum target, GLuint renderbuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexRenderbufferNV, target, renderbuffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteFencesNV, GLsizei n, const GLuint* fences) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteFencesNV, n, fences)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteFencesNV, GLsizei n, const GLuint* fences) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteFencesNV, n, fences)
|
||||||
@@ -2566,7 +2584,10 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackStreamAttribsNV, GLsizei co
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedbackNV, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedbackNV, target, id)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedbackNV, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedbackNV, target, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacksNV, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacksNV, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacksNV, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacksNV, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacksNV, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacksNV, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacksNV, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacksNV, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsTransformFeedbackNV, GLuint id) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsTransformFeedbackNV, id)
|
MOBILEGL_GL_API GLboolean glIsTransformFeedbackNV(GLuint id) {
|
||||||
|
MGLOG_W("Stub function: %s(...)", __FUNCTION__);
|
||||||
|
return GL_FALSE;
|
||||||
|
}
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedbackNV, )
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedbackNV, )
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedbackNV, )
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedbackNV, )
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackNV, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackNV, mode, id)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackNV, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackNV, mode, id)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@
|
|||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::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 ReadnPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize,
|
||||||
|
void* data);
|
||||||
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);
|
||||||
@@ -26,6 +28,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void GenRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
void GenRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||||
void CreateRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
void CreateRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||||
void NamedRenderbufferStorage(GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height);
|
void NamedRenderbufferStorage(GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height);
|
||||||
|
void NamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat,
|
||||||
|
GLsizei width, GLsizei height);
|
||||||
void GetNamedRenderbufferParameteriv(GLuint renderbuffer, GLenum pname, GLint* params);
|
void GetNamedRenderbufferParameteriv(GLuint renderbuffer, GLenum pname, GLint* params);
|
||||||
void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
||||||
void NamedFramebufferRenderbuffer(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget,
|
void NamedFramebufferRenderbuffer(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget,
|
||||||
@@ -44,12 +48,29 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void FramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
void FramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||||
void FramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
|
void FramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
|
||||||
void NamedFramebufferTexture(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level);
|
void NamedFramebufferTexture(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level);
|
||||||
|
void NamedFramebufferTexture1D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||||
|
void NamedFramebufferTexture2D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||||
|
void NamedFramebufferTexture3D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
||||||
|
GLint zoffset);
|
||||||
|
void NamedFramebufferTextureLayer(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
||||||
void NamedFramebufferDrawBuffer(GLuint framebuffer, GLenum buf);
|
void NamedFramebufferDrawBuffer(GLuint framebuffer, GLenum buf);
|
||||||
void NamedFramebufferDrawBuffers(GLuint framebuffer, GLsizei n, const GLenum* bufs);
|
void NamedFramebufferDrawBuffers(GLuint framebuffer, GLsizei n, const GLenum* bufs);
|
||||||
void NamedFramebufferReadBuffer(GLuint framebuffer, GLenum src);
|
void NamedFramebufferReadBuffer(GLuint framebuffer, GLenum src);
|
||||||
void ClearNamedFramebufferfv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearNamedFramebufferfv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearNamedFramebufferfi(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
void ClearNamedFramebufferfi(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
|
void InvalidateNamedFramebufferData(GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments);
|
||||||
|
void InvalidateNamedFramebufferSubData(GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments,
|
||||||
|
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||||
|
void InvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments);
|
||||||
|
void InvalidateSubFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y,
|
||||||
|
GLsizei width, GLsizei height);
|
||||||
|
void ClearNamedFramebufferiv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
|
void ClearNamedFramebufferuiv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
GLenum CheckNamedFramebufferStatus(GLuint framebuffer, GLenum target);
|
GLenum CheckNamedFramebufferStatus(GLuint framebuffer, GLenum target);
|
||||||
|
void GetFramebufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||||
|
void FramebufferParameteri(GLenum target, GLenum pname, GLint param);
|
||||||
|
void GetNamedFramebufferParameteriv(GLuint framebuffer, GLenum pname, GLint* params);
|
||||||
|
void NamedFramebufferParameteri(GLuint framebuffer, GLenum pname, GLint param);
|
||||||
void GetNamedFramebufferAttachmentParameteriv(GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params);
|
void GetNamedFramebufferAttachmentParameteriv(GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params);
|
||||||
void BlitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1,
|
void BlitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1,
|
||||||
GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask,
|
GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask,
|
||||||
@@ -71,6 +92,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
SharedPtr<MG_State::GLState::ITextureObject> stencilAttachment;
|
SharedPtr<MG_State::GLState::ITextureObject> stencilAttachment;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern UniquePtr<DefaultFramebufferInfo> pDefaultFramebufferInfo;
|
extern UniquePtr<DefaultFramebufferInfo>& pDefaultFramebufferInfo;
|
||||||
} // namespace FramebufferImpl
|
} // namespace FramebufferImpl
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// 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>
|
||||||
@@ -60,6 +61,26 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ValidateColorAttachmentInRange(FramebufferAttachmentType attachment, const char* caller) {
|
||||||
|
const auto first = static_cast<SizeT>(FramebufferAttachmentType::Color0);
|
||||||
|
const auto index = static_cast<SizeT>(attachment);
|
||||||
|
if (index < first) return true;
|
||||||
|
const auto colorIndex = index - first;
|
||||||
|
const auto limit = static_cast<SizeT>(
|
||||||
|
MG_Backend::pActiveBackendObject ? MG_Backend::pActiveBackendObject->GetDynamicParameters()
|
||||||
|
.MaxColorAttachments
|
||||||
|
: static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS));
|
||||||
|
if (colorIndex >= limit) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||||
|
std::format("Colour attachment {} is beyond GL_MAX_COLOR_ATTACHMENTS ({}).", colorIndex, limit)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
|
Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
|
||||||
if (target == RenderbufferTarget::Unknown) {
|
if (target == RenderbufferTarget::Unknown) {
|
||||||
using namespace MG_Util;
|
using namespace MG_Util;
|
||||||
@@ -76,7 +97,13 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero) {
|
Bool ValidateRenderbufferName(Uint index, Bool allowZero) {
|
||||||
if (index == 0 && !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(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue,
|
ErrorCode::InvalidValue,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
||||||
@@ -91,4 +118,100 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
std::format("Renderbuffer name {} is not valid.", index)));
|
std::format("Renderbuffer name {} is not valid.", index)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ValidateFramebufferParameterPname(GLenum pname, Bool isDefaultFramebuffer, Bool forSetter,
|
||||||
|
const char* caller) {
|
||||||
|
Bool isDefaultParameter = false;
|
||||||
|
switch (pname) {
|
||||||
|
case GL_FRAMEBUFFER_DEFAULT_WIDTH:
|
||||||
|
case GL_FRAMEBUFFER_DEFAULT_HEIGHT:
|
||||||
|
case GL_FRAMEBUFFER_DEFAULT_LAYERS:
|
||||||
|
case GL_FRAMEBUFFER_DEFAULT_SAMPLES:
|
||||||
|
case GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:
|
||||||
|
isDefaultParameter = true;
|
||||||
|
break;
|
||||||
|
case GL_DOUBLEBUFFER:
|
||||||
|
case GL_IMPLEMENTATION_COLOR_READ_FORMAT:
|
||||||
|
case GL_IMPLEMENTATION_COLOR_READ_TYPE:
|
||||||
|
case GL_SAMPLES:
|
||||||
|
case GL_SAMPLE_BUFFERS:
|
||||||
|
case GL_STEREO:
|
||||||
|
// Queryable only; glFramebufferParameteri sets none of these.
|
||||||
|
if (forSetter) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||||
|
std::format("pname {} is not settable on a framebuffer.",
|
||||||
|
MG_Util::ConvertGLEnumToString(pname))));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||||
|
std::format("pname {} is not a framebuffer parameter.",
|
||||||
|
MG_Util::ConvertGLEnumToString(pname))));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default framebuffer has no DEFAULT_* state of its own - its shape comes from the
|
||||||
|
// surface - so those names are accepted enums it simply cannot answer or accept.
|
||||||
|
if (isDefaultFramebuffer && isDefaultParameter) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||||
|
std::format("pname {} does not apply to the default framebuffer.",
|
||||||
|
MG_Util::ConvertGLEnumToString(pname))));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ValidateReadFramebufferForCopy(const char* caller) {
|
||||||
|
auto& framebufferObject =
|
||||||
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
|
||||||
|
if (!framebufferObject || !framebufferObject->CheckCompleteness()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidFramebufferOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||||
|
"Read framebuffer is not framebuffer complete."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FramebufferAttachmentType readBuffer = framebufferObject->GetReadBuffer();
|
||||||
|
if (readBuffer == FramebufferAttachmentType::None ||
|
||||||
|
!framebufferObject->GetAttachment(readBuffer).IsValid()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||||
|
"Read buffer names no attachment of the read framebuffer."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAMPLE_BUFFERS is one whenever the read buffer resolves to multisample storage. A
|
||||||
|
// multisample texture says so by its target - its sample count can legally be one - while a
|
||||||
|
// renderbuffer says so by having been given a non-zero sample count.
|
||||||
|
const auto& readAttachment = framebufferObject->GetAttachment(readBuffer);
|
||||||
|
Bool isMultisampled = false;
|
||||||
|
if (readAttachment.IsRenderbuffer() && readAttachment.GetRenderbuffer()) {
|
||||||
|
isMultisampled = readAttachment.GetRenderbuffer()->GetSamples() > 0;
|
||||||
|
} else if (readAttachment.IsTexture() && readAttachment.GetTexture()) {
|
||||||
|
const auto target = readAttachment.GetTexture()->GetTarget();
|
||||||
|
isMultisampled = target == TextureTarget::Texture2DMultisample ||
|
||||||
|
target == TextureTarget::Texture2DMultisampleArray;
|
||||||
|
}
|
||||||
|
if (isMultisampled) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||||
|
"Cannot copy from a multisampled read framebuffer."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||||
|
|||||||
@@ -14,6 +14,21 @@ namespace MobileGL::MG_Impl::GLImpl::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);
|
||||||
|
// GL_COLOR_ATTACHMENTn is a token per n up to 31, but only the first GL_MAX_COLOR_ATTACHMENTS of
|
||||||
|
// them name an attachment point of a framebuffer object; the rest are INVALID_OPERATION for the
|
||||||
|
// attaching entry points (GL 4.6 core 9.2.7). Non-colour attachments pass through unchanged.
|
||||||
|
Bool ValidateColorAttachmentInRange(FramebufferAttachmentType attachment, const char* caller);
|
||||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
||||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
||||||
|
// The read-framebuffer preconditions the CopyTexSubImage family shares (GL 4.6 core 8.6): the
|
||||||
|
// read framebuffer must be complete, its read buffer must name a real attachment, and it must
|
||||||
|
// not be multisampled. Incompleteness is INVALID_FRAMEBUFFER_OPERATION, the other two are
|
||||||
|
// INVALID_OPERATION.
|
||||||
|
Bool ValidateReadFramebufferForCopy(const char* caller);
|
||||||
|
// The pname sets of glGet/FramebufferParameteri (GL 4.6 core 9.2.3). Order matters and is part
|
||||||
|
// of the contract: a name outside the table is INVALID_ENUM, and only then is a name that the
|
||||||
|
// DEFAULT framebuffer does not answer INVALID_OPERATION. Testing the framebuffer kind first
|
||||||
|
// would turn GL_FRAMEBUFFER_DEFAULT_WIDTH on framebuffer zero into the wrong error.
|
||||||
|
Bool ValidateFramebufferParameterPname(GLenum pname, Bool isDefaultFramebuffer, Bool forSetter,
|
||||||
|
const char* caller);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,9 +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* data);
|
void GetInteger64v(GLenum pname, GLint64* params);
|
||||||
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||||
void GetInteger64i_v(GLenum target, GLuint index, GLint64* 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
@@ -22,6 +22,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLchar* name);
|
GLchar* name);
|
||||||
void GetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type,
|
void GetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type,
|
||||||
GLchar* name);
|
GLchar* name);
|
||||||
|
void GetActiveUniformName(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length,
|
||||||
|
GLchar* uniformName);
|
||||||
|
void GetUniformIndices(GLuint program, GLsizei uniformCount, const GLchar* const* uniformNames,
|
||||||
|
GLuint* uniformIndices);
|
||||||
|
void GetActiveUniformsiv(GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname,
|
||||||
|
GLint* params);
|
||||||
void GetAttachedShaders(GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders);
|
void GetAttachedShaders(GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders);
|
||||||
GLint GetAttribLocation(GLuint program, const GLchar* name);
|
GLint GetAttribLocation(GLuint program, const GLchar* name);
|
||||||
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||||
@@ -32,6 +38,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLint GetUniformLocation(GLuint program, const GLchar* name);
|
GLint GetUniformLocation(GLuint program, const GLchar* name);
|
||||||
void GetUniformfv(GLuint program, GLint location, GLfloat* params);
|
void GetUniformfv(GLuint program, GLint location, GLfloat* params);
|
||||||
void GetUniformiv(GLuint program, GLint location, GLint* params);
|
void GetUniformiv(GLuint program, GLint location, GLint* params);
|
||||||
|
void GetUniformuiv(GLuint program, GLint location, GLuint* params);
|
||||||
GLboolean IsProgram(GLuint program);
|
GLboolean IsProgram(GLuint program);
|
||||||
GLboolean IsShader(GLuint shader);
|
GLboolean IsShader(GLuint shader);
|
||||||
void LinkProgram(GLuint program);
|
void LinkProgram(GLuint program);
|
||||||
@@ -46,6 +53,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void Uniform3i(GLint location, GLint v0, GLint v1, GLint v2);
|
void Uniform3i(GLint location, GLint v0, GLint v1, GLint v2);
|
||||||
void Uniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
|
void Uniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
|
||||||
void Uniform1ui(GLint location, GLuint v0);
|
void Uniform1ui(GLint location, GLuint v0);
|
||||||
|
void Uniform2ui(GLint location, GLuint v0, GLuint v1);
|
||||||
|
void Uniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2);
|
||||||
|
void Uniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
|
||||||
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value);
|
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value);
|
||||||
void Uniform2fv(GLint location, GLsizei count, const GLfloat* value);
|
void Uniform2fv(GLint location, GLsizei count, const GLfloat* value);
|
||||||
void Uniform3fv(GLint location, GLsizei count, const GLfloat* value);
|
void Uniform3fv(GLint location, GLsizei count, const GLfloat* value);
|
||||||
@@ -55,6 +65,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void Uniform3iv(GLint location, GLsizei count, const GLint* value);
|
void Uniform3iv(GLint location, GLsizei count, const GLint* value);
|
||||||
void Uniform4iv(GLint location, GLsizei count, const GLint* value);
|
void Uniform4iv(GLint location, GLsizei count, const GLint* value);
|
||||||
void Uniform1uiv(GLint location, GLsizei count, const GLuint* value);
|
void Uniform1uiv(GLint location, GLsizei count, const GLuint* value);
|
||||||
|
void Uniform2uiv(GLint location, GLsizei count, const GLuint* value);
|
||||||
|
void Uniform3uiv(GLint location, GLsizei count, const GLuint* value);
|
||||||
|
void Uniform4uiv(GLint location, GLsizei count, const GLuint* value);
|
||||||
void UniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
void UniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
void UniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
void UniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
void UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
void UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
@@ -88,13 +101,33 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const GLfloat* value);
|
const GLfloat* value);
|
||||||
void ProgramUniformMatrix4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
void ProgramUniformMatrix4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
const GLfloat* value);
|
const GLfloat* value);
|
||||||
|
void UniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix2x3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix3x2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix2x4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix4x2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix3x4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix4x3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
GLuint GetUniformBlockIndex(GLuint program, const GLchar* uniformBlockName);
|
GLuint GetUniformBlockIndex(GLuint program, const GLchar* uniformBlockName);
|
||||||
void UniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
|
void UniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
|
||||||
void GetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params);
|
void GetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params);
|
||||||
void GetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length,
|
void GetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length,
|
||||||
GLchar* uniformBlockName);
|
GLchar* uniformBlockName);
|
||||||
void BindFragDataLocation(GLuint program, GLuint colorNumber, const char* name);
|
void BindFragDataLocation(GLuint program, GLuint colorNumber, const char* name);
|
||||||
|
void BindFragDataLocationIndexed(GLuint program, GLuint colorNumber, GLuint index, const char* name);
|
||||||
GLint GetFragDataLocation(GLuint program, const char* name);
|
GLint GetFragDataLocation(GLuint program, const char* name);
|
||||||
|
GLint GetFragDataIndex(GLuint program, const char* name);
|
||||||
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||||
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
||||||
@@ -104,5 +137,47 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
GLint GetProgramResourceLocationIndex(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 ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||||
|
void Uniform1d(GLint location, GLdouble v0);
|
||||||
|
void Uniform1dv(GLint location, GLsizei count, const GLdouble* value);
|
||||||
|
void ProgramUniform1d(GLuint program, GLint location, GLdouble v0);
|
||||||
|
void ProgramUniform1dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
||||||
|
void Uniform2d(GLint location, GLdouble v0, GLdouble v1);
|
||||||
|
void Uniform2dv(GLint location, GLsizei count, const GLdouble* value);
|
||||||
|
void ProgramUniform2d(GLuint program, GLint location, GLdouble v0, GLdouble v1);
|
||||||
|
void ProgramUniform2dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
||||||
|
void Uniform3d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2);
|
||||||
|
void Uniform3dv(GLint location, GLsizei count, const GLdouble* value);
|
||||||
|
void ProgramUniform3d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2);
|
||||||
|
void ProgramUniform3dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
||||||
|
void Uniform4d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3);
|
||||||
|
void Uniform4dv(GLint location, GLsizei count, const GLdouble* value);
|
||||||
|
void ProgramUniform4d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3);
|
||||||
|
void ProgramUniform4dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
||||||
|
void UniformMatrix2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void UniformMatrix3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void UniformMatrix4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void UniformMatrix2x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void UniformMatrix2x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void UniformMatrix3x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void UniformMatrix3x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void UniformMatrix4x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void UniformMatrix4x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void ProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||||
|
void GetUniformdv(GLuint program, GLint location, GLdouble* params);
|
||||||
void ValidateProgram(GLuint program);
|
void ValidateProgram(GLuint program);
|
||||||
|
void ProgramParameteri(GLuint program, GLenum pname, GLint value);
|
||||||
|
GLuint CreateShaderProgramv(GLenum type, GLsizei count, const GLchar* const* strings);
|
||||||
|
void GetProgramBinary(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary);
|
||||||
|
void ProgramBinary(GLuint program, GLenum binaryFormat, const void* binary, GLsizei length);
|
||||||
|
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);
|
||||||
|
void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size,
|
||||||
|
GLenum* type, GLchar* name);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#include "GL_ProgramPipeline.h"
|
||||||
|
|
||||||
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_State/GLState/ErrorState/ErrorInfo.h>
|
||||||
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
namespace {
|
||||||
|
void RecordPipelineError(ErrorCode code, const char* function, String message) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
code, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", function, Move(message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A pipeline name only names an object once it has been bound or created; querying a
|
||||||
|
// reserved-but-unmaterialised name is INVALID_OPERATION (GL 4.6 core 7.4).
|
||||||
|
const SharedPtr<MG_State::GLState::ProgramPipelineObject>* TryGetPipeline(GLuint pipeline,
|
||||||
|
const char* function) {
|
||||||
|
if (!MG_State::pGLContext->IsProgramPipelineObject(pipeline)) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidOperation, function,
|
||||||
|
std::format("Program pipeline {} does not exist.", pipeline));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &MG_State::pGLContext->GetProgramPipelineObject(pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ValidatePipelineCount(GLsizei n, const char* function) {
|
||||||
|
if (n < 0) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidValue, function, "n must be non-negative.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GL 4.6 core table 7.1 maps each stage bit onto a shader stage.
|
||||||
|
Bool TryResolveStageBit(GLbitfield bit, ShaderStage& outStage) {
|
||||||
|
switch (bit) {
|
||||||
|
case GL_VERTEX_SHADER_BIT: outStage = ShaderStage::Vertex; return true;
|
||||||
|
case GL_TESS_CONTROL_SHADER_BIT: outStage = ShaderStage::TessControl; return true;
|
||||||
|
case GL_TESS_EVALUATION_SHADER_BIT: outStage = ShaderStage::TessEval; return true;
|
||||||
|
case GL_GEOMETRY_SHADER_BIT: outStage = ShaderStage::Geometry; return true;
|
||||||
|
case GL_FRAGMENT_SHADER_BIT: outStage = ShaderStage::Fragment; return true;
|
||||||
|
case GL_COMPUTE_SHADER_BIT: outStage = ShaderStage::Compute; return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr GLbitfield kAllStageBits = GL_VERTEX_SHADER_BIT | GL_TESS_CONTROL_SHADER_BIT |
|
||||||
|
GL_TESS_EVALUATION_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
|
||||||
|
GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void GenProgramPipelines(GLsizei n, GLuint* pipelines) {
|
||||||
|
if (!ValidatePipelineCount(n, __func__)) return;
|
||||||
|
if (n == 0 || !pipelines) return;
|
||||||
|
|
||||||
|
static thread_local Vector<GLuint> names;
|
||||||
|
MG_State::pGLContext->GenProgramPipelineNames(static_cast<Uint>(n), names);
|
||||||
|
Memcpy(pipelines, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateProgramPipelines(GLsizei n, GLuint* pipelines) {
|
||||||
|
if (!ValidatePipelineCount(n, __func__)) return;
|
||||||
|
if (n == 0 || !pipelines) return;
|
||||||
|
|
||||||
|
static thread_local Vector<GLuint> names;
|
||||||
|
MG_State::pGLContext->GenProgramPipelineNames(static_cast<Uint>(n), names);
|
||||||
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
|
pipelines[i] = names[static_cast<SizeT>(i)];
|
||||||
|
MG_State::pGLContext->CreateProgramPipelineObject(names[static_cast<SizeT>(i)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteProgramPipelines(GLsizei n, const GLuint* pipelines) {
|
||||||
|
if (!ValidatePipelineCount(n, __func__)) return;
|
||||||
|
if (!pipelines) return;
|
||||||
|
|
||||||
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
|
// Deleting zero, an unknown name, or a name that was only reserved is silently ignored.
|
||||||
|
MG_State::pGLContext->MarkProgramPipelineForDeletion(pipelines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindProgramPipeline(GLuint pipeline) {
|
||||||
|
if (pipeline != 0 && !MG_State::pGLContext->ValidateProgramPipelineName(pipeline)) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidOperation, __func__,
|
||||||
|
std::format("Program pipeline name {} is not valid.", pipeline));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->BindProgramPipelineObject(pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLboolean IsProgramPipeline(GLuint pipeline) {
|
||||||
|
return MG_State::pGLContext->IsProgramPipelineObject(pipeline) ? GL_TRUE : GL_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetProgramPipelineiv(GLuint pipeline, GLenum pname, GLint* params) {
|
||||||
|
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||||
|
if (!pipelineObject || !params) return;
|
||||||
|
|
||||||
|
const auto stageProgramName = [&](ShaderStage stage) -> GLint {
|
||||||
|
const auto& program = (*pipelineObject)->GetStageProgram(stage);
|
||||||
|
return program ? static_cast<GLint>(program->GetExternalIndex()) : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (pname) {
|
||||||
|
case GL_ACTIVE_PROGRAM: {
|
||||||
|
const auto& active = (*pipelineObject)->GetActiveProgram();
|
||||||
|
*params = active ? static_cast<GLint>(active->GetExternalIndex()) : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GL_VERTEX_SHADER: *params = stageProgramName(ShaderStage::Vertex); break;
|
||||||
|
case GL_TESS_CONTROL_SHADER: *params = stageProgramName(ShaderStage::TessControl); break;
|
||||||
|
case GL_TESS_EVALUATION_SHADER: *params = stageProgramName(ShaderStage::TessEval); break;
|
||||||
|
case GL_GEOMETRY_SHADER: *params = stageProgramName(ShaderStage::Geometry); break;
|
||||||
|
case GL_FRAGMENT_SHADER: *params = stageProgramName(ShaderStage::Fragment); break;
|
||||||
|
case GL_COMPUTE_SHADER: *params = stageProgramName(ShaderStage::Compute); break;
|
||||||
|
case GL_VALIDATE_STATUS: *params = (*pipelineObject)->GetValidateStatus() ? GL_TRUE : GL_FALSE; break;
|
||||||
|
case GL_INFO_LOG_LENGTH: {
|
||||||
|
// GL counts the null terminator, and reports 0 rather than 1 for an empty log.
|
||||||
|
const auto& log = (*pipelineObject)->GetInfoLog();
|
||||||
|
*params = log.empty() ? 0 : static_cast<GLint>(log.length()) + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
RecordPipelineError(ErrorCode::InvalidEnum, __func__,
|
||||||
|
std::format("pname {} is not a program pipeline parameter.",
|
||||||
|
MG_Util::ConvertGLEnumToString(pname)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetProgramPipelineInfoLog(GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) {
|
||||||
|
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||||
|
if (!pipelineObject) return;
|
||||||
|
if (bufSize < 0) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidValue, __func__, "bufSize must be non-negative.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bufSize == 0 || !infoLog) {
|
||||||
|
if (length) *length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& log = (*pipelineObject)->GetInfoLog();
|
||||||
|
const auto copied = std::min<GLsizei>(bufSize - 1, static_cast<GLsizei>(log.length()));
|
||||||
|
if (copied > 0) Memcpy(infoLog, log.data(), static_cast<SizeT>(copied));
|
||||||
|
infoLog[copied] = '\0';
|
||||||
|
if (length) *length = copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program) {
|
||||||
|
if (stages != GL_ALL_SHADER_BITS && (stages & ~kAllStageBits) != 0) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidValue, __func__, "stages names a bit that is not a shader stage.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||||
|
if (!pipelineObject) return;
|
||||||
|
|
||||||
|
SharedPtr<MG_State::GLState::ProgramObject> programObject;
|
||||||
|
if (program != 0) {
|
||||||
|
if (!MG_State::pGLContext->ValidateProgramName(program)) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidValue, __func__,
|
||||||
|
std::format("{} is not the name of a program object.", program));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
programObject = MG_State::pGLContext->GetProgramObject(program);
|
||||||
|
if (!programObject) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidValue, __func__,
|
||||||
|
std::format("{} is not the name of a program object.", program));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!programObject->GetLinkStatus()) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidOperation, __func__,
|
||||||
|
std::format("Program {} has not been linked successfully.", program));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLbitfield selected = stages == GL_ALL_SHADER_BITS ? kAllStageBits : stages;
|
||||||
|
for (GLbitfield bit = 1; bit != 0 && bit <= kAllStageBits; bit <<= 1) {
|
||||||
|
if ((selected & bit) == 0) continue;
|
||||||
|
ShaderStage stage = ShaderStage::Unknown;
|
||||||
|
if (!TryResolveStageBit(bit, stage)) continue;
|
||||||
|
// program == 0 clears the stage, which is what a null program reference means here.
|
||||||
|
(*pipelineObject)->SetStageProgram(stage, programObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActiveShaderProgram(GLuint pipeline, GLuint program) {
|
||||||
|
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||||
|
if (!pipelineObject) return;
|
||||||
|
|
||||||
|
if (program == 0) {
|
||||||
|
(*pipelineObject)->SetActiveProgram(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!MG_State::pGLContext->ValidateProgramName(program)) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidValue, __func__,
|
||||||
|
std::format("{} is not the name of a program object.", program));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto programObject = MG_State::pGLContext->GetProgramObject(program);
|
||||||
|
if (!programObject) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidValue, __func__,
|
||||||
|
std::format("{} is not the name of a program object.", program));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!programObject->GetLinkStatus()) {
|
||||||
|
RecordPipelineError(ErrorCode::InvalidOperation, __func__,
|
||||||
|
std::format("Program {} has not been linked successfully.", program));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*pipelineObject)->SetActiveProgram(programObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ValidateProgramPipeline(GLuint pipeline) {
|
||||||
|
const auto* pipelineObject = TryGetPipeline(pipeline, __func__);
|
||||||
|
if (!pipelineObject) return;
|
||||||
|
// Nothing here can fail today: MobileGL links each stage program on its own, so there is no
|
||||||
|
// cross-stage interface to re-check at validation time. The log stays empty, which GL allows.
|
||||||
|
(*pipelineObject)->SetValidateStatus(true);
|
||||||
|
}
|
||||||
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.h
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
void GenProgramPipelines(GLsizei n, GLuint* pipelines);
|
||||||
|
void CreateProgramPipelines(GLsizei n, GLuint* pipelines);
|
||||||
|
void DeleteProgramPipelines(GLsizei n, const GLuint* pipelines);
|
||||||
|
void BindProgramPipeline(GLuint pipeline);
|
||||||
|
GLboolean IsProgramPipeline(GLuint pipeline);
|
||||||
|
void GetProgramPipelineiv(GLuint pipeline, GLenum pname, GLint* params);
|
||||||
|
void GetProgramPipelineInfoLog(GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
|
||||||
|
void UseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program);
|
||||||
|
void ActiveShaderProgram(GLuint pipeline, GLuint program);
|
||||||
|
void ValidateProgramPipeline(GLuint pipeline);
|
||||||
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
@@ -0,0 +1,651 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#include "GL_Query.h"
|
||||||
|
#include "../Getter/GL_Getter.h"
|
||||||
|
#include <Config.h>
|
||||||
|
#include <MG_Backend/BackendObjects.h>
|
||||||
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_State/GLState/ErrorState/ErrorInfo.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
namespace {
|
||||||
|
// Frontend query object (GL_ARB_timer_query): wraps an optional backend
|
||||||
|
// timer-query handle. A null backend handle (backend has no timer-query
|
||||||
|
// support, timer queries are disabled by config, or the backend could
|
||||||
|
// not create a query at call time) keeps a graceful fallback: the query
|
||||||
|
// result is immediately available and reads as zero.
|
||||||
|
struct QueryObject {
|
||||||
|
GLuint id = 0;
|
||||||
|
GLenum target = 0; // 0 = gen'd but never used with BeginQuery/QueryCounter
|
||||||
|
// glCreateQueries makes the object outright; glGenQueries only reserves the name,
|
||||||
|
// and the object appears when the name is first used (GL 4.6 core 4.2.1).
|
||||||
|
Bool created = false;
|
||||||
|
MG_Backend::BackendQueryHandle backendHandle = nullptr;
|
||||||
|
Bool active = false;
|
||||||
|
Bool ended = false;
|
||||||
|
Bool resultCached = false;
|
||||||
|
Uint64 cachedResult = 0;
|
||||||
|
// Transform feedback primitive counter at BeginQuery time.
|
||||||
|
Uint64 counterSnapshot = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Query calls may arrive from any thread (launchers migrate the context
|
||||||
|
// across JVM threads), so the live-object registry is mutex-guarded,
|
||||||
|
// like the sync-object registry in GL_Sync.cpp. Entries left at process
|
||||||
|
// shutdown are simply dropped; their backend handles die with the
|
||||||
|
// backend.
|
||||||
|
std::mutex g_queryObjectsMutex;
|
||||||
|
UnorderedMap<GLuint, QueryObject*> g_liveQueryObjects;
|
||||||
|
// Monotonically increasing id allocator; ids are valid query objects
|
||||||
|
// immediately after GenQueries.
|
||||||
|
GLuint g_nextQueryId = 1;
|
||||||
|
// Id of the query currently active on GL_TIME_ELAPSED (0 = none).
|
||||||
|
GLuint g_activeTimeElapsedQueryId = 0;
|
||||||
|
// Ids of the queries active on the transform feedback targets (0 = none).
|
||||||
|
GLuint g_activePrimitivesWrittenQueryId = 0;
|
||||||
|
GLuint g_activePrimitivesGeneratedQueryId = 0;
|
||||||
|
// Id of the query active on GL_SAMPLES_PASSED (0 = none).
|
||||||
|
GLuint g_activeSamplesPassedQueryId = 0;
|
||||||
|
|
||||||
|
Bool TimerQueryDisabled() {
|
||||||
|
return MG_Config::Features.DisableTimerQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecordQueryError(ErrorCode code, const char* function, const char* message) {
|
||||||
|
MG_State::pGLContext->RecordError(code,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", function, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The by-buffer query getters write the result into a buffer object instead of client
|
||||||
|
// memory. Everything about the query itself - the name, whether it is still active, the
|
||||||
|
// parameter - is checked by GetQueryObjectValue; what is left is the destination, so this
|
||||||
|
// resolves the buffer and confirms the write lands inside it (GL 4.6 core 4.2.1).
|
||||||
|
Bool ResolveQueryResultDestination(GLuint buffer, GLintptr offset, SizeT writeSize, const char* function,
|
||||||
|
SharedPtr<MG_State::GLState::BufferObject>& outBuffer) {
|
||||||
|
if (offset < 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidValue, function, "Offset cannot be negative.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!MG_State::pGLContext->ValidateBufferObject(buffer)) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, function, "Buffer object does not exist.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
if (!bufferObject) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, function, "Buffer object does not exist.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (static_cast<SizeT>(offset) + writeSize > bufferObject->GetSize()) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, function,
|
||||||
|
"The query result does not fit in the buffer object at this offset.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
outBuffer = bufferObject;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callers must hold g_queryObjectsMutex.
|
||||||
|
QueryObject* FindQueryObjectLocked(GLuint id) {
|
||||||
|
const auto it = g_liveQueryObjects.find(id);
|
||||||
|
return it != g_liveQueryObjects.end() ? it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callers must hold g_queryObjectsMutex. Releases the backend handle
|
||||||
|
// (if any) and clears any cached result, so the object can be reused.
|
||||||
|
void ResetQueryObjectLocked(QueryObject* queryObject) {
|
||||||
|
if (queryObject->backendHandle) {
|
||||||
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
|
deleteBackendQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->backendHandle = nullptr;
|
||||||
|
}
|
||||||
|
queryObject->active = false;
|
||||||
|
queryObject->ended = false;
|
||||||
|
queryObject->resultCached = false;
|
||||||
|
queryObject->cachedResult = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callers must hold g_queryObjectsMutex.
|
||||||
|
void EndTimeElapsedQueryLocked(QueryObject* queryObject) {
|
||||||
|
const auto endTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.EndTimeElapsedQuery;
|
||||||
|
if (endTimeElapsedQuery && queryObject->backendHandle) {
|
||||||
|
endTimeElapsedQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->active = false;
|
||||||
|
queryObject->ended = true;
|
||||||
|
g_activeTimeElapsedQueryId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared GetQueryObject* implementation. Returns false when an error
|
||||||
|
// was recorded and no value should be written back. `outValueProduced`, when given,
|
||||||
|
// additionally distinguishes "succeeded with a value" from "succeeded but the result is not
|
||||||
|
// ready" - the GL_QUERY_RESULT_NO_WAIT case, where GL_ARB_query_buffer_object says the
|
||||||
|
// destination is left alone rather than written with a placeholder.
|
||||||
|
Bool GetQueryObjectValue(GLuint id, GLenum pname, const char* function, Uint64& outValue,
|
||||||
|
Bool* outValueProduced = nullptr) {
|
||||||
|
if (outValueProduced) *outValueProduced = true;
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
auto* queryObject = FindQueryObjectLocked(id);
|
||||||
|
if (!queryObject) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, function, "Query object does not exist.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (queryObject->active) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, function, "Query object is still active.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (pname) {
|
||||||
|
case GL_QUERY_TARGET:
|
||||||
|
// The target a query was begun with (or created with, for glCreateQueries) - state
|
||||||
|
// the object has carried all along, GL 4.6 core table 23.35.
|
||||||
|
outValue = queryObject->target;
|
||||||
|
return true;
|
||||||
|
case GL_QUERY_RESULT_NO_WAIT: {
|
||||||
|
if (queryObject->resultCached) {
|
||||||
|
outValue = queryObject->cachedResult;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Uint64 result = 0;
|
||||||
|
const auto getQueryResult64 = MG_Backend::gBackendFunctionsTable.GL.GetQueryResult64;
|
||||||
|
if (queryObject->backendHandle && getQueryResult64 &&
|
||||||
|
!getQueryResult64(queryObject->backendHandle, /*wait=*/false, &result)) {
|
||||||
|
// Not ready. The whole point of the no-wait form is that the caller's
|
||||||
|
// destination keeps whatever it already held.
|
||||||
|
if (outValueProduced) *outValueProduced = false;
|
||||||
|
outValue = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (queryObject->target == GL_ANY_SAMPLES_PASSED ||
|
||||||
|
queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||||
|
result = result != 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
if (queryObject->backendHandle) {
|
||||||
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
|
deleteBackendQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->backendHandle = nullptr;
|
||||||
|
}
|
||||||
|
queryObject->cachedResult = result;
|
||||||
|
queryObject->resultCached = true;
|
||||||
|
outValue = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case GL_QUERY_RESULT_AVAILABLE: {
|
||||||
|
if (queryObject->resultCached || !queryObject->backendHandle) {
|
||||||
|
outValue = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto isQueryResultAvailable = MG_Backend::gBackendFunctionsTable.GL.IsQueryResultAvailable;
|
||||||
|
outValue = (!isQueryResultAvailable || isQueryResultAvailable(queryObject->backendHandle)) ? 1 : 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case GL_QUERY_RESULT: {
|
||||||
|
if (queryObject->resultCached) {
|
||||||
|
outValue = queryObject->cachedResult;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Uint64 result = 0;
|
||||||
|
if (queryObject->backendHandle) {
|
||||||
|
const auto getQueryResult64 = MG_Backend::gBackendFunctionsTable.GL.GetQueryResult64;
|
||||||
|
if (getQueryResult64 &&
|
||||||
|
!getQueryResult64(queryObject->backendHandle, /*wait=*/true, &result)) {
|
||||||
|
// The backend could not produce the result YET (e.g. a
|
||||||
|
// Vulkan wait refusing to block on a not-yet-submitted
|
||||||
|
// frame serial). Per the documented no-stall tradeoff
|
||||||
|
// this call reads 0, but the value is NOT cached and
|
||||||
|
// the backend handle is kept, so a later AVAILABLE
|
||||||
|
// poll / RESULT read still produces the real value.
|
||||||
|
outValue = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// ANY_SAMPLES_PASSED* report a boolean.
|
||||||
|
if (queryObject->target == GL_ANY_SAMPLES_PASSED ||
|
||||||
|
queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||||
|
result = result != 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
// Final value produced (or no GetQueryResult64 hook: the
|
||||||
|
// query degrades to a zero result); the backend handle is
|
||||||
|
// consumed and the value cached for later reads.
|
||||||
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
|
deleteBackendQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->backendHandle = nullptr;
|
||||||
|
}
|
||||||
|
queryObject->cachedResult = result;
|
||||||
|
queryObject->resultCached = true;
|
||||||
|
outValue = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, function, "Unsupported query object parameter.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void GetQueryBufferObject(GLuint id, GLuint buffer, GLenum pname, GLintptr offset, const char* function) {
|
||||||
|
SharedPtr<MG_State::GLState::BufferObject> bufferObject;
|
||||||
|
if (!ResolveQueryResultDestination(buffer, offset, sizeof(T), function, bufferObject)) return;
|
||||||
|
|
||||||
|
Uint64 value = 0;
|
||||||
|
Bool valueProduced = false;
|
||||||
|
if (!GetQueryObjectValue(id, pname, function, value, &valueProduced)) return;
|
||||||
|
// GL_QUERY_RESULT_NO_WAIT on a result that has not landed writes nothing at all.
|
||||||
|
if (!valueProduced) return;
|
||||||
|
|
||||||
|
const T narrowed = static_cast<T>(value);
|
||||||
|
bufferObject->UploadSubData({const_cast<T*>(&narrowed), sizeof(T)}, static_cast<SizeT>(offset));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void GenQueries(GLsizei n, GLuint* ids) {
|
||||||
|
if (n < 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ids) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
|
const GLuint id = g_nextQueryId++;
|
||||||
|
auto* queryObject = new QueryObject;
|
||||||
|
queryObject->id = id;
|
||||||
|
g_liveQueryObjects[id] = queryObject;
|
||||||
|
ids[i] = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// glCreateQueries differs from glGenQueries in creating the objects outright, with their
|
||||||
|
// target already fixed and the rest of their state at the defaults (GL 4.6 core 4.2.1).
|
||||||
|
void CreateQueries(GLenum target, GLsizei n, GLuint* ids) {
|
||||||
|
switch (target) {
|
||||||
|
case GL_SAMPLES_PASSED:
|
||||||
|
case GL_ANY_SAMPLES_PASSED:
|
||||||
|
case GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
|
||||||
|
case GL_TIME_ELAPSED:
|
||||||
|
case GL_TIMESTAMP:
|
||||||
|
case GL_PRIMITIVES_GENERATED:
|
||||||
|
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not accepted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n < 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ids) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
|
const GLuint id = g_nextQueryId++;
|
||||||
|
auto* queryObject = new QueryObject;
|
||||||
|
queryObject->id = id;
|
||||||
|
queryObject->target = target;
|
||||||
|
queryObject->created = true;
|
||||||
|
g_liveQueryObjects[id] = queryObject;
|
||||||
|
ids[i] = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteQueries(GLsizei n, const GLuint* ids) {
|
||||||
|
if (n < 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ids) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
|
const auto it = g_liveQueryObjects.find(ids[i]);
|
||||||
|
if (it == g_liveQueryObjects.end()) {
|
||||||
|
continue; // unknown ids are silently ignored
|
||||||
|
}
|
||||||
|
QueryObject* queryObject = it->second;
|
||||||
|
if (queryObject->active) {
|
||||||
|
// Implicitly end before deletion, releasing the matching active slot.
|
||||||
|
if (queryObject->target == GL_SAMPLES_PASSED || queryObject->target == GL_ANY_SAMPLES_PASSED ||
|
||||||
|
queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||||
|
if (const auto endOcclusionQuery = MG_Backend::gBackendFunctionsTable.GL.EndOcclusionQuery;
|
||||||
|
endOcclusionQuery && queryObject->backendHandle) {
|
||||||
|
endOcclusionQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->active = false;
|
||||||
|
g_activeSamplesPassedQueryId = 0;
|
||||||
|
} else if (queryObject->target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ||
|
||||||
|
queryObject->target == GL_PRIMITIVES_GENERATED) {
|
||||||
|
queryObject->active = false;
|
||||||
|
(queryObject->target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN
|
||||||
|
? g_activePrimitivesWrittenQueryId
|
||||||
|
: g_activePrimitivesGeneratedQueryId) = 0;
|
||||||
|
} else {
|
||||||
|
EndTimeElapsedQueryLocked(queryObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queryObject->backendHandle) {
|
||||||
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
|
deleteBackendQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->backendHandle = nullptr;
|
||||||
|
}
|
||||||
|
g_liveQueryObjects.erase(it);
|
||||||
|
delete queryObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLboolean IsQuery(GLuint id) {
|
||||||
|
if (id == 0) {
|
||||||
|
return GL_FALSE;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
// A name from glGenQueries is not yet a query object: it becomes one when it is first
|
||||||
|
// used with BeginQuery/QueryCounter (which is what a non-zero target records), or
|
||||||
|
// immediately if it came from glCreateQueries.
|
||||||
|
const auto* queryObject = FindQueryObjectLocked(id);
|
||||||
|
return (queryObject != nullptr && (queryObject->created || queryObject->target != 0)) ? GL_TRUE : GL_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginQuery(GLenum target, GLuint id) {
|
||||||
|
const Bool isTransformFeedbackQuery =
|
||||||
|
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
||||||
|
const Bool isOcclusionQuery =
|
||||||
|
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||||
|
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
||||||
|
MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||||
|
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery) {
|
||||||
|
// GL_TIMESTAMP is not a valid BeginQuery target; the occlusion targets
|
||||||
|
// need backend support.
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (id == 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query id 0 cannot be used.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
auto* queryObject = FindQueryObjectLocked(id);
|
||||||
|
if (!queryObject) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GLuint& activeQueryId = isTransformFeedbackQuery
|
||||||
|
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
||||||
|
: g_activePrimitivesGeneratedQueryId)
|
||||||
|
: (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId);
|
||||||
|
if (activeQueryId != 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
|
"A query is already active on this target.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queryObject->active) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object is already active.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queryObject->target != 0 && queryObject->target != target) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
|
"Query object was already used with a different target.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||||
|
queryObject->target = target;
|
||||||
|
queryObject->active = true;
|
||||||
|
if (isTransformFeedbackQuery) {
|
||||||
|
// Prefer real GPU transform-feedback queries (exact with geometry shaders);
|
||||||
|
// the CPU accounting delta stays as the fallback when the backend lacks them.
|
||||||
|
const auto beginXfbPrimitivesQuery = MG_Backend::gBackendFunctionsTable.GL.BeginXfbPrimitivesQuery;
|
||||||
|
queryObject->backendHandle =
|
||||||
|
beginXfbPrimitivesQuery ? beginXfbPrimitivesQuery(target == GL_PRIMITIVES_GENERATED) : nullptr;
|
||||||
|
queryObject->counterSnapshot = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter();
|
||||||
|
} else if (isOcclusionQuery) {
|
||||||
|
queryObject->backendHandle = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery();
|
||||||
|
} else {
|
||||||
|
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
||||||
|
queryObject->backendHandle =
|
||||||
|
(!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr;
|
||||||
|
}
|
||||||
|
activeQueryId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndQuery(GLenum target) {
|
||||||
|
const Bool isTransformFeedbackQuery =
|
||||||
|
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
||||||
|
const Bool isOcclusionQuery =
|
||||||
|
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||||
|
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
||||||
|
MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||||
|
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
GLuint& activeQueryId = isTransformFeedbackQuery
|
||||||
|
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
||||||
|
: g_activePrimitivesGeneratedQueryId)
|
||||||
|
: (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId);
|
||||||
|
if (activeQueryId == 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on this target.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* queryObject = FindQueryObjectLocked(activeQueryId);
|
||||||
|
if (!queryObject) {
|
||||||
|
activeQueryId = 0; // should not happen; keep state consistent
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isTransformFeedbackQuery) {
|
||||||
|
if (queryObject->backendHandle) {
|
||||||
|
if (const auto endXfbPrimitivesQuery = MG_Backend::gBackendFunctionsTable.GL.EndXfbPrimitivesQuery) {
|
||||||
|
endXfbPrimitivesQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
// Result comes from the GPU query at read time.
|
||||||
|
} else {
|
||||||
|
queryObject->cachedResult =
|
||||||
|
MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter() - queryObject->counterSnapshot;
|
||||||
|
queryObject->resultCached = true;
|
||||||
|
}
|
||||||
|
queryObject->active = false;
|
||||||
|
queryObject->ended = true;
|
||||||
|
activeQueryId = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isOcclusionQuery) {
|
||||||
|
if (const auto endOcclusionQuery = MG_Backend::gBackendFunctionsTable.GL.EndOcclusionQuery;
|
||||||
|
endOcclusionQuery && queryObject->backendHandle) {
|
||||||
|
endOcclusionQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->active = false;
|
||||||
|
queryObject->ended = true;
|
||||||
|
activeQueryId = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EndTimeElapsedQueryLocked(queryObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryCounter(GLuint id, GLenum target) {
|
||||||
|
if (target != GL_TIMESTAMP) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "QueryCounter target must be GL_TIMESTAMP.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (id == 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query id 0 cannot be used.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
auto* queryObject = FindQueryObjectLocked(id);
|
||||||
|
if (!queryObject) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queryObject->active) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object is currently active.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queryObject->target != 0 && queryObject->target != target) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
|
"Query object was already used with a different target.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||||
|
queryObject->target = target;
|
||||||
|
const auto queryCounterTimestamp = MG_Backend::gBackendFunctionsTable.GL.QueryCounterTimestamp;
|
||||||
|
queryObject->backendHandle =
|
||||||
|
(!TimerQueryDisabled() && queryCounterTimestamp) ? queryCounterTimestamp() : nullptr;
|
||||||
|
queryObject->ended = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryiv(GLenum target, GLenum pname, GLint* params) {
|
||||||
|
if (!params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (pname) {
|
||||||
|
case GL_CURRENT_QUERY: {
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
switch (target) {
|
||||||
|
case GL_TIME_ELAPSED:
|
||||||
|
*params = static_cast<GLint>(g_activeTimeElapsedQueryId);
|
||||||
|
break;
|
||||||
|
case GL_SAMPLES_PASSED:
|
||||||
|
case GL_ANY_SAMPLES_PASSED:
|
||||||
|
case GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
|
||||||
|
*params = static_cast<GLint>(g_activeSamplesPassedQueryId);
|
||||||
|
break;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
||||||
|
*params = static_cast<GLint>(g_activePrimitivesWrittenQueryId);
|
||||||
|
break;
|
||||||
|
case GL_PRIMITIVES_GENERATED:
|
||||||
|
*params = static_cast<GLint>(g_activePrimitivesGeneratedQueryId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*params = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case GL_QUERY_COUNTER_BITS: {
|
||||||
|
// 64 bits are advertised only while the live backend can actually
|
||||||
|
// time: IsTimerQuerySupported is the dynamic truth (extension /
|
||||||
|
// entry points / timestamp valid bits at call time, not at table
|
||||||
|
// init), and the MOBILEGL_DISABLE_TIMERQUERY kill switch always
|
||||||
|
// wins.
|
||||||
|
if (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||||
|
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||||
|
const Bool occlusionSupported = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||||
|
*params = occlusionSupported ? (target == GL_SAMPLES_PASSED ? 32 : 1) : 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Bool timerTarget = target == GL_TIME_ELAPSED || target == GL_TIMESTAMP;
|
||||||
|
const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported;
|
||||||
|
const Bool supported =
|
||||||
|
timerTarget && !TimerQueryDisabled() && isTimerQuerySupported && isTimerQuerySupported();
|
||||||
|
*params = supported ? 64 : 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Unsupported query parameter.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryBufferObjectiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) {
|
||||||
|
GetQueryBufferObject<GLint>(id, buffer, pname, offset, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryBufferObjectuiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) {
|
||||||
|
GetQueryBufferObject<GLuint>(id, buffer, pname, offset, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) {
|
||||||
|
GetQueryBufferObject<GLint64>(id, buffer, pname, offset, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) {
|
||||||
|
GetQueryBufferObject<GLuint64>(id, buffer, pname, offset, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params) {
|
||||||
|
Uint64 value = 0;
|
||||||
|
Bool valueProduced = false;
|
||||||
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
constexpr Uint64 kMaxInt = static_cast<Uint64>(INT_MAX);
|
||||||
|
*params = value > kMaxInt ? INT_MAX : static_cast<GLint>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params) {
|
||||||
|
Uint64 value = 0;
|
||||||
|
Bool valueProduced = false;
|
||||||
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*params = static_cast<GLuint>(value & 0xFFFFFFFFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params) {
|
||||||
|
Uint64 value = 0;
|
||||||
|
Bool valueProduced = false;
|
||||||
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*params = static_cast<GLint64>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) {
|
||||||
|
Uint64 value = 0;
|
||||||
|
Bool valueProduced = false;
|
||||||
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value, &valueProduced) || !valueProduced || !params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*params = static_cast<GLuint64>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// The indexed query entry points differ from the plain ones only in the vertex
|
||||||
|
// stream they address (GL 4.6 core 4.2.1): index must be below GL_MAX_VERTEX_STREAMS
|
||||||
|
// for the two transform feedback targets and zero for every other target. With a
|
||||||
|
// single vertex stream both bounds are 1, so a valid call is always index 0 and
|
||||||
|
// forwards to the unindexed implementation.
|
||||||
|
Bool ValidateQueryStreamIndex(const char* function, GLenum target, GLuint index) {
|
||||||
|
const Bool perStreamTarget =
|
||||||
|
target == GL_PRIMITIVES_GENERATED || target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN;
|
||||||
|
GLint maxVertexStreams = 1;
|
||||||
|
if (perStreamTarget) {
|
||||||
|
GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams);
|
||||||
|
}
|
||||||
|
if (index < static_cast<GLuint>(std::max(maxVertexStreams, 1))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
RecordQueryError(ErrorCode::InvalidValue, function,
|
||||||
|
perStreamTarget ? "index is not less than GL_MAX_VERTEX_STREAMS."
|
||||||
|
: "index must be zero for this query target.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id) {
|
||||||
|
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
||||||
|
BeginQuery(target, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndQueryIndexed(GLenum target, GLuint index) {
|
||||||
|
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
||||||
|
EndQuery(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params) {
|
||||||
|
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
||||||
|
GetQueryiv(target, pname, params);
|
||||||
|
}
|
||||||
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/GLImpl/Query/GL_Query.h
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
void GenQueries(GLsizei n, GLuint* ids);
|
||||||
|
void CreateQueries(GLenum target, GLsizei n, GLuint* ids);
|
||||||
|
void DeleteQueries(GLsizei n, const GLuint* ids);
|
||||||
|
GLboolean IsQuery(GLuint id);
|
||||||
|
void BeginQuery(GLenum target, GLuint id);
|
||||||
|
void EndQuery(GLenum target);
|
||||||
|
void GetQueryiv(GLenum target, GLenum pname, GLint* params);
|
||||||
|
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id);
|
||||||
|
void EndQueryIndexed(GLenum target, GLuint index);
|
||||||
|
void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params);
|
||||||
|
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params);
|
||||||
|
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params);
|
||||||
|
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params);
|
||||||
|
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params);
|
||||||
|
void GetQueryBufferObjectiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||||
|
void GetQueryBufferObjectuiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||||
|
void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||||
|
void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||||
|
void QueryCounter(GLuint id, GLenum target);
|
||||||
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "GL_RenderState.h"
|
#include "GL_RenderState.h"
|
||||||
|
#include <cmath>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
#include <MG_Util/Converters/GLToMG/RenderStateEnumConverter.h>
|
#include <MG_Util/Converters/GLToMG/RenderStateEnumConverter.h>
|
||||||
@@ -14,6 +15,32 @@
|
|||||||
#include <MG_Util/Converters/MGToStr/RenderStateEnumConverter.h>
|
#include <MG_Util/Converters/MGToStr/RenderStateEnumConverter.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
static Float ClampUnitFloat(GLfloat value) {
|
||||||
|
return std::clamp(static_cast<Float>(value), 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool ValidateIndexedBlendCapability(GLenum target, GLuint index, const char* functionName) {
|
||||||
|
if (target != GL_BLEND) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"Only GL_BLEND is supported for indexed capability state."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", functionName,
|
||||||
|
"Buffer index " + std::to_string(index) + " is out of range. Max supported is " +
|
||||||
|
std::to_string(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS - 1) + "."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static Bool TryConvertBlendEquation(GLenum mode, const char* functionName,
|
static Bool TryConvertBlendEquation(GLenum mode, const char* functionName,
|
||||||
::MobileGL::BlendEquation& outEquation) {
|
::MobileGL::BlendEquation& outEquation) {
|
||||||
outEquation = MG_Util::ConvertGLEnumToBlendEquation(mode);
|
outEquation = MG_Util::ConvertGLEnumToBlendEquation(mode);
|
||||||
@@ -27,6 +54,43 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Bool TryDecodeStencilFace(GLenum face, const char* functionName, Bool& applyFront, Bool& applyBack) {
|
||||||
|
switch (face) {
|
||||||
|
case GL_FRONT:
|
||||||
|
applyFront = true;
|
||||||
|
applyBack = false;
|
||||||
|
return true;
|
||||||
|
case GL_BACK:
|
||||||
|
applyFront = false;
|
||||||
|
applyBack = true;
|
||||||
|
return true;
|
||||||
|
case GL_FRONT_AND_BACK:
|
||||||
|
applyFront = true;
|
||||||
|
applyBack = true;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"Stencil face enum " + MG_Util::ConvertGLEnumToString(face) +
|
||||||
|
" is not supported."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool TryConvertStencilOperation(GLenum value, const char* functionName, const char* paramName,
|
||||||
|
StencilOperation& outOperation) {
|
||||||
|
outOperation = MG_Util::ConvertGLEnumToStencilOperation(value);
|
||||||
|
if (outOperation != StencilOperation::Unknown) return true;
|
||||||
|
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
String(paramName) + " enum " + MG_Util::ConvertGLEnumToString(value) +
|
||||||
|
" is not supported."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Viewport_State(GLint x, GLint y, GLsizei width, GLsizei height) {
|
void Viewport_State(GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||||
if (width < 0 || height < 0) {
|
if (width < 0 || height < 0) {
|
||||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidValue,
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidValue,
|
||||||
@@ -39,27 +103,74 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StencilOpSeparate_State(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) {
|
void StencilOpSeparate_State(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) {
|
||||||
// TODO: implement
|
Bool applyFront = false;
|
||||||
|
Bool applyBack = false;
|
||||||
|
if (!TryDecodeStencilFace(face, "StencilOpSeparate_State", applyFront, applyBack)) return;
|
||||||
|
|
||||||
|
StencilOperation failOp = StencilOperation::Unknown;
|
||||||
|
StencilOperation depthFailOp = StencilOperation::Unknown;
|
||||||
|
StencilOperation depthPassOp = StencilOperation::Unknown;
|
||||||
|
if (!TryConvertStencilOperation(sfail, "StencilOpSeparate_State", "sfail", failOp) ||
|
||||||
|
!TryConvertStencilOperation(dpfail, "StencilOpSeparate_State", "dpfail", depthFailOp) ||
|
||||||
|
!TryConvertStencilOperation(dppass, "StencilOpSeparate_State", "dppass", depthPassOp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyFront) {
|
||||||
|
MG_State::pGLContext->SetStencilOp(StencilFace::Front, failOp, depthFailOp, depthPassOp);
|
||||||
|
}
|
||||||
|
if (applyBack) {
|
||||||
|
MG_State::pGLContext->SetStencilOp(StencilFace::Back, failOp, depthFailOp, depthPassOp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StencilOp_State(GLenum fail, GLenum zfail, GLenum zpass) {
|
void StencilOp_State(GLenum fail, GLenum zfail, GLenum zpass) {
|
||||||
// TODO: implement
|
StencilOpSeparate_State(GL_FRONT_AND_BACK, fail, zfail, zpass);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StencilMaskSeparate_State(GLenum face, GLuint mask) {
|
void StencilMaskSeparate_State(GLenum face, GLuint mask) {
|
||||||
// TODO: implement
|
Bool applyFront = false;
|
||||||
|
Bool applyBack = false;
|
||||||
|
if (!TryDecodeStencilFace(face, "StencilMaskSeparate_State", applyFront, applyBack)) return;
|
||||||
|
|
||||||
|
if (applyFront) {
|
||||||
|
MG_State::pGLContext->SetStencilMask(StencilFace::Front, mask);
|
||||||
|
}
|
||||||
|
if (applyBack) {
|
||||||
|
MG_State::pGLContext->SetStencilMask(StencilFace::Back, mask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StencilMask_State(GLuint mask) {
|
void StencilMask_State(GLuint mask) {
|
||||||
// TODO: implement
|
StencilMaskSeparate_State(GL_FRONT_AND_BACK, mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StencilFuncSeparate_State(GLenum face, GLenum func, GLint ref, GLuint mask) {
|
void StencilFuncSeparate_State(GLenum face, GLenum func, GLint ref, GLuint mask) {
|
||||||
// TODO: implement
|
Bool applyFront = false;
|
||||||
|
Bool applyBack = false;
|
||||||
|
if (!TryDecodeStencilFace(face, "StencilFuncSeparate_State", applyFront, applyBack)) return;
|
||||||
|
|
||||||
|
DepthTestFunc depthFunc = MG_Util::ConvertGLEnumToDepthTestFunc(func);
|
||||||
|
if (depthFunc == DepthTestFunc::Unknown) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "StencilFuncSeparate_State",
|
||||||
|
"Stencil func enum " + MG_Util::ConvertGLEnumToString(func) +
|
||||||
|
" is not supported."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Int clampedRef = std::max(ref, 0);
|
||||||
|
if (applyFront) {
|
||||||
|
MG_State::pGLContext->SetStencilFunc(StencilFace::Front, depthFunc, clampedRef, mask);
|
||||||
|
}
|
||||||
|
if (applyBack) {
|
||||||
|
MG_State::pGLContext->SetStencilFunc(StencilFace::Back, depthFunc, clampedRef, mask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StencilFunc_State(GLenum func, GLint ref, GLuint mask) {
|
void StencilFunc_State(GLenum func, GLint ref, GLuint mask) {
|
||||||
// TODO: implement
|
StencilFuncSeparate_State(GL_FRONT_AND_BACK, func, ref, mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scissor_State(GLint x, GLint y, GLsizei width, GLsizei height) {
|
void Scissor_State(GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||||
@@ -74,27 +185,111 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SampleCoverage_State(GLfloat value, GLboolean invert) {
|
void SampleCoverage_State(GLfloat value, GLboolean invert) {
|
||||||
// TODO: implement
|
MG_State::pGLContext->SetSampleCoverage(std::clamp(static_cast<Float>(value), 0.0f, 1.0f), invert == GL_TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PolygonOffset_State(GLfloat factor, GLfloat units) {
|
void PolygonOffset_State(GLfloat factor, GLfloat units) {
|
||||||
// TODO: implement
|
MG_State::pGLContext->SetPolygonOffset(static_cast<Float>(factor), static_cast<Float>(units));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PolygonMode_State(GLenum face, GLenum mode) {
|
void PolygonMode_State(GLenum face, GLenum mode) {
|
||||||
// TODO: implement
|
// GL 3.3 core: separate front/back polygon modes were removed in 3.1, so the only legal
|
||||||
|
// face is GL_FRONT_AND_BACK. GL_FRONT / GL_BACK must be rejected (some desktop drivers
|
||||||
|
// leniently accept them, but that is non-conformant). Both errors are GL_INVALID_ENUM and
|
||||||
|
// leave state untouched.
|
||||||
|
if (face != GL_FRONT_AND_BACK) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"glPolygonMode face must be GL_FRONT_AND_BACK in the core profile; got " +
|
||||||
|
MG_Util::ConvertGLEnumToString(face) + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode != GL_POINT && mode != GL_LINE && mode != GL_FILL) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"glPolygonMode mode must be GL_POINT, GL_LINE, or GL_FILL; got " +
|
||||||
|
MG_Util::ConvertGLEnumToString(mode) + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Core sets both faces together; keep two slots so GL_POLYGON_MODE round-trips its two values.
|
||||||
|
MG_State::pGLContext->SetPolygonMode(mode, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PointSize_State(GLfloat size) {
|
void PointSize_State(GLfloat size) {
|
||||||
// TODO: implement
|
if (size <= 0.0f) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "PointSize_State",
|
||||||
|
"Point size must be greater than zero."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_State::pGLContext->SetPointSize(static_cast<Float>(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PointParameterf_State(GLenum pname, GLfloat param) {
|
// Single funnel for all four glPointParameter forms. The two GL 3.3 core pnames both carry one
|
||||||
// TODO: implement
|
// component, so the *v forms pass params[0], and the integer forms widen to float. The
|
||||||
|
// GL_POINT_SPRITE_COORD_ORIGIN value is an enum passed as a float (36001.0/36002.0 are exact).
|
||||||
|
void PointParameter_State(GLenum pname, GLfloat value) {
|
||||||
|
switch (pname) {
|
||||||
|
case GL_POINT_FADE_THRESHOLD_SIZE:
|
||||||
|
if (value < 0.0f) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"GL_POINT_FADE_THRESHOLD_SIZE must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetPointFadeThresholdSize(value);
|
||||||
|
return;
|
||||||
|
case GL_POINT_SPRITE_COORD_ORIGIN: {
|
||||||
|
const GLenum origin = static_cast<GLenum>(std::lround(value));
|
||||||
|
if (origin != GL_LOWER_LEFT && origin != GL_UPPER_LEFT) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"GL_POINT_SPRITE_COORD_ORIGIN must be GL_LOWER_LEFT or "
|
||||||
|
"GL_UPPER_LEFT."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetPointSpriteCoordOrigin(origin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Unsupported point parameter pname: " + std::to_string(pname)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PointParameterf_State(GLenum pname, GLfloat param) { PointParameter_State(pname, param); }
|
||||||
|
|
||||||
void PointParameteri_State(GLenum pname, GLint param) {
|
void PointParameteri_State(GLenum pname, GLint param) {
|
||||||
// TODO: implement
|
PointParameter_State(pname, static_cast<GLfloat>(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointParameterfv_State(GLenum pname, const GLfloat* params) {
|
||||||
|
if (!params) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "params pointer cannot be null."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PointParameter_State(pname, params[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointParameteriv_State(GLenum pname, const GLint* params) {
|
||||||
|
if (!params) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "params pointer cannot be null."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PointParameter_State(pname, static_cast<GLfloat>(params[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PixelStorei_State(GLenum pname, GLint param) {
|
void PixelStorei_State(GLenum pname, GLint param) {
|
||||||
@@ -114,14 +309,36 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LogicOp_State(GLenum opcode) {
|
void LogicOp_State(GLenum opcode) {
|
||||||
// TODO: implement
|
LogicOperation logicOp = MG_Util::ConvertGLEnumToLogicOperation(opcode);
|
||||||
|
if (logicOp == LogicOperation::Unknown) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "LogicOp_State",
|
||||||
|
"Logic op enum " + MG_Util::ConvertGLEnumToString(opcode) +
|
||||||
|
" is not supported."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_State::pGLContext->SetLogicOp(logicOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LineWidth_State(GLfloat width) {
|
void LineWidth_State(GLfloat width) {
|
||||||
// TODO: implement
|
if (width <= 0.0f) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "LineWidth_State",
|
||||||
|
"Line width must be greater than zero."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_State::pGLContext->SetLineWidth(static_cast<Float>(width));
|
||||||
}
|
}
|
||||||
|
|
||||||
GLboolean IsEnabledi_State(GLenum target, GLuint index) {
|
GLboolean IsEnabledi_State(GLenum target, GLuint index) {
|
||||||
|
if (!ValidateIndexedBlendCapability(target, index, "IsEnabledi_State")) {
|
||||||
|
return GL_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
CapabilityInput capInput = MG_Util::ConvertGLEnumToCapabilityInput(target);
|
CapabilityInput capInput = MG_Util::ConvertGLEnumToCapabilityInput(target);
|
||||||
if (capInput == CapabilityInput::Unknown) {
|
if (capInput == CapabilityInput::Unknown) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -135,6 +352,37 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return MG_State::pGLContext->IsCapabilityEnabledIndexed(capInput, index) ? GL_TRUE : GL_FALSE;
|
return MG_State::pGLContext->IsCapabilityEnabledIndexed(capInput, index) ? GL_TRUE : GL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetBooleani_v_State(GLenum target, GLuint index, GLboolean* data) {
|
||||||
|
if (!data) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBooleani_v_State",
|
||||||
|
"data pointer cannot be null."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == GL_COLOR_WRITEMASK) {
|
||||||
|
// Indexed per-draw-buffer color writemask writes 4 booleans (R,G,B,A) for draw buffer
|
||||||
|
// `index`. The non-indexed glGetBooleanv(GL_COLOR_WRITEMASK) reports draw buffer 0.
|
||||||
|
if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBooleani_v_State",
|
||||||
|
"Color writemask draw buffer index " + std::to_string(index) +
|
||||||
|
" is out of range."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const BoolVec4 mask = MG_State::pGLContext->GetColorMaskIndexed(index);
|
||||||
|
data[0] = mask.x() ? GL_TRUE : GL_FALSE;
|
||||||
|
data[1] = mask.y() ? GL_TRUE : GL_FALSE;
|
||||||
|
data[2] = mask.z() ? GL_TRUE : GL_FALSE;
|
||||||
|
data[3] = mask.w() ? GL_TRUE : GL_FALSE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*data = IsEnabledi_State(target, index);
|
||||||
|
}
|
||||||
|
|
||||||
GLboolean IsEnabled_State(GLenum cap) {
|
GLboolean IsEnabled_State(GLenum cap) {
|
||||||
CapabilityInput capInput = MG_Util::ConvertGLEnumToCapabilityInput(cap);
|
CapabilityInput capInput = MG_Util::ConvertGLEnumToCapabilityInput(cap);
|
||||||
if (capInput == CapabilityInput::Unknown) {
|
if (capInput == CapabilityInput::Unknown) {
|
||||||
@@ -150,7 +398,27 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Hint_State(GLenum target, GLenum mode) {
|
void Hint_State(GLenum target, GLenum mode) {
|
||||||
// TODO: implement
|
switch (target) {
|
||||||
|
case GL_LINE_SMOOTH_HINT:
|
||||||
|
case GL_POLYGON_SMOOTH_HINT:
|
||||||
|
case GL_TEXTURE_COMPRESSION_HINT:
|
||||||
|
case GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Unsupported hint target: " + std::to_string(target)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode != GL_FASTEST && mode != GL_NICEST && mode != GL_DONT_CARE) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Hint mode must be GL_FASTEST, GL_NICEST or GL_DONT_CARE."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetHint(target, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrontFace_State(GLenum mode) {
|
void FrontFace_State(GLenum mode) {
|
||||||
@@ -184,6 +452,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Enable_State(GLenum cap) {
|
void Enable_State(GLenum cap) {
|
||||||
|
switch (cap) {
|
||||||
|
case GL_TEXTURE_1D:
|
||||||
|
case GL_TEXTURE_2D:
|
||||||
|
case GL_TEXTURE_3D:
|
||||||
|
case GL_TEXTURE_CUBE_MAP:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
CapabilityInput capInput = MG_Util::ConvertGLEnumToCapabilityInput(cap);
|
CapabilityInput capInput = MG_Util::ConvertGLEnumToCapabilityInput(cap);
|
||||||
if (capInput == CapabilityInput::Unknown) {
|
if (capInput == CapabilityInput::Unknown) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -198,6 +476,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Disable_State(GLenum cap) {
|
void Disable_State(GLenum cap) {
|
||||||
|
switch (cap) {
|
||||||
|
case GL_TEXTURE_1D:
|
||||||
|
case GL_TEXTURE_2D:
|
||||||
|
case GL_TEXTURE_3D:
|
||||||
|
case GL_TEXTURE_CUBE_MAP:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
CapabilityInput capInput = MG_Util::ConvertGLEnumToCapabilityInput(cap);
|
CapabilityInput capInput = MG_Util::ConvertGLEnumToCapabilityInput(cap);
|
||||||
if (capInput == CapabilityInput::Unknown) {
|
if (capInput == CapabilityInput::Unknown) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -212,7 +500,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DepthRange_State(GLclampd near_val, GLclampd far_val) {
|
void DepthRange_State(GLclampd near_val, GLclampd far_val) {
|
||||||
// TODO: implement
|
MG_State::pGLContext->SetDepthRange(
|
||||||
|
FloatVec2(ClampUnitFloat(static_cast<GLfloat>(near_val)), ClampUnitFloat(static_cast<GLfloat>(far_val))));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DepthMask_State(GLboolean flag) {
|
void DepthMask_State(GLboolean flag) {
|
||||||
@@ -249,12 +538,59 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ColorMask_State(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
|
void ColorMask_State(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
|
||||||
|
// glColorMask broadcasts to every draw buffer. GLboolean coercion: any nonzero value enables
|
||||||
|
// the component; only exactly GL_FALSE disables it.
|
||||||
MG_State::pGLContext->SetColorMask(
|
MG_State::pGLContext->SetColorMask(
|
||||||
BoolVec4(red == GL_TRUE, green == GL_TRUE, blue == GL_TRUE, alpha == GL_TRUE));
|
BoolVec4(red != GL_FALSE, green != GL_FALSE, blue != GL_FALSE, alpha != GL_FALSE));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorMaski_State(GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
|
||||||
|
// Indexed color writemask for the single draw buffer `buf`. buf is a GLuint index, never an
|
||||||
|
// enum, so the only error is GL_INVALID_VALUE when it is out of range (mirrors the indexed
|
||||||
|
// blend entry points, which bound against MAX_DRAW_BUFFERS).
|
||||||
|
if (buf >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", __func__,
|
||||||
|
"glColorMaski buffer index " + std::to_string(buf) + " is out of range. Max supported is " +
|
||||||
|
std::to_string(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS - 1) + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetColorMaskIndexed(
|
||||||
|
buf, BoolVec4(red != GL_FALSE, green != GL_FALSE, blue != GL_FALSE, alpha != GL_FALSE));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrimitiveRestartIndex_State(GLuint index) {
|
||||||
|
// glPrimitiveRestartIndex accepts any GLuint and generates no error.
|
||||||
|
MG_State::pGLContext->SetPrimitiveRestartIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClampColor_State(GLenum target, GLenum clamp) {
|
void ClampColor_State(GLenum target, GLenum clamp) {
|
||||||
// TODO: implement
|
// GL 3.3 core: the only legal target is GL_CLAMP_READ_COLOR. The compatibility-only
|
||||||
|
// GL_CLAMP_VERTEX_COLOR / GL_CLAMP_FRAGMENT_COLOR were removed from the core profile and
|
||||||
|
// must be rejected.
|
||||||
|
if (target != GL_CLAMP_READ_COLOR) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"glClampColor target must be GL_CLAMP_READ_COLOR in the core profile; "
|
||||||
|
"got " +
|
||||||
|
MG_Util::ConvertGLEnumToString(target) + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// clamp must be one of GL_TRUE, GL_FALSE, or GL_FIXED_ONLY. NOTE: the Khronos man page's
|
||||||
|
// Errors section wrongly omits GL_FIXED_ONLY, but the spec lists it as legal AND it is the
|
||||||
|
// default value, so it must be accepted here.
|
||||||
|
if (clamp != GL_TRUE && clamp != GL_FALSE && clamp != GL_FIXED_ONLY) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"glClampColor clamp must be GL_TRUE, GL_FALSE, or GL_FIXED_ONLY; got " +
|
||||||
|
MG_Util::ConvertGLEnumToString(clamp) + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetClampReadColor(clamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlendFuncSeparate_State(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha) {
|
void BlendFuncSeparate_State(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha) {
|
||||||
@@ -304,7 +640,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlendColor_State(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
void BlendColor_State(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
||||||
// TODO: implement
|
MG_State::pGLContext->SetBlendColor(
|
||||||
|
FloatVec4(ClampUnitFloat(red), ClampUnitFloat(green), ClampUnitFloat(blue), ClampUnitFloat(alpha)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearStencil_State(GLint s) {
|
void ClearStencil_State(GLint s) {
|
||||||
@@ -312,7 +649,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ClearDepth_State(GLclampd depth) {
|
void ClearDepth_State(GLclampd depth) {
|
||||||
MG_State::pGLContext->SetClearDepth(static_cast<Float>(depth));
|
// GL 3.3 §4.2.3: the clear depth is clamped to [0,1] at specification time (Vulkan clear
|
||||||
|
// values additionally require it: VUID-VkClearDepthStencilValue-depth-00022).
|
||||||
|
MG_State::pGLContext->SetClearDepth(ClampUnitFloat(static_cast<Float>(depth)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearColor_State(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
void ClearColor_State(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
||||||
@@ -374,6 +713,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Disablei_State(GLenum target, GLuint index) {
|
void Disablei_State(GLenum target, GLuint index) {
|
||||||
|
if (!ValidateIndexedBlendCapability(target, index, "Disablei_State")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto capInput = MG_Util::ConvertGLEnumToCapabilityInput(target);
|
auto capInput = MG_Util::ConvertGLEnumToCapabilityInput(target);
|
||||||
if (capInput == CapabilityInput::Unknown) {
|
if (capInput == CapabilityInput::Unknown) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -388,6 +731,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Enablei_State(GLenum target, GLuint index) {
|
void Enablei_State(GLenum target, GLuint index) {
|
||||||
|
if (!ValidateIndexedBlendCapability(target, index, "Enablei_State")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto capInput = MG_Util::ConvertGLEnumToCapabilityInput(target);
|
auto capInput = MG_Util::ConvertGLEnumToCapabilityInput(target);
|
||||||
if (capInput == CapabilityInput::Unknown) {
|
if (capInput == CapabilityInput::Unknown) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -418,6 +765,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BlendFuncSeparatei_State(buf, srcRGB, dstRGB, srcAlpha, dstAlpha);
|
BlendFuncSeparatei_State(buf, srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetBooleani_v(GLenum target, GLuint index, GLboolean* data) {
|
||||||
|
GetBooleani_v_State(target, index, data);
|
||||||
|
}
|
||||||
|
|
||||||
void Disablei(GLenum target, GLuint index) {
|
void Disablei(GLenum target, GLuint index) {
|
||||||
Disablei_State(target, index);
|
Disablei_State(target, index);
|
||||||
}
|
}
|
||||||
@@ -486,10 +837,36 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
PointParameteri_State(pname, param);
|
PointParameteri_State(pname, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PointParameterfv(GLenum pname, const GLfloat* params) {
|
||||||
|
PointParameterfv_State(pname, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointParameteriv(GLenum pname, const GLint* params) {
|
||||||
|
PointParameteriv_State(pname, params);
|
||||||
|
}
|
||||||
|
|
||||||
void PixelStorei(GLenum pname, GLint param) {
|
void PixelStorei(GLenum pname, GLint param) {
|
||||||
PixelStorei_State(pname, param);
|
PixelStorei_State(pname, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PixelStoref(GLenum pname, GLfloat param) {
|
||||||
|
// Boolean pixel-store pnames convert by a zero-test (0.4 -> TRUE); integer pnames round to
|
||||||
|
// nearest. Branch before converting so a fractional value cannot round a true flag to false.
|
||||||
|
GLint intParam;
|
||||||
|
switch (pname) {
|
||||||
|
case GL_PACK_SWAP_BYTES:
|
||||||
|
case GL_UNPACK_SWAP_BYTES:
|
||||||
|
case GL_PACK_LSB_FIRST:
|
||||||
|
case GL_UNPACK_LSB_FIRST:
|
||||||
|
intParam = (param != 0.0f) ? 1 : 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
intParam = static_cast<GLint>(std::lround(param));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
PixelStorei_State(pname, intParam);
|
||||||
|
}
|
||||||
|
|
||||||
void LogicOp(GLenum opcode) {
|
void LogicOp(GLenum opcode) {
|
||||||
LogicOp_State(opcode);
|
LogicOp_State(opcode);
|
||||||
}
|
}
|
||||||
@@ -546,10 +923,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
ColorMask_State(red, green, blue, alpha);
|
ColorMask_State(red, green, blue, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ColorMaski(GLuint index, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {
|
||||||
|
ColorMaski_State(index, red, green, blue, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
void ClampColor(GLenum target, GLenum clamp) {
|
void ClampColor(GLenum target, GLenum clamp) {
|
||||||
ClampColor_State(target, clamp);
|
ClampColor_State(target, clamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrimitiveRestartIndex(GLuint index) {
|
||||||
|
PrimitiveRestartIndex_State(index);
|
||||||
|
}
|
||||||
|
|
||||||
void BlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha) {
|
void BlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha) {
|
||||||
BlendFuncSeparate_State(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha);
|
BlendFuncSeparate_State(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void BlendEquationSeparatei(GLuint buf, GLenum modeRGB, GLenum modeAlpha);
|
void BlendEquationSeparatei(GLuint buf, GLenum modeRGB, GLenum modeAlpha);
|
||||||
void BlendFunci(GLuint buf, GLenum src, GLenum dst);
|
void BlendFunci(GLuint buf, GLenum src, GLenum dst);
|
||||||
void BlendFuncSeparatei(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
|
void BlendFuncSeparatei(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
|
||||||
|
void GetBooleani_v(GLenum target, GLuint index, GLboolean* data);
|
||||||
void Disablei(GLenum target, GLuint index);
|
void Disablei(GLenum target, GLuint index);
|
||||||
void Enablei(GLenum target, GLuint index);
|
void Enablei(GLenum target, GLuint index);
|
||||||
void BlendFunc(GLenum sfactor, GLenum dfactor);
|
void BlendFunc(GLenum sfactor, GLenum dfactor);
|
||||||
@@ -32,7 +33,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void PointSize(GLfloat size);
|
void PointSize(GLfloat size);
|
||||||
void PointParameterf(GLenum pname, GLfloat param);
|
void PointParameterf(GLenum pname, GLfloat param);
|
||||||
void PointParameteri(GLenum pname, GLint param);
|
void PointParameteri(GLenum pname, GLint param);
|
||||||
|
void PointParameterfv(GLenum pname, const GLfloat* params);
|
||||||
|
void PointParameteriv(GLenum pname, const GLint* params);
|
||||||
void PixelStorei(GLenum pname, GLint param);
|
void PixelStorei(GLenum pname, GLint param);
|
||||||
|
void PixelStoref(GLenum pname, GLfloat param);
|
||||||
void LogicOp(GLenum opcode);
|
void LogicOp(GLenum opcode);
|
||||||
void LineWidth(GLfloat width);
|
void LineWidth(GLfloat width);
|
||||||
GLboolean IsEnabledi(GLenum target, GLuint index);
|
GLboolean IsEnabledi(GLenum target, GLuint index);
|
||||||
@@ -47,7 +51,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DepthFunc(GLenum func);
|
void DepthFunc(GLenum func);
|
||||||
void CullFace(GLenum mode);
|
void CullFace(GLenum mode);
|
||||||
void ColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
|
void ColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
|
||||||
|
void ColorMaski(GLuint index, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
|
||||||
void ClampColor(GLenum target, GLenum clamp);
|
void ClampColor(GLenum target, GLenum clamp);
|
||||||
|
void PrimitiveRestartIndex(GLuint index);
|
||||||
void BlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
|
void BlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
|
||||||
void BlendEquation(GLenum mode);
|
void BlendEquation(GLenum mode);
|
||||||
void BlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha);
|
void BlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha);
|
||||||
|
|||||||
@@ -13,7 +13,50 @@
|
|||||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
void SetSamplerParam_State(GLuint sampler, GLenum pname, const void* param, bool isFloat, bool isInteger) {
|
namespace {
|
||||||
|
Float ReadSamplerScalar(const void* param, Bool isFloat, Bool isUnsignedInteger) {
|
||||||
|
if (isFloat) return *(const GLfloat*)param;
|
||||||
|
if (isUnsignedInteger) return static_cast<Float>(*(const GLuint*)param);
|
||||||
|
return static_cast<Float>(*(const GLint*)param);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ValidateSamplerParameterValue(GLenum pname, const void* param, Bool isFloat, Bool isUnsignedInteger) {
|
||||||
|
if (param == nullptr) return false;
|
||||||
|
|
||||||
|
switch (pname) {
|
||||||
|
case GL_TEXTURE_MIN_LOD:
|
||||||
|
case GL_TEXTURE_MAX_LOD:
|
||||||
|
case GL_TEXTURE_LOD_BIAS:
|
||||||
|
return true;
|
||||||
|
// Four components, and GL puts no range on them - a border colour outside [0,1] is
|
||||||
|
// clamped when a fixed-point format is sampled, not rejected here. The scalar readers
|
||||||
|
// below would look at one component and invent an error.
|
||||||
|
case GL_TEXTURE_BORDER_COLOR:
|
||||||
|
return true;
|
||||||
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||||
|
if (ReadSamplerScalar(param, isFloat, isUnsignedInteger) >= 1.0f) return true;
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "SetSamplerParam_State",
|
||||||
|
"GL_TEXTURE_MAX_ANISOTROPY_EXT must be at least 1.0."));
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFloat) {
|
||||||
|
return SamplerImpl::ValidateSamplerFloatParam(pname, *(const GLfloat*)param);
|
||||||
|
}
|
||||||
|
if (isUnsignedInteger) {
|
||||||
|
return SamplerImpl::ValidateSamplerIntParam(pname, static_cast<GLint>(*(const GLuint*)param));
|
||||||
|
}
|
||||||
|
return SamplerImpl::ValidateSamplerIntParam(pname, *(const GLint*)param);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void SetSamplerParam_State(GLuint sampler, GLenum pname, const void* param, bool isFloat,
|
||||||
|
bool isUnsignedInteger) {
|
||||||
|
if (param == nullptr) return;
|
||||||
if (!SamplerImpl::ValidateSamplerName(sampler)) return;
|
if (!SamplerImpl::ValidateSamplerName(sampler)) return;
|
||||||
|
|
||||||
Bool doesSamplerObjectCreated = MG_State::pGLContext->ValidateSamplerObject(sampler);
|
Bool doesSamplerObjectCreated = MG_State::pGLContext->ValidateSamplerObject(sampler);
|
||||||
@@ -23,6 +66,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
auto& samplerObj = MG_State::pGLContext->GetSamplerObject(sampler);
|
auto& samplerObj = MG_State::pGLContext->GetSamplerObject(sampler);
|
||||||
if (!SamplerImpl::ValidateSamplerObject(sampler)) return;
|
if (!SamplerImpl::ValidateSamplerObject(sampler)) return;
|
||||||
|
if (!ValidateSamplerParameterValue(pname, param, isFloat, isUnsignedInteger)) return;
|
||||||
|
|
||||||
using namespace MG_Util;
|
using namespace MG_Util;
|
||||||
switch (pname) {
|
switch (pname) {
|
||||||
@@ -51,12 +95,29 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_LOD_BIAS:
|
case GL_TEXTURE_LOD_BIAS:
|
||||||
samplerObj->SetLodBias(*(const GLfloat*)param);
|
samplerObj->SetLodBias(*(const GLfloat*)param);
|
||||||
break;
|
break;
|
||||||
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||||
|
samplerObj->SetMaxAnisotropy(ReadSamplerScalar(param, isFloat, isUnsignedInteger));
|
||||||
|
break;
|
||||||
case GL_TEXTURE_COMPARE_MODE:
|
case GL_TEXTURE_COMPARE_MODE:
|
||||||
samplerObj->SetCompareMode(MG_Util::ConvertGLEnumToSamplerCompareMode(*(const GLint*)param));
|
samplerObj->SetCompareMode(MG_Util::ConvertGLEnumToSamplerCompareMode(*(const GLint*)param));
|
||||||
break;
|
break;
|
||||||
case GL_TEXTURE_COMPARE_FUNC:
|
case GL_TEXTURE_COMPARE_FUNC:
|
||||||
samplerObj->SetSamplerCompareFunc(MG_Util::ConvertGLEnumToSamplerCompareFunc(*(const GLint*)param));
|
samplerObj->SetSamplerCompareFunc(MG_Util::ConvertGLEnumToSamplerCompareFunc(*(const GLint*)param));
|
||||||
break;
|
break;
|
||||||
|
case GL_TEXTURE_BORDER_COLOR:
|
||||||
|
// The only four-component sampler parameter: the caller's form decides which
|
||||||
|
// representation is authoritative, and SamplerObject keeps the other two in step.
|
||||||
|
if (isFloat) {
|
||||||
|
const auto* values = (const GLfloat*)param;
|
||||||
|
samplerObj->SetBorderColor(FloatVec4(values[0], values[1], values[2], values[3]));
|
||||||
|
} else if (isUnsignedInteger) {
|
||||||
|
const auto* values = (const GLuint*)param;
|
||||||
|
samplerObj->SetBorderColorUI(UintVec4(values[0], values[1], values[2], values[3]));
|
||||||
|
} else {
|
||||||
|
const auto* values = (const GLint*)param;
|
||||||
|
samplerObj->SetBorderColorI(IntVec4(values[0], values[1], values[2], values[3]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "SetSamplerParam_State",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "SetSamplerParam_State",
|
||||||
@@ -64,7 +125,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetSamplerParam_State(GLuint sampler, GLenum pname, void* params, bool isFloat, bool isInteger) {
|
void GetSamplerParam_State(GLuint sampler, GLenum pname, void* params, bool isFloat,
|
||||||
|
bool isUnsignedInteger) {
|
||||||
|
if (params == nullptr) return;
|
||||||
if (!SamplerImpl::ValidateSamplerName(sampler)) return;
|
if (!SamplerImpl::ValidateSamplerName(sampler)) return;
|
||||||
|
|
||||||
Bool doesSamplerObjectCreated = MG_State::pGLContext->ValidateSamplerObject(sampler);
|
Bool doesSamplerObjectCreated = MG_State::pGLContext->ValidateSamplerObject(sampler);
|
||||||
@@ -103,12 +166,46 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_LOD_BIAS:
|
case GL_TEXTURE_LOD_BIAS:
|
||||||
*(GLfloat*)params = samplerObj->GetLodBias();
|
*(GLfloat*)params = samplerObj->GetLodBias();
|
||||||
break;
|
break;
|
||||||
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||||
|
if (isFloat) {
|
||||||
|
*(GLfloat*)params = samplerObj->GetMaxAnisotropy();
|
||||||
|
} else if (isUnsignedInteger) {
|
||||||
|
*(GLuint*)params = static_cast<GLuint>(samplerObj->GetMaxAnisotropy());
|
||||||
|
} else {
|
||||||
|
*(GLint*)params = static_cast<GLint>(samplerObj->GetMaxAnisotropy());
|
||||||
|
}
|
||||||
|
break;
|
||||||
case GL_TEXTURE_COMPARE_MODE:
|
case GL_TEXTURE_COMPARE_MODE:
|
||||||
*(GLuint*)params = MG_Util::ConvertSamplerCompareModeToGLEnum(samplerObj->GetCompareMode());
|
*(GLuint*)params = MG_Util::ConvertSamplerCompareModeToGLEnum(samplerObj->GetCompareMode());
|
||||||
break;
|
break;
|
||||||
case GL_TEXTURE_COMPARE_FUNC:
|
case GL_TEXTURE_COMPARE_FUNC:
|
||||||
*(GLuint*)params = MG_Util::ConvertSamplerCompareFuncToGLEnum(samplerObj->GetSamplerCompareFunc());
|
*(GLuint*)params = MG_Util::ConvertSamplerCompareFuncToGLEnum(samplerObj->GetSamplerCompareFunc());
|
||||||
break;
|
break;
|
||||||
|
case GL_TEXTURE_BORDER_COLOR: {
|
||||||
|
if (isFloat) {
|
||||||
|
const auto& color = samplerObj->GetBorderColor();
|
||||||
|
auto* out = (GLfloat*)params;
|
||||||
|
out[0] = color.x();
|
||||||
|
out[1] = color.y();
|
||||||
|
out[2] = color.z();
|
||||||
|
out[3] = color.w();
|
||||||
|
} else if (isUnsignedInteger) {
|
||||||
|
const auto& color = samplerObj->GetBorderColorUI();
|
||||||
|
auto* out = (GLuint*)params;
|
||||||
|
out[0] = color.x();
|
||||||
|
out[1] = color.y();
|
||||||
|
out[2] = color.z();
|
||||||
|
out[3] = color.w();
|
||||||
|
} else {
|
||||||
|
const auto& color = samplerObj->GetBorderColorI();
|
||||||
|
auto* out = (GLint*)params;
|
||||||
|
out[0] = color.x();
|
||||||
|
out[1] = color.y();
|
||||||
|
out[2] = color.z();
|
||||||
|
out[3] = color.w();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetSamplerParam_State",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetSamplerParam_State",
|
||||||
@@ -132,6 +229,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
static thread_local Vector<GLuint> names;
|
static thread_local Vector<GLuint> names;
|
||||||
MG_State::pGLContext->GenSamplerNames(count, names);
|
MG_State::pGLContext->GenSamplerNames(count, names);
|
||||||
Memcpy(samplers, names.data(), count * sizeof(GLuint));
|
Memcpy(samplers, names.data(), count * sizeof(GLuint));
|
||||||
|
// Unlike textures/buffers, glGenSamplers CREATES the sampler objects: each name
|
||||||
|
// is immediately a sampler (glIsSampler == GL_TRUE before any bind).
|
||||||
|
for (GLsizei i = 0; i < count; ++i) {
|
||||||
|
MG_State::pGLContext->CreateSamplerObject(names[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeleteSamplers_State(GLsizei count, const GLuint* samplers) {
|
void DeleteSamplers_State(GLsizei count, const GLuint* samplers) {
|
||||||
@@ -176,10 +278,20 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject((Int)unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject((Int)unit);
|
||||||
|
MG_State::pGLContext->NoteTextureUnitTouched((Int)unit);
|
||||||
if (sampler == 0) {
|
if (sampler == 0) {
|
||||||
textureUnit.SetSamplerObject(nullptr);
|
textureUnit.SetSamplerObject(nullptr);
|
||||||
} else {
|
} else {
|
||||||
if (!SamplerImpl::ValidateSamplerName(sampler)) return;
|
// GL 3.3 core 3.8.2: BindSampler on a name GenSamplers never returned - or one already
|
||||||
|
// deleted - is INVALID_OPERATION. SamplerParameter* raises INVALID_VALUE for the same
|
||||||
|
// name, which is why this cannot go through the shared SamplerImpl validator.
|
||||||
|
if (!MG_State::pGLContext->ValidateSamplerName(sampler)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "BindSampler_State",
|
||||||
|
std::format("Invalid sampler name {}", sampler)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
Bool doesSamplerObjectCreated = MG_State::pGLContext->ValidateSamplerObject(sampler);
|
Bool doesSamplerObjectCreated = MG_State::pGLContext->ValidateSamplerObject(sampler);
|
||||||
if (!doesSamplerObjectCreated) {
|
if (!doesSamplerObjectCreated) {
|
||||||
MG_State::pGLContext->CreateSamplerObject(sampler);
|
MG_State::pGLContext->CreateSamplerObject(sampler);
|
||||||
@@ -213,7 +325,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SamplerParameterIiv(GLuint sampler, GLenum pname, const GLint* param) {
|
void SamplerParameterIiv(GLuint sampler, GLenum pname, const GLint* param) {
|
||||||
SetSamplerParam_State(sampler, pname, param, false, true);
|
SetSamplerParam_State(sampler, pname, param, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SamplerParameteriv(GLuint sampler, GLenum pname, const GLint* param) {
|
void SamplerParameteriv(GLuint sampler, GLenum pname, const GLint* param) {
|
||||||
@@ -241,7 +353,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GetSamplerParameterIiv(GLuint sampler, GLenum pname, GLint* params) {
|
void GetSamplerParameterIiv(GLuint sampler, GLenum pname, GLint* params) {
|
||||||
GetSamplerParam_State(sampler, pname, params, false, true);
|
GetSamplerParam_State(sampler, pname, params, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetSamplerParameterfv(GLuint sampler, GLenum pname, GLfloat* params) {
|
void GetSamplerParameterfv(GLuint sampler, GLenum pname, GLfloat* params) {
|
||||||
|
|||||||
@@ -99,6 +99,16 @@ namespace MobileGL::MG_Impl::GLImpl::SamplerImpl {
|
|||||||
case GL_TEXTURE_LOD_BIAS:
|
case GL_TEXTURE_LOD_BIAS:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||||
|
if (!(param >= 1.0f)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateSamplerFloatParam",
|
||||||
|
"GL_TEXTURE_MAX_ANISOTROPY_EXT must be at least 1.0."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
case GL_TEXTURE_BORDER_COLOR:
|
case GL_TEXTURE_BORDER_COLOR:
|
||||||
if (param < 0.0f || param > 1.0f) {
|
if (param < 0.0f || param > 1.0f) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -125,6 +135,16 @@ namespace MobileGL::MG_Impl::GLImpl::SamplerImpl {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||||
|
if (param < 1) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateSamplerIntParam",
|
||||||
|
"GL_TEXTURE_MAX_ANISOTROPY_EXT must be at least 1."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ValidateSamplerParam(pname, static_cast<GLenum>(param));
|
return ValidateSamplerParam(pname, static_cast<GLenum>(param));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,115 +7,123 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "GL_Sync.h"
|
#include "GL_Sync.h"
|
||||||
|
#include <MG_Backend/BackendObjects.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
|
||||||
#include <MG_State/GLState/ErrorState/Error.h>
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
namespace {
|
namespace {
|
||||||
|
// Frontend sync object: wraps an optional backend fence handle. A null
|
||||||
|
// backend handle (backend has no fence support, or could not create a
|
||||||
|
// fence at call time) keeps the legacy always-signaled behavior.
|
||||||
struct SyncObject {
|
struct SyncObject {
|
||||||
GLenum Condition = GL_SYNC_GPU_COMMANDS_COMPLETE;
|
MG_Backend::BackendSyncHandle backendHandle = nullptr;
|
||||||
GLbitfield Flags = 0;
|
GLenum condition = GL_SYNC_GPU_COMMANDS_COMPLETE;
|
||||||
|
GLbitfield flags = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
UnorderedMap<GLsync, UniquePtr<SyncObject>> g_syncObjects;
|
// Sync calls may arrive from any thread (launchers migrate the context
|
||||||
|
// across JVM threads), so the live-object registry is mutex-guarded.
|
||||||
|
// Entries left at process shutdown are simply dropped; their backend
|
||||||
|
// handles die with the backend.
|
||||||
|
std::mutex g_syncObjectsMutex;
|
||||||
|
UnorderedMap<GLsync, SyncObject*> g_liveSyncObjects;
|
||||||
|
|
||||||
void RecordSyncError(const char* funcName, ErrorCode code, String message) {
|
SyncObject* FindSyncObject(GLsync sync) {
|
||||||
MG_State::pGLContext->RecordError(code,
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName, Move(message)));
|
const auto it = g_liveSyncObjects.find(sync);
|
||||||
}
|
return it != g_liveSyncObjects.end() ? it->second : nullptr;
|
||||||
|
|
||||||
SyncObject* GetSyncObject(GLsync sync, const char* funcName) {
|
|
||||||
auto it = g_syncObjects.find(sync);
|
|
||||||
if (sync == nullptr || it == g_syncObjects.end()) {
|
|
||||||
RecordSyncError(funcName, ErrorCode::InvalidValue, "Sync object is not valid.");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return it->second.get();
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
GLsync FenceSync(GLenum condition, GLbitfield flags) {
|
GLsync FenceSync(GLenum condition, GLbitfield flags) {
|
||||||
if (condition != GL_SYNC_GPU_COMMANDS_COMPLETE) {
|
auto* syncObject = new SyncObject;
|
||||||
RecordSyncError("FenceSync", ErrorCode::InvalidEnum, "Condition must be GL_SYNC_GPU_COMMANDS_COMPLETE.");
|
syncObject->condition = condition;
|
||||||
return nullptr;
|
syncObject->flags = flags;
|
||||||
|
if (const auto backendFenceSync = MG_Backend::gBackendFunctionsTable.GL.FenceSync) {
|
||||||
|
syncObject->backendHandle = backendFenceSync();
|
||||||
}
|
}
|
||||||
if (flags != 0) {
|
const GLsync handle = reinterpret_cast<GLsync>(syncObject);
|
||||||
RecordSyncError("FenceSync", ErrorCode::InvalidValue, "Flags must be zero.");
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
return nullptr;
|
g_liveSyncObjects[handle] = syncObject;
|
||||||
}
|
|
||||||
|
|
||||||
auto syncObject = MakeUnique<SyncObject>();
|
|
||||||
syncObject->Condition = condition;
|
|
||||||
syncObject->Flags = flags;
|
|
||||||
GLsync handle = reinterpret_cast<GLsync>(syncObject.get());
|
|
||||||
g_syncObjects[handle] = Move(syncObject);
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLboolean IsSync(GLsync sync) {
|
GLboolean IsSync(GLsync sync) {
|
||||||
return g_syncObjects.find(sync) != g_syncObjects.end() ? GL_TRUE : GL_FALSE;
|
return FindSyncObject(sync) != nullptr ? GL_TRUE : GL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||||
(void)timeout;
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
if ((flags & ~GL_SYNC_FLUSH_COMMANDS_BIT) != 0) {
|
if (!syncObject) {
|
||||||
RecordSyncError("ClientWaitSync", ErrorCode::InvalidValue,
|
|
||||||
"Flags can only contain GL_SYNC_FLUSH_COMMANDS_BIT.");
|
|
||||||
return GL_WAIT_FAILED;
|
return GL_WAIT_FAILED;
|
||||||
}
|
}
|
||||||
if (!GetSyncObject(sync, "ClientWaitSync")) return GL_WAIT_FAILED;
|
const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync;
|
||||||
return GL_ALREADY_SIGNALED;
|
if (!backendClientWaitSync || !syncObject->backendHandle) {
|
||||||
|
return GL_ALREADY_SIGNALED; // legacy always-signaled fallback
|
||||||
|
}
|
||||||
|
return backendClientWaitSync(syncObject->backendHandle, flags, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||||
if (flags != 0) {
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
RecordSyncError("WaitSync", ErrorCode::InvalidValue, "Flags must be zero.");
|
if (!syncObject) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (timeout != GL_TIMEOUT_IGNORED) {
|
const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync;
|
||||||
RecordSyncError("WaitSync", ErrorCode::InvalidValue, "Timeout must be GL_TIMEOUT_IGNORED.");
|
if (backendWaitSync && syncObject->backendHandle) {
|
||||||
return;
|
backendWaitSync(syncObject->backendHandle, flags, timeout);
|
||||||
}
|
}
|
||||||
(void)GetSyncObject(sync, "WaitSync");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeleteSync(GLsync sync) {
|
void DeleteSync(GLsync sync) {
|
||||||
if (sync == nullptr) return;
|
if (sync == nullptr) {
|
||||||
auto it = g_syncObjects.find(sync);
|
return; // glDeleteSync(0) is silently ignored
|
||||||
if (it == g_syncObjects.end()) {
|
|
||||||
RecordSyncError("DeleteSync", ErrorCode::InvalidValue, "Sync object is not valid.");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
g_syncObjects.erase(it);
|
SyncObject* syncObject = nullptr;
|
||||||
|
{
|
||||||
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
|
const auto it = g_liveSyncObjects.find(sync);
|
||||||
|
if (it == g_liveSyncObjects.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syncObject = it->second;
|
||||||
|
g_liveSyncObjects.erase(it);
|
||||||
|
}
|
||||||
|
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
||||||
|
if (backendDeleteSync && syncObject->backendHandle) {
|
||||||
|
backendDeleteSync(syncObject->backendHandle);
|
||||||
|
}
|
||||||
|
delete syncObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) {
|
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) {
|
||||||
if (bufSize < 0) {
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
RecordSyncError("GetSynciv", ErrorCode::InvalidValue, "bufSize must be non-negative.");
|
if (!syncObject) {
|
||||||
|
if (length) {
|
||||||
|
*length = 0;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* syncObject = GetSyncObject(sync, "GetSynciv");
|
|
||||||
if (!syncObject) return;
|
|
||||||
|
|
||||||
GLint value = 0;
|
GLint value = 0;
|
||||||
switch (pname) {
|
switch (pname) {
|
||||||
case GL_OBJECT_TYPE:
|
case GL_OBJECT_TYPE:
|
||||||
value = GL_SYNC_FENCE;
|
value = GL_SYNC_FENCE;
|
||||||
break;
|
break;
|
||||||
case GL_SYNC_STATUS:
|
case GL_SYNC_STATUS: {
|
||||||
value = GL_SIGNALED;
|
const auto backendGetSyncStatus = MG_Backend::gBackendFunctionsTable.GL.GetSyncStatus;
|
||||||
|
const Bool signaled = !backendGetSyncStatus || !syncObject->backendHandle ||
|
||||||
|
backendGetSyncStatus(syncObject->backendHandle);
|
||||||
|
value = signaled ? GL_SIGNALED : GL_UNSIGNALED;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case GL_SYNC_CONDITION:
|
case GL_SYNC_CONDITION:
|
||||||
value = static_cast<GLint>(syncObject->Condition);
|
value = static_cast<GLint>(syncObject->condition);
|
||||||
break;
|
break;
|
||||||
case GL_SYNC_FLAGS:
|
case GL_SYNC_FLAGS:
|
||||||
value = static_cast<GLint>(syncObject->Flags);
|
value = static_cast<GLint>(syncObject->flags);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
RecordSyncError("GetSynciv", ErrorCode::InvalidEnum, std::format("Invalid pname enum: 0x{:X}", pname));
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length) {
|
if (length) {
|
||||||
@@ -125,4 +133,33 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
values[0] = value;
|
values[0] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DestroyAllSyncObjects() {
|
||||||
|
// Detach the registry under the lock, release outside it. Entries the app
|
||||||
|
// already deleted were erased by DeleteSync, so nothing here double-frees;
|
||||||
|
// a DeleteSync racing this sweep finds an empty registry and returns. A
|
||||||
|
// thread still blocked inside ClientWaitSync/GetSynciv during teardown
|
||||||
|
// holds a raw SyncObject* these deletes invalidate - the same undefined
|
||||||
|
// race an app-driven DeleteSync already has.
|
||||||
|
UnorderedMap<GLsync, SyncObject*> orphans;
|
||||||
|
{
|
||||||
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
|
orphans.swap(g_liveSyncObjects);
|
||||||
|
}
|
||||||
|
if (orphans.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Both backends' DeleteSync only free the heap wrapper once their GL
|
||||||
|
// context/renderer is gone (generation/current-thread guards), so this is
|
||||||
|
// safe after the backend has released its EGL resources - but not after
|
||||||
|
// the function table itself is cleared.
|
||||||
|
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
||||||
|
for (const auto& [_, syncObject] : orphans) {
|
||||||
|
if (backendDeleteSync && syncObject->backendHandle) {
|
||||||
|
backendDeleteSync(syncObject->backendHandle);
|
||||||
|
}
|
||||||
|
delete syncObject;
|
||||||
|
}
|
||||||
|
MGLOG_D("DestroyAllSyncObjects: reclaimed %zu sync object(s) the app left undeleted", orphans.size());
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -16,4 +16,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);
|
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||||
void DeleteSync(GLsync sync);
|
void DeleteSync(GLsync sync);
|
||||||
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values);
|
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values);
|
||||||
|
// Destroys every still-registered sync object exactly as DeleteSync would.
|
||||||
|
// GL requires syncs to die with their context; called only from full library
|
||||||
|
// teardown (DestroyImpl), where no context survives on any thread, so the
|
||||||
|
// process-global registry can be drained wholesale. Must run while the
|
||||||
|
// backend function table is still populated: each backend handle has to be
|
||||||
|
// released by the backend that created it, never by a later re-initialized
|
||||||
|
// one.
|
||||||
|
void DestroyAllSyncObjects();
|
||||||
} // 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