mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 20:58:31 +09:00
Compare commits
518
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44cf9ab26a | ||
|
|
77366d51ed | ||
|
|
fc0688c223 | ||
|
|
626c7f26fd | ||
|
|
1d947d934b | ||
|
|
4203837648 | ||
|
|
f5cba4c2f1 | ||
|
|
c2a1db3fcb | ||
|
|
594916850f | ||
|
|
c718d6bad8 | ||
|
|
abfda60ff1 | ||
|
|
92cced9bcc | ||
|
|
1929a7c546 | ||
|
|
df7f5a369d | ||
|
|
0de9861da4 | ||
|
|
6b223e4d23 | ||
|
|
48568cdb89 | ||
|
|
8d83dedc0f | ||
|
|
a25d8ee0e7 | ||
|
|
c30bd0fabb | ||
|
|
c9fbf79d6a | ||
|
|
f39e8738da | ||
|
|
c6d22e6ece | ||
|
|
981f10e4da | ||
|
|
076cd0d19d | ||
|
|
5cd82f2002 | ||
|
|
d4922cb0fb | ||
|
|
870d882fef | ||
|
|
e2f873c95c | ||
|
|
efd7b47388 | ||
|
|
a08669df72 | ||
|
|
b8db509581 | ||
|
|
5d6b544021 | ||
|
|
346cd417ca | ||
|
|
f61675e9ce | ||
|
|
8026838563 | ||
|
|
5bd8fa8c4e | ||
|
|
37ef2cb600 | ||
|
|
5184901a5b | ||
|
|
4172959e49 | ||
|
|
eb5b4bca3e | ||
|
|
56db115a1a | ||
|
|
b14edd515a | ||
|
|
57030b5fb9 | ||
|
|
72532de780 | ||
|
|
25323bfb8e | ||
|
|
9ed5dbf483 | ||
|
|
3ca57068a8 | ||
|
|
075471cdbd | ||
|
|
62a0ad5639 | ||
|
|
8bf8f6f906 | ||
|
|
249c1ca574 | ||
|
|
6a843b3088 | ||
|
|
2e94314b78 | ||
|
|
c59c15f66a | ||
|
|
4d1613ba55 | ||
|
|
254cf1dc21 | ||
|
|
607e84deed | ||
|
|
df0e9fca71 | ||
|
|
b831dae8d5 | ||
|
|
0005a50517 | ||
|
|
9f302373d6 | ||
|
|
f896c7396f | ||
|
|
6ca48e40fe | ||
|
|
1cefb9780b | ||
|
|
a1a8a18575 | ||
|
|
bae222227a | ||
|
|
37a050b106 | ||
|
|
12a67f596c | ||
|
|
164bfd810b | ||
|
|
274c234aff | ||
|
|
b8dc4a6004 | ||
|
|
20e1b417cc | ||
|
|
3e8c8b756f | ||
|
|
4dbdbd3bcd | ||
|
|
763d4c3207 | ||
|
|
5eeba29579 | ||
|
|
9351d66dbc | ||
|
|
dae8a40af1 | ||
|
|
e9bd520d9b | ||
|
|
8b78379f6f | ||
|
|
1dc217b32c | ||
|
|
535e9f8b15 | ||
|
|
35626da5c4 | ||
|
|
b9844ed7c1 | ||
|
|
8496e7c7eb | ||
|
|
f0ed5c1b8e | ||
|
|
c247ad5807 | ||
|
|
b604188849 | ||
|
|
7514587b5a | ||
|
|
cf8f928db8 | ||
|
|
5b38f61961 | ||
|
|
176d130f09 | ||
|
|
8bdab8005b | ||
|
|
0b94e02de5 | ||
|
|
273c7ebcf0 | ||
|
|
fe7a5ee1b2 | ||
|
|
3a40778c4b | ||
|
|
5331150cb9 | ||
|
|
37a7f35a27 | ||
|
|
dc2f3a477b | ||
|
|
c6ed9429be | ||
|
|
21753b0e4c | ||
|
|
cb6af44984 | ||
|
|
f509b19b1d | ||
|
|
7464179249 | ||
|
|
c7ac5de28e | ||
|
|
315e9cb194 | ||
|
|
2e7073a890 | ||
|
|
e8d9a913d8 | ||
|
|
305701326c | ||
|
|
4453f1910d | ||
|
|
8c89b1618a | ||
|
|
1b0be9a997 | ||
|
|
3e4ce5caa7 | ||
|
|
f80f6f4a62 | ||
|
|
66caf907fd | ||
|
|
d3150399c7 | ||
|
|
f1c6a12c06 | ||
|
|
947442ec78 | ||
|
|
165dd003d7 | ||
|
|
b852ced3c1 | ||
|
|
4a03d62b91 | ||
|
|
056574eebe | ||
|
|
e61685547a | ||
|
|
7ebaf43282 | ||
|
|
15580ff6a6 | ||
|
|
fd6f5bca83 | ||
|
|
b6d311f20b | ||
|
|
9c0d5517bd | ||
|
|
3445ab9304 | ||
|
|
533219ede7 | ||
|
|
b1f55026af | ||
|
|
a26e9aaf25 | ||
|
|
a55a0645e2 | ||
|
|
e529e12d27 | ||
|
|
e78eee972e | ||
|
|
37cd5b42de | ||
|
|
7fe5247626 | ||
|
|
f098983c9f | ||
|
|
516d2a659e | ||
|
|
808c5dcc46 | ||
|
|
ecea8054a6 | ||
|
|
b0076af9bd | ||
|
|
24cf1e3a7f | ||
|
|
e5ee4cde4f | ||
|
|
e8e1521972 | ||
|
|
6f53b9a6bb | ||
|
|
acaa9f6dc7 | ||
|
|
375f2df694 | ||
|
|
542e50be33 | ||
|
|
ad9ee99521 | ||
|
|
25395a9f9a | ||
|
|
d7029952bb | ||
|
|
340449b77e | ||
|
|
527e229ac8 | ||
|
|
d5bc753764 | ||
|
|
436f7f7e86 | ||
|
|
009e37ec6f | ||
|
|
b253df881d | ||
|
|
c1743aa42d | ||
|
|
0f99d93300 | ||
|
|
e9fa99e16b | ||
|
|
e18d369adf | ||
|
|
22ac8a8c10 | ||
|
|
bebe534bad | ||
|
|
9ffcb23877 | ||
|
|
dc3c2cc5c7 | ||
|
|
4dd2b2216c | ||
|
|
0cada09aa7 | ||
|
|
3ff8cafac6 | ||
|
|
041de6cba3 | ||
|
|
aa5c33a42d | ||
|
|
f892f609c2 | ||
|
|
5e8106114f | ||
|
|
95876d9d8c | ||
|
|
e460536119 | ||
|
|
561d8992bc | ||
|
|
d5e19cb7ba | ||
|
|
d40f753983 | ||
|
|
eb090c6170 | ||
|
|
7b00255b11 | ||
|
|
f0dd5d667b | ||
|
|
7eb3994b02 | ||
|
|
7d31a6fcd7 | ||
|
|
096d6f591b | ||
|
|
19348631ab | ||
|
|
4e558ee142 | ||
|
|
effdaabab3 | ||
|
|
a394fe1af3 | ||
|
|
d16b7ccd6a | ||
|
|
28facc1c3f | ||
|
|
85b68a9969 | ||
|
|
a6a5edf573 | ||
|
|
bd208783d7 | ||
|
|
08e808ef20 | ||
|
|
bb3a18c627 | ||
|
|
74ae1e4a29 | ||
|
|
139de76347 | ||
|
|
2395a6ded2 | ||
|
|
41b15955b0 | ||
|
|
07055bb531 | ||
|
|
638999213e | ||
|
|
5ea49f3c50 | ||
|
|
9fbb708e64 | ||
|
|
f355080b6f | ||
|
|
b8ffd25148 | ||
|
|
2ed96e9678 | ||
|
|
fbbf3c4beb | ||
|
|
b88066b73b | ||
|
|
83d475eb02 | ||
|
|
292576d2a1 | ||
|
|
0c8af978db | ||
|
|
23671f1a99 | ||
|
|
94e882762e | ||
|
|
672538f4f1 | ||
|
|
a9763639ed | ||
|
|
616e694bdd | ||
|
|
0bee379b61 | ||
|
|
d35e452368 | ||
|
|
86f322e252 | ||
|
|
b40def47eb | ||
|
|
93cf3559e1 | ||
|
|
8f947253ad | ||
|
|
8cce59302b | ||
|
|
74ad6d76eb | ||
|
|
95f2f5bab9 | ||
|
|
e5ef9b2ace | ||
|
|
0a138276f8 | ||
|
|
1549598e52 | ||
|
|
1902518cd6 | ||
|
|
bcb8a9b57b | ||
|
|
bdb276cd68 | ||
|
|
79aa381722 | ||
|
|
45f1a13cc3 | ||
|
|
afdbf0a194 | ||
|
|
940ab5fd8e | ||
|
|
6418561d3c | ||
|
|
ce2b5a793f | ||
|
|
6ecefaec75 | ||
|
|
bdf29fc4d5 | ||
|
|
51d9fa91ed | ||
|
|
5cdc6c902e | ||
|
|
03696f8a1a | ||
|
|
7c26ff1b81 | ||
|
|
d472d8c32e | ||
|
|
75e5fe1dd3 | ||
|
|
fb1da4bbd0 | ||
|
|
ac43c0224c | ||
|
|
011b2ad6f8 | ||
|
|
e83e6ed76e | ||
|
|
edec6e4e62 | ||
|
|
cd96db7829 | ||
|
|
ca38d8fe86 | ||
|
|
f10f7df389 | ||
|
|
1cd75f7f54 | ||
|
|
1c84422f8e | ||
|
|
ba330cd70a | ||
|
|
4439162fea | ||
|
|
91120d86ba | ||
|
|
71f5ba9601 | ||
|
|
2e14b44349 | ||
|
|
ac66ea7790 | ||
|
|
632a4f0859 | ||
|
|
e2e4b6e579 | ||
|
|
37255523b1 | ||
|
|
c3a830e9e6 | ||
|
|
8266376838 | ||
|
|
d8e3c29744 | ||
|
|
ef3273674b | ||
|
|
633a25b456 | ||
|
|
b9de562491 | ||
|
|
76f5a23b7f | ||
|
|
4a3a226f27 | ||
|
|
ced7f28898 | ||
|
|
bc2db26e07 | ||
|
|
6649241193 | ||
|
|
fea8e615f9 | ||
|
|
57eb9bd415 | ||
|
|
0cd236414e | ||
|
|
e4957e089a | ||
|
|
3c643d943a | ||
|
|
9b06475811 | ||
|
|
fabae2465b | ||
|
|
233277d94b | ||
|
|
59976a7f7b | ||
|
|
4613167abb | ||
|
|
f5f63aa044 | ||
|
|
6495c6dad9 | ||
|
|
2d1b8cdd30 | ||
|
|
d2cbd2f596 | ||
|
|
6c7c5a1bc7 | ||
|
|
5b116696f0 | ||
|
|
2f1949e093 | ||
|
|
cca4df17d9 | ||
|
|
ef06d90b6b | ||
|
|
e7e6888768 | ||
|
|
a762346a3b | ||
|
|
d78892cad0 | ||
|
|
3fc6357f28 | ||
|
|
65ff056b2e | ||
|
|
f121aeb57f | ||
|
|
d61a0b6904 | ||
|
|
0ef9c76224 | ||
|
|
46f3192c67 | ||
|
|
b2aafc3f95 | ||
|
|
531ebb3537 | ||
|
|
6a11f96a5b | ||
|
|
4fa2e0a58d | ||
|
|
c3d08a2125 | ||
|
|
56be5318ab | ||
|
|
db388e64b9 | ||
|
|
5af927224f | ||
|
|
b425b37e19 | ||
|
|
0132781fff | ||
|
|
9db9513c30 | ||
|
|
2f62b90d7b | ||
|
|
f68c7296a6 | ||
|
|
8fd25acbb6 | ||
|
|
59c9b94d76 | ||
|
|
d23e08f564 | ||
|
|
9a48c3f10c | ||
|
|
790b542163 | ||
|
|
5032148cf0 | ||
|
|
1c14c7b3ba | ||
|
|
82fabe90b3 | ||
|
|
acf7341fb8 | ||
|
|
ac33292e1b | ||
|
|
5d6cb7dfed | ||
|
|
195330ccea | ||
|
|
f6114c9e15 | ||
|
|
d2f2a0039f | ||
|
|
943600edb2 | ||
|
|
76acae9889 | ||
|
|
9cca0a8753 | ||
|
|
8fe8d096fb | ||
|
|
496fa50a23 | ||
|
|
9137396eae | ||
|
|
2f52264f01 | ||
|
|
f0f6d1e5fa | ||
|
|
18d19a9a8b | ||
|
|
3f53041ed2 | ||
|
|
c08ac7db72 | ||
|
|
7419f62159 | ||
|
|
252e59334d | ||
|
|
565dc90bf0 | ||
|
|
84eddaef2f | ||
|
|
641bfb1dd9 | ||
|
|
b87b698148 | ||
|
|
dac5f8964f | ||
|
|
85ffcb74d8 | ||
|
|
9e719461e2 | ||
|
|
f270988e03 | ||
|
|
b4f9401395 | ||
|
|
a1e2007b82 | ||
|
|
e92a57011f | ||
|
|
3736e1fc38 | ||
|
|
2660e1669c | ||
|
|
be6818effe | ||
|
|
a49a463acf | ||
|
|
317b3602c3 | ||
|
|
b637962da0 | ||
|
|
44f9dcb3a2 | ||
|
|
1a2b337618 | ||
|
|
d219ac3f6f | ||
|
|
1a2f867ba9 | ||
|
|
0329f40df5 | ||
|
|
193204de52 | ||
|
|
3f945dcfeb | ||
|
|
2f9ccb84d9 | ||
|
|
f10ea4ebcd | ||
|
|
ada6923039 | ||
|
|
f4cf398651 | ||
|
|
84dba77275 | ||
|
|
19e4ba386d | ||
|
|
7d101182cd | ||
|
|
e60b044ff7 | ||
|
|
4366909cf0 | ||
|
|
7b1d8ce9dd | ||
|
|
94fdeb633f | ||
|
|
50eda634e0 | ||
|
|
2bf75537d8 | ||
|
|
f780736f29 | ||
|
|
85e71622f8 | ||
|
|
ea02a2fac9 | ||
|
|
8368901bf9 | ||
|
|
a18170cdb1 | ||
|
|
e70fb39cb9 | ||
|
|
e7ba689ca2 | ||
|
|
78dcf43c72 | ||
|
|
70951f46e5 | ||
|
|
a6b4c4b049 | ||
|
|
db3d569ed0 | ||
|
|
ec0a1a0b70 | ||
|
|
42e3cce8c3 | ||
|
|
c82062a51b | ||
|
|
61349ac0c2 | ||
|
|
102bd2cfd2 | ||
|
|
e7bb46e819 | ||
|
|
02f8c7ab56 | ||
|
|
96ecabc38b | ||
|
|
57600f2bd3 | ||
|
|
a8628feb3c | ||
|
|
e2695235ab | ||
|
|
44c3ea7844 | ||
|
|
7c81906459 | ||
|
|
4e3679c7b7 | ||
|
|
19c0ae4e33 | ||
|
|
565649e3b6 | ||
|
|
527ef9653a | ||
|
|
23bb5245bd | ||
|
|
e3c4b94b11 | ||
|
|
3fa89640cd | ||
|
|
722bddf916 | ||
|
|
294dad1773 | ||
|
|
b33ae1c481 | ||
|
|
0dc3a0916e | ||
|
|
24372c8558 | ||
|
|
49faa5e749 | ||
|
|
69a3985f9d | ||
|
|
d335131f52 | ||
|
|
fe1b6db653 | ||
|
|
4c2c4cb565 | ||
|
|
bc8109b695 | ||
|
|
a88ca75c14 | ||
|
|
e453a75ea9 | ||
|
|
627f737bbe | ||
|
|
ef3e8da1b5 | ||
|
|
c2d6134cfa | ||
|
|
d51723c153 | ||
|
|
94b2a863bf | ||
|
|
442510f793 | ||
|
|
75a0fc2c0c | ||
|
|
72b1f50314 | ||
|
|
66c36cd945 | ||
|
|
d091d9c460 | ||
|
|
d3a2e2e666 | ||
|
|
a4261f8b19 | ||
|
|
6051588458 | ||
|
|
5c1fd733da | ||
|
|
c499480692 | ||
|
|
3da0b1dfd4 | ||
|
|
720919455f | ||
|
|
df90753d85 | ||
|
|
7bee645b9a | ||
|
|
83a6f24f93 | ||
|
|
8d35be14a0 | ||
|
|
85bd0613ca | ||
|
|
727939af5b | ||
|
|
dd52f0381a | ||
|
|
cf165c0db5 | ||
|
|
ab9db43599 | ||
|
|
d553e363a7 | ||
|
|
be3c3eb9bb | ||
|
|
357807666d | ||
|
|
d1a5a4e39c | ||
|
|
a701c896f0 | ||
|
|
b0de886f8e | ||
|
|
ad1ca4ea92 | ||
|
|
fbaf5261e2 | ||
|
|
19ada4b8f9 | ||
|
|
ff41e59282 | ||
|
|
a15a13ca46 | ||
|
|
2937043e77 | ||
|
|
24efb0bb17 | ||
|
|
dd2bd267c1 | ||
|
|
5cfe9c8998 | ||
|
|
3bd8a62aa8 | ||
|
|
dcd37f3c38 | ||
|
|
a579fd2342 | ||
|
|
f93ff00642 | ||
|
|
8a628631fb | ||
|
|
59733a2a26 | ||
|
|
c7d385c03d | ||
|
|
27a8aa057c | ||
|
|
e63bfbabf0 | ||
|
|
c107d9edcf | ||
|
|
49e69c1c7d | ||
|
|
482b6d7bbf | ||
|
|
dcfe91bdfc | ||
|
|
6072b5b703 | ||
|
|
9c50ac8a69 | ||
|
|
8b7acad257 | ||
|
|
cb8cb48a60 | ||
|
|
8c00ae1d38 | ||
|
|
633b3d3b0a | ||
|
|
60199c0323 | ||
|
|
9e0e03d4c0 | ||
|
|
4972ebf914 | ||
|
|
4a36d63c1b | ||
|
|
d7e768096e | ||
|
|
e13b5d0618 | ||
|
|
c5bf0dc07d | ||
|
|
9fbd83d602 | ||
|
|
39c5b28dfb | ||
|
|
be533994e5 | ||
|
|
368c089172 | ||
|
|
407d4344c4 | ||
|
|
76e3950028 | ||
|
|
7337b00ca8 | ||
|
|
22582dea3e | ||
|
|
f600a07404 | ||
|
|
ff790e1ff1 | ||
|
|
a345369269 | ||
|
|
8e4a4359b4 | ||
|
|
0a000d1628 | ||
|
|
5bd8e369c4 | ||
|
|
777d756d1b | ||
|
|
7716a8e05d | ||
|
|
94e93b171c | ||
|
|
1bf12be278 | ||
|
|
99b753be9a | ||
|
|
bc2f2d896b | ||
|
|
659fbf26da | ||
|
|
f61a7bb9c0 | ||
|
|
212309083a | ||
|
|
c1ebd01a70 | ||
|
|
af58e2c7b4 |
@@ -0,0 +1,4 @@
|
|||||||
|
tools/trace_replay/fixtures/*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
tools/trace_replay/fixtures/*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
tools/trace_replay/fixtures/openra.tgz -filter -diff -merge -text
|
||||||
|
tools/trace_replay/fixtures/openra.0000031249.png -filter -diff -merge -text
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
|
||||||
|
echo "usage: $0 <trace-case> [fixture-dir]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
case_name="$1"
|
||||||
|
fixture_dir="${2:-tools/trace_replay/fixtures}"
|
||||||
|
python_bin="${PYTHON:-python3}"
|
||||||
|
mirror_base="${MOBILEGL_TRACE_FIXTURE_MIRROR_BASE:-https://repo.miawa.cn/mgl/tools/trace_replay/fixtures}"
|
||||||
|
download_attempts="${MOBILEGL_TRACE_FIXTURE_DOWNLOAD_ATTEMPTS:-5}"
|
||||||
|
retry_delay="${MOBILEGL_TRACE_FIXTURE_RETRY_DELAY:-2}"
|
||||||
|
|
||||||
|
if ! command -v "${python_bin}" >/dev/null 2>&1 && command -v python >/dev/null 2>&1; then
|
||||||
|
python_bin=python
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "${download_attempts}" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
echo "MOBILEGL_TRACE_FIXTURE_DOWNLOAD_ATTEMPTS must be a positive integer: ${download_attempts}" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! [[ "${retry_delay}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "MOBILEGL_TRACE_FIXTURE_RETRY_DELAY must be a non-negative integer: ${retry_delay}" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
fixture_list="$("${python_bin}" tools/trace_replay/trace_cases.py \
|
||||||
|
--format fixture-files \
|
||||||
|
--case "${case_name}" \
|
||||||
|
--fixture-root "${fixture_dir}")"
|
||||||
|
mapfile -t files <<< "${fixture_list}"
|
||||||
|
|
||||||
|
include="$(IFS=,; echo "${files[*]}")"
|
||||||
|
if [ "${case_name}" = "OpenRA" ]; then
|
||||||
|
echo "Fixture files for ${case_name} are stored in Git: ${include}"
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
test -s "${file}"
|
||||||
|
if head -n 1 "${file}" | grep -q "version https://git-lfs.github.com/spec/v1"; then
|
||||||
|
echo "fixture should not be stored as an LFS pointer: ${file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
get_lfs_metadata() {
|
||||||
|
local file="$1"
|
||||||
|
local pointer
|
||||||
|
local expected_oid
|
||||||
|
local expected_size
|
||||||
|
|
||||||
|
if ! pointer="$(git show "HEAD:${file}" 2>/dev/null)"; then
|
||||||
|
echo "failed to read tracked fixture metadata: ${file}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! grep -q '^version https://git-lfs.github.com/spec/v1$' <<< "${pointer}"; then
|
||||||
|
echo "tracked fixture is not a Git LFS pointer: ${file}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expected_oid="$(awk '$1 == "oid" && $2 ~ /^sha256:/ { sub(/^sha256:/, "", $2); print $2 }' <<< "${pointer}")"
|
||||||
|
expected_size="$(awk '$1 == "size" { print $2 }' <<< "${pointer}")"
|
||||||
|
if ! [[ "${expected_oid}" =~ ^[0-9a-f]{64}$ ]] || ! [[ "${expected_size}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "invalid Git LFS pointer metadata: ${file}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s %s\n' "${expected_oid}" "${expected_size}"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_fixture_file() {
|
||||||
|
local downloaded_file="$1"
|
||||||
|
local display_name="$2"
|
||||||
|
local expected_oid="$3"
|
||||||
|
local expected_size="$4"
|
||||||
|
local actual_oid
|
||||||
|
local actual_size
|
||||||
|
|
||||||
|
if [ ! -f "${downloaded_file}" ]; then
|
||||||
|
echo "fixture file is missing: ${display_name}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
actual_size="$(wc -c < "${downloaded_file}" | tr -d '[:space:]')"
|
||||||
|
if [ "${actual_size}" != "${expected_size}" ]; then
|
||||||
|
echo "fixture size mismatch for ${display_name}: expected ${expected_size}, got ${actual_size}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
actual_oid="$(sha256sum "${downloaded_file}" | awk '{ print $1 }')"
|
||||||
|
if [ "${actual_oid}" != "${expected_oid}" ]; then
|
||||||
|
echo "fixture SHA-256 mismatch for ${display_name}: expected ${expected_oid}, got ${actual_oid}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_file_from_mirror() {
|
||||||
|
local file="$1"
|
||||||
|
local url="$2"
|
||||||
|
local metadata
|
||||||
|
local expected_oid
|
||||||
|
local expected_size
|
||||||
|
local tmp_file="${file}.tmp"
|
||||||
|
local attempt
|
||||||
|
local partial_size
|
||||||
|
local curl_status
|
||||||
|
|
||||||
|
metadata="$(get_lfs_metadata "${file}")" || return 1
|
||||||
|
read -r expected_oid expected_size <<< "${metadata}"
|
||||||
|
|
||||||
|
if [ -f "${tmp_file}" ]; then
|
||||||
|
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
|
||||||
|
if [ "${partial_size}" -gt "${expected_size}" ]; then
|
||||||
|
echo "Discarding oversized partial fixture ${tmp_file}: ${partial_size} > ${expected_size}" >&2
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
elif [ "${partial_size}" = "${expected_size}" ]; then
|
||||||
|
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
|
||||||
|
mv "${tmp_file}" "${file}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
for ((attempt = 1; attempt <= download_attempts; attempt++)); do
|
||||||
|
partial_size=0
|
||||||
|
if [ -f "${tmp_file}" ]; then
|
||||||
|
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${partial_size}" -gt 0 ]; then
|
||||||
|
echo "Resuming mirror download for ${file} at byte ${partial_size} (attempt ${attempt}/${download_attempts})"
|
||||||
|
else
|
||||||
|
echo "Starting mirror download for ${file} (attempt ${attempt}/${download_attempts})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if curl -L --fail --show-error --continue-at - --output "${tmp_file}" "${url}"; then
|
||||||
|
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
|
||||||
|
mv "${tmp_file}" "${file}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "Mirror download failed integrity verification; retrying from the beginning: ${file}" >&2
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
else
|
||||||
|
curl_status=$?
|
||||||
|
partial_size=0
|
||||||
|
if [ -f "${tmp_file}" ]; then
|
||||||
|
partial_size="$(wc -c < "${tmp_file}" | tr -d '[:space:]')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${partial_size}" = "${expected_size}" ]; then
|
||||||
|
if verify_fixture_file "${tmp_file}" "${file}" "${expected_oid}" "${expected_size}"; then
|
||||||
|
mv "${tmp_file}" "${file}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
partial_size=0
|
||||||
|
elif [ "${partial_size}" -gt "${expected_size}" ]; then
|
||||||
|
echo "Discarding oversized partial fixture ${tmp_file}: ${partial_size} > ${expected_size}" >&2
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
partial_size=0
|
||||||
|
elif [ "${curl_status}" -eq 33 ]; then
|
||||||
|
echo "Mirror refused the resume request; retrying from the beginning: ${file}" >&2
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
partial_size=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Mirror download attempt ${attempt}/${download_attempts} failed with curl exit ${curl_status}; retained ${partial_size} bytes for resume: ${file}" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${attempt}" -lt "${download_attempts}" ]; then
|
||||||
|
sleep "${retry_delay}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_from_mirror() {
|
||||||
|
mkdir -p "${fixture_dir}"
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
local name
|
||||||
|
local url
|
||||||
|
name="$(basename "${file}")"
|
||||||
|
url="${mirror_base%/}/${name}"
|
||||||
|
echo "Fetching trace fixture from mirror: ${url}"
|
||||||
|
if ! fetch_file_from_mirror "${file}" "${url}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if fetch_from_mirror; then
|
||||||
|
echo "Fetched trace fixture files for ${case_name} from mirror: ${include}"
|
||||||
|
else
|
||||||
|
echo "Mirror fetch failed for ${case_name}; falling back to Git LFS: ${include}"
|
||||||
|
git lfs install --local
|
||||||
|
git lfs pull --include="${include}" --exclude=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
metadata="$(get_lfs_metadata "${file}")"
|
||||||
|
read -r expected_oid expected_size <<< "${metadata}"
|
||||||
|
verify_fixture_file "${file}" "${file}" "${expected_oid}" "${expected_size}"
|
||||||
|
done
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 3 ]]; then
|
||||||
|
echo "Usage: $0 <aapt2> <plugin-apk> <trace-apk>" >&2
|
||||||
|
exit 64
|
||||||
|
fi
|
||||||
|
|
||||||
|
aapt2=$1
|
||||||
|
plugin_apk=$2
|
||||||
|
trace_apk=$3
|
||||||
|
|
||||||
|
require() {
|
||||||
|
local needle=$1
|
||||||
|
local content=$2
|
||||||
|
local description=$3
|
||||||
|
if ! grep -Fq -- "$needle" <<<"$content"; then
|
||||||
|
echo "::error::Missing ${description}: ${needle}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for apk in "$plugin_apk" "$trace_apk"; do
|
||||||
|
[[ -f "$apk" ]] || { echo "::error::APK not found: $apk" >&2; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
plugin_manifest=$("$aapt2" dump xmltree --file AndroidManifest.xml "$plugin_apk")
|
||||||
|
plugin_resources=$("$aapt2" dump resources "$plugin_apk")
|
||||||
|
plugin_resource_text=$(tr -d '"' <<<"$plugin_resources")
|
||||||
|
trace_manifest=$("$aapt2" dump xmltree --file AndroidManifest.xml "$trace_apk")
|
||||||
|
plugin_contents=$(unzip -Z1 "$plugin_apk")
|
||||||
|
|
||||||
|
require 'top.mobilegl.plugin' "$plugin_manifest" 'plugin package name'
|
||||||
|
require 'MobileGL' "$plugin_manifest" 'plugin label'
|
||||||
|
require 'fclPlugin' "$plugin_manifest" 'legacy plugin marker'
|
||||||
|
require 'fclPlugin_V2' "$plugin_manifest" 'V2 plugin marker'
|
||||||
|
require 'LIBGL_ES=3:POJAV_RENDERER=opengles3:MOBILEGL_BACKEND_TYPE=DirectGLES' "$plugin_manifest" 'V1 DirectGLES fallback'
|
||||||
|
require 'string/config' "$plugin_resources" 'V2 renderer configuration resource'
|
||||||
|
require '{displayName:MobileGL,rendererId:opengles3' "$plugin_resource_text" 'V2 MobileGL entry and renderer ID'
|
||||||
|
require 'rendererGLPath:**|libMobileGL.so' "$plugin_resource_text" 'V2 GL library path'
|
||||||
|
require 'rendererEGLPath:**|libMobileGL.so' "$plugin_resource_text" 'V2 EGL library path'
|
||||||
|
require 'key:LIBGL_ES,value:3' "$plugin_resource_text" 'V2 fixed LIBGL_ES variable'
|
||||||
|
require 'key:MOBILEGL_BACKEND_TYPE' "$plugin_resource_text" 'V2 backend variable'
|
||||||
|
require 'defaultValue:DirectGLES' "$plugin_resource_text" 'V2 DirectGLES default'
|
||||||
|
require 'DirectVulkan' "$plugin_resource_text" 'V2 DirectVulkan option'
|
||||||
|
require 'key:MOBILEGL_DISABLE_TIMERQUERY' "$plugin_resource_text" 'V2 timer-query toggle'
|
||||||
|
require 'key:MOBILEGL_DISABLE_SUBGROUP' "$plugin_resource_text" 'V2 Vulkan subgroup toggle'
|
||||||
|
require 'key:MOBILEGL_MAGMA_R11G11B10F_FALLBACK' "$plugin_resource_text" 'V2 Magma format fallback toggle'
|
||||||
|
require 'key:MOBILEGL_MAGMA_FRAMESINFLIGHT' "$plugin_resource_text" 'V2 Magma frames-in-flight setting'
|
||||||
|
require 'key:MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER' "$plugin_resource_text" 'V2 sampler workaround toggle'
|
||||||
|
require 'key:MOBILEGL_COHERENT_AS_FLUSH' "$plugin_resource_text" 'V2 coherent-as-flush toggle'
|
||||||
|
require 'key:MOBILEGL_USE_ANGLE' "$plugin_resource_text" 'V2 ANGLE toggle'
|
||||||
|
|
||||||
|
if [[ $(grep -Fc 'fclPlugin_V2' <<<"$plugin_manifest") -ne 1 ]]; then
|
||||||
|
echo '::error::Plugin manifest must expose exactly one V2 descriptor' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -Eq '^lib/[^/]+/libMobileGL\.so$' <<<"$plugin_contents"; then
|
||||||
|
echo '::error::Plugin APK does not contain libMobileGL.so' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
require 'top.mobilegl.plugin.trace' "$trace_manifest" 'trace package name'
|
||||||
|
require 'top.mobilegl.plugin.TRACE_REPLAY' "$trace_manifest" 'trace replay action'
|
||||||
|
if grep -Fq 'fclPlugin' <<<"$trace_manifest"; then
|
||||||
|
echo '::error::Trace APK must not advertise renderer-plugin metadata' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if grep -Fq 'android.intent.action.MAIN' <<<"$trace_manifest"; then
|
||||||
|
echo '::error::Trace APK must not expose a launcher activity' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 'Validated unified MobileGL plugin APK and isolated trace APK.'
|
||||||
@@ -0,0 +1,560 @@
|
|||||||
|
name: MobileGL APK
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- Feat/Backend-Direct-GLES
|
||||||
|
- Feat/Backend-Direct-Vulkan
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||||
|
CCACHE_COMPRESS: "true"
|
||||||
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
CCACHE_MAXSIZE: 4G
|
||||||
|
CCACHE_NOHASHDIR: "true"
|
||||||
|
MOBILEGL_CMAKE_COMPILER_LAUNCHER: ccache
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set artifact metadata
|
||||||
|
run: |
|
||||||
|
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: zulu
|
||||||
|
java-version: '17'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v6
|
||||||
|
with:
|
||||||
|
gradle-version: 8.10.2
|
||||||
|
|
||||||
|
- name: Restore ccache
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: .ccache
|
||||||
|
key: ${{ runner.os }}-apk-${{ github.job }}-ccache-${{ github.ref_name }}-${{ github.run_id }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-apk-${{ github.job }}-ccache-${{ github.ref_name }}-
|
||||||
|
${{ runner.os }}-apk-${{ github.job }}-ccache-
|
||||||
|
|
||||||
|
- name: Install ccache
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y ccache
|
||||||
|
ccache --version
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v4
|
||||||
|
with:
|
||||||
|
accept-android-sdk-licenses: false
|
||||||
|
|
||||||
|
- name: Accept Android SDK licenses
|
||||||
|
run: yes | sdkmanager --licenses >/dev/null
|
||||||
|
|
||||||
|
- name: Install Android NDK
|
||||||
|
run: |
|
||||||
|
sdkmanager "ndk;27.3.13750724"
|
||||||
|
echo "ndk.dir=$ANDROID_HOME/ndk/27.3.13750724" >> android-plugin/local.properties
|
||||||
|
|
||||||
|
- name: Update glslang external sources
|
||||||
|
working-directory: 3rdparty/glslang
|
||||||
|
run: python update_glslang_sources.py
|
||||||
|
|
||||||
|
- name: Build plugin APK
|
||||||
|
run: gradle --no-daemon -p android-plugin :app:assemblePluginRelease -Pmobilegl.apkSuffix="${GITHUB_SHA}" -Pmobilegl.logLevel=MOBILEGL_LOG_LEVEL_INFO --parallel --max-workers "$(nproc)"
|
||||||
|
env:
|
||||||
|
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||||
|
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
|
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Download ANGLE x86_64 libraries
|
||||||
|
run: |
|
||||||
|
angle_dir="android-plugin/app/src/trace/jniLibs/x86_64"
|
||||||
|
rm -rf "${angle_dir}"
|
||||||
|
mkdir -p "${angle_dir}"
|
||||||
|
|
||||||
|
package_angle_variant() {
|
||||||
|
variant="$1"
|
||||||
|
commit="$2"
|
||||||
|
egl_sha="$3"
|
||||||
|
gles_sha="$4"
|
||||||
|
source_dir="${RUNNER_TEMP}/mobilegl-angle-${variant}"
|
||||||
|
base="https://raw.githubusercontent.com/FCL-Team/FoldCraftLauncher/${commit}/FCLauncher/src/main/jniLibs/x86_64"
|
||||||
|
mkdir -p "${source_dir}"
|
||||||
|
curl -L --fail --retry 3 -o "${source_dir}/libEGL_angle.so" "${base}/libEGL_angle.so"
|
||||||
|
curl -L --fail --retry 3 -o "${source_dir}/libGLESv2_angle.so" "${base}/libGLESv2_angle.so"
|
||||||
|
echo "${egl_sha} ${source_dir}/libEGL_angle.so" | sha256sum -c -
|
||||||
|
echo "${gles_sha} ${source_dir}/libGLESv2_angle.so" | sha256sum -c -
|
||||||
|
for library in libEGL_angle libGLESv2_angle; do
|
||||||
|
filename="${library}_${variant}.so"
|
||||||
|
cp "${source_dir}/${library}.so" "${angle_dir}/${filename}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
package_angle_variant \
|
||||||
|
ec889e6ea831 \
|
||||||
|
f2a3d510dffd8f6540a52e1a7d0c5787d151075b \
|
||||||
|
c41828768d089899fa058ec0bee711a91be88347f29bdb935223da6be1149c40 \
|
||||||
|
e4f820d99f94365c66df868c7740fef142fe5c0cd7c941790a9e30638857ca4d
|
||||||
|
package_angle_variant \
|
||||||
|
90a62123d794 \
|
||||||
|
bdcc96ac11c79001018ae4375eb73cb54a9f682f \
|
||||||
|
d0f4298ccc770cc801fc52e21733521646161e8a4adb3bd0052d9a1b57ee0ca8 \
|
||||||
|
66fdc867e552192d553d59095ea2e3cef4829de65c356f1fd826027b1905972e
|
||||||
|
|
||||||
|
- name: Build retrace APK
|
||||||
|
run: gradle --no-daemon -p android-plugin :app:assembleTraceRelease -Pmobilegl.apkSuffix="${GITHUB_SHA}" -Pmobilegl.abis=all -Pmobilegl.debuggableRelease=true -Pmobilegl.logLevel=MOBILEGL_LOG_LEVEL_INFO --parallel --max-workers "$(nproc)"
|
||||||
|
env:
|
||||||
|
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||||
|
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
|
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Show ccache stats
|
||||||
|
if: always()
|
||||||
|
run: ccache --show-stats
|
||||||
|
|
||||||
|
- name: Verify APK metadata and packaging
|
||||||
|
run: |
|
||||||
|
AAPT2="$(find "$ANDROID_HOME/build-tools" -name aapt2 -type f | sort -V | tail -n 1)"
|
||||||
|
plugin_apk="android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${GITHUB_SHA}.apk"
|
||||||
|
trace_apk="android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk"
|
||||||
|
test -f "${plugin_apk}"
|
||||||
|
test -f "${trace_apk}"
|
||||||
|
bash .github/scripts/validate-plugin-apks.sh "$AAPT2" "$plugin_apk" "$trace_apk"
|
||||||
|
|
||||||
|
- name: Verify signed APKs
|
||||||
|
run: |
|
||||||
|
APKSIGNER="$(find "$ANDROID_HOME/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
|
||||||
|
mapfile -t APKS < <(printf '%s\n' \
|
||||||
|
"android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${GITHUB_SHA}.apk" \
|
||||||
|
"android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk")
|
||||||
|
for APK in "${APKS[@]}"; do
|
||||||
|
if [[ ! -f "$APK" ]]; then
|
||||||
|
echo "::error::Expected release APK was not produced: $APK"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for APK in "${APKS[@]}"; do
|
||||||
|
if [[ "$APK" == *-unsigned.apk ]]; then
|
||||||
|
echo "::error::Unsigned release APK produced: $APK"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
"$APKSIGNER" verify --verbose "$APK"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload plugin APK
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: MobileGL-plugin-${{ env.date_today }}-${{ github.sha }}
|
||||||
|
path: android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-${{ github.sha }}.apk
|
||||||
|
archive: false
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload retrace APK
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: MobileGL-retrace-apk-${{ env.date_today }}-${{ github.sha }}
|
||||||
|
path: android-plugin/app/build/outputs/apk/trace/release/MobileGL-plugin-trace-release-${{ github.sha }}.apk
|
||||||
|
archive: false
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
trace-cases:
|
||||||
|
name: trace case matrix
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
outputs:
|
||||||
|
android: ${{ steps.trace-cases.outputs.android }}
|
||||||
|
names: ${{ steps.trace-cases.outputs.names }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Load trace cases
|
||||||
|
id: trace-cases
|
||||||
|
run: |
|
||||||
|
echo "android=$(python3 tools/trace_replay/trace_cases.py --ci --format github-apk)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
trace-fixtures:
|
||||||
|
name: trace fixture (${{ matrix.case }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: trace-cases
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 4
|
||||||
|
matrix:
|
||||||
|
case: ${{ fromJSON(needs.trace-cases.outputs.names) }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Fetch trace fixture
|
||||||
|
run: bash .github/scripts/fetch-trace-fixture-lfs.sh '${{ matrix.case }}'
|
||||||
|
|
||||||
|
- name: Stage trace fixture
|
||||||
|
run: |
|
||||||
|
safe_case="$(printf '%s' '${{ matrix.case }}' | sed 's/[^A-Za-z0-9._-]/_/g')"
|
||||||
|
stage_dir="trace-fixtures/${safe_case}"
|
||||||
|
mkdir -p "${stage_dir}"
|
||||||
|
python3 tools/trace_replay/trace_cases.py --format fixture-files --case '${{ matrix.case }}' |
|
||||||
|
while IFS= read -r file; do
|
||||||
|
cp "${file}" "${stage_dir}/"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload trace fixture
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: MobileGL-trace-fixture-${{ matrix.case }}
|
||||||
|
path: trace-fixtures/**
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
android-avd:
|
||||||
|
name: android avd image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
AVD_NAME: mobilegl-ci
|
||||||
|
ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd
|
||||||
|
ANDROID_HOME: ${{ github.workspace }}/.android/sdk
|
||||||
|
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v4
|
||||||
|
with:
|
||||||
|
accept-android-sdk-licenses: false
|
||||||
|
|
||||||
|
- name: Accept Android SDK licenses
|
||||||
|
run: yes | sdkmanager --licenses >/dev/null
|
||||||
|
|
||||||
|
- name: Restore Android AVD cache
|
||||||
|
id: android-avd-cache
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ env.ANDROID_AVD_HOME }}
|
||||||
|
${{ env.ANDROID_SDK_ROOT }}/emulator
|
||||||
|
${{ env.ANDROID_SDK_ROOT }}/platform-tools
|
||||||
|
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
|
||||||
|
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64
|
||||||
|
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
|
||||||
|
|
||||||
|
- name: Create AVD
|
||||||
|
if: steps.android-avd-cache.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
sh android-plugin/run-avd-ci.sh create \
|
||||||
|
--api-level 35 \
|
||||||
|
--target google_apis \
|
||||||
|
--arch x86_64 \
|
||||||
|
--profile pixel_6 \
|
||||||
|
--avd-name "${AVD_NAME}"
|
||||||
|
|
||||||
|
retrace:
|
||||||
|
name: retrace (${{ matrix.backend.name }}, ${{ matrix.case.name }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
- android-avd
|
||||||
|
- trace-cases
|
||||||
|
- trace-fixtures
|
||||||
|
if: ${{ always() && needs.build.result == 'success' && needs.android-avd.result == 'success' && needs.trace-cases.result == 'success' }}
|
||||||
|
timeout-minutes: 75
|
||||||
|
env:
|
||||||
|
AVD_NAME: mobilegl-ci
|
||||||
|
ANDROID_AVD_HOME: ${{ github.workspace }}/.android/avd
|
||||||
|
ANDROID_HOME: ${{ github.workspace }}/.android/sdk
|
||||||
|
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 4
|
||||||
|
matrix:
|
||||||
|
backend:
|
||||||
|
- name: DirectGLES
|
||||||
|
gpu: software
|
||||||
|
- name: DirectVulkan
|
||||||
|
gpu: lavapipe
|
||||||
|
case: ${{ fromJSON(needs.trace-cases.outputs.android) }}
|
||||||
|
steps:
|
||||||
|
- name: Set Swap Space
|
||||||
|
uses: pierotofy/set-swap-space@v1.0
|
||||||
|
with:
|
||||||
|
swap-size-gb: 8
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Download trace fixture
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: MobileGL-trace-fixture-${{ matrix.case.name }}
|
||||||
|
path: trace-fixture-download
|
||||||
|
|
||||||
|
- name: Install trace fixture
|
||||||
|
run: |
|
||||||
|
mkdir -p tools/trace_replay/fixtures
|
||||||
|
find trace-fixture-download -type f -exec cp {} tools/trace_replay/fixtures/ \;
|
||||||
|
|
||||||
|
- name: Set artifact metadata
|
||||||
|
run: |
|
||||||
|
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||||
|
echo "EMULATOR_LOG=${RUNNER_TEMP}/mobilegl-emulator.log" >> "$GITHUB_ENV"
|
||||||
|
echo "EMULATOR_PID_FILE=${RUNNER_TEMP}/mobilegl-emulator.pid" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v4
|
||||||
|
with:
|
||||||
|
accept-android-sdk-licenses: false
|
||||||
|
|
||||||
|
- name: Accept Android SDK licenses
|
||||||
|
run: yes | sdkmanager --licenses >/dev/null
|
||||||
|
|
||||||
|
- name: Restore Android AVD cache
|
||||||
|
id: android-avd-cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ env.ANDROID_AVD_HOME }}
|
||||||
|
${{ env.ANDROID_SDK_ROOT }}/emulator
|
||||||
|
${{ env.ANDROID_SDK_ROOT }}/platform-tools
|
||||||
|
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
|
||||||
|
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64
|
||||||
|
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
|
||||||
|
|
||||||
|
- name: Download retrace APK
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: MobileGL-plugin-trace-release-${{ github.sha }}.apk
|
||||||
|
path: android-retrace-apks
|
||||||
|
|
||||||
|
- name: Enable KVM
|
||||||
|
run: |
|
||||||
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger --name-match=kvm
|
||||||
|
|
||||||
|
- name: Create AVD
|
||||||
|
if: steps.android-avd-cache.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
sh android-plugin/run-avd-ci.sh create \
|
||||||
|
--api-level 35 \
|
||||||
|
--target google_apis \
|
||||||
|
--arch x86_64 \
|
||||||
|
--profile pixel_6 \
|
||||||
|
--avd-name "${AVD_NAME}"
|
||||||
|
|
||||||
|
- name: Launch Emulator
|
||||||
|
run: |
|
||||||
|
sh android-plugin/run-avd-ci.sh start \
|
||||||
|
--avd-name "${AVD_NAME}" \
|
||||||
|
--gpu "${{ matrix.backend.gpu }}" \
|
||||||
|
--emulator-log "${EMULATOR_LOG}" \
|
||||||
|
--pid-file "${EMULATOR_PID_FILE}" \
|
||||||
|
--boot-timeout 300
|
||||||
|
|
||||||
|
- name: Retrace and validate
|
||||||
|
env:
|
||||||
|
MOBILEGL_USE_ANGLE: ${{ matrix.backend.name == 'DirectGLES' && '1' || '0' }}
|
||||||
|
MOBILEGL_TRACE_ANGLE_VARIANT: ${{ matrix.case.name == 'minecraft-1.21.4-fabric-iris-bliss-in-world' && '90a62123d794' || 'ec889e6ea831' }}
|
||||||
|
MOBILEGL_MAGMA_R11G11B10F_FALLBACK: ${{ matrix.backend.name == 'DirectVulkan' && '1' || '0' }}
|
||||||
|
run: |
|
||||||
|
apk_file="android-retrace-apks/MobileGL-plugin-trace-release-${GITHUB_SHA}.apk"
|
||||||
|
test -f "${apk_file}"
|
||||||
|
extra_retrace_args=()
|
||||||
|
# Bliss needs the newer signed ANGLE variant plus sampler mipmap
|
||||||
|
# min-filter downgrading on ANGLE llvmpipe.
|
||||||
|
if [ "${{ matrix.backend.name }}" = "DirectGLES" ] && [ "${{ matrix.case.name }}" = "minecraft-1.21.4-fabric-iris-bliss-in-world" ]; then
|
||||||
|
extra_retrace_args+=(--avoid-angle-llvmpipe-sampler-mipmap-min-filter)
|
||||||
|
fi
|
||||||
|
if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then
|
||||||
|
extra_retrace_args+=(--coherent-as-flush)
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_retrace() {
|
||||||
|
timeout "$(( ${{ matrix.case.timeout_seconds }} + 300 ))" sh android-plugin/trace-replay-ci.sh \
|
||||||
|
--apk-file "${apk_file}" \
|
||||||
|
--package top.mobilegl.plugin.trace \
|
||||||
|
--backend "${{ matrix.backend.name }}" \
|
||||||
|
--result-root android-retrace-result \
|
||||||
|
--fixture-root android-retrace-fixture \
|
||||||
|
--case "${{ matrix.case.name }}" \
|
||||||
|
--trace-archive "${{ matrix.case.trace_archive }}" \
|
||||||
|
--trace-file "${{ matrix.case.trace_file }}" \
|
||||||
|
--golden "${{ matrix.case.golden }}" \
|
||||||
|
--alternate-golden "${{ matrix.case.alternate_golden || '' }}" \
|
||||||
|
--target-call "${{ matrix.case.target_call }}" \
|
||||||
|
--width "${{ matrix.case.width }}" \
|
||||||
|
--height "${{ matrix.case.height }}" \
|
||||||
|
--ssim-threshold "${{ matrix.case.ssim_threshold || '0.99' }}" \
|
||||||
|
--crop-x "${{ matrix.case.crop_x }}" \
|
||||||
|
--crop-y "${{ matrix.case.crop_y }}" \
|
||||||
|
--crop-width "${{ matrix.case.crop_width }}" \
|
||||||
|
--crop-height "${{ matrix.case.crop_height }}" \
|
||||||
|
--timeout-seconds "${{ matrix.case.timeout_seconds }}" \
|
||||||
|
"${extra_retrace_args[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
retrace_status=0
|
||||||
|
run_retrace || retrace_status=$?
|
||||||
|
if [ "${retrace_status}" -eq 75 ]; then
|
||||||
|
echo "::warning::Android emulator infrastructure failed; restarting it and retrying this retrace once."
|
||||||
|
sh android-plugin/run-avd-ci.sh stop \
|
||||||
|
--avd-name "${AVD_NAME}" \
|
||||||
|
--emulator-log "${EMULATOR_LOG}" \
|
||||||
|
--pid-file "${EMULATOR_PID_FILE}"
|
||||||
|
adb kill-server || true
|
||||||
|
sleep 2
|
||||||
|
sh android-plugin/run-avd-ci.sh start \
|
||||||
|
--avd-name "${AVD_NAME}" \
|
||||||
|
--gpu "${{ matrix.backend.gpu }}" \
|
||||||
|
--emulator-log "${EMULATOR_LOG}" \
|
||||||
|
--pid-file "${EMULATOR_PID_FILE}" \
|
||||||
|
--boot-timeout 300
|
||||||
|
run_retrace
|
||||||
|
elif [ "${retrace_status}" -ne 0 ]; then
|
||||||
|
exit "${retrace_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Collect retrace summary inputs
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
safe_case="$(printf '%s' '${{ matrix.case.name }}' | sed 's/[^A-Za-z0-9._-]/_/g')"
|
||||||
|
result_dir="android-retrace-result/${safe_case}-${{ matrix.backend.name }}"
|
||||||
|
mkdir -p "${result_dir}"
|
||||||
|
if [ -s "${{ matrix.case.golden }}" ]; then
|
||||||
|
cp "${{ matrix.case.golden }}" "${result_dir}/${safe_case}-${{ matrix.backend.name }}-golden.png"
|
||||||
|
fi
|
||||||
|
if [ -n "${{ matrix.case.alternate_golden || '' }}" ] && [ -s "${{ matrix.case.alternate_golden || '' }}" ]; then
|
||||||
|
cp "${{ matrix.case.alternate_golden || '' }}" "${result_dir}/${safe_case}-${{ matrix.backend.name }}-alternate-golden.png"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Collect emulator diagnostics
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
mkdir -p android-retrace-result/diagnostics
|
||||||
|
adb devices -l > android-retrace-result/diagnostics/adb-devices.txt || true
|
||||||
|
timeout 30 adb logcat -d -t 1000 > android-retrace-result/diagnostics/logcat.txt || true
|
||||||
|
if [ -f "${EMULATOR_LOG}" ]; then
|
||||||
|
cp "${EMULATOR_LOG}" android-retrace-result/diagnostics/emulator.log
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Stop Emulator
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
sh android-plugin/run-avd-ci.sh stop \
|
||||||
|
--avd-name "${AVD_NAME}" \
|
||||||
|
--emulator-log "${EMULATOR_LOG}" \
|
||||||
|
--pid-file "${EMULATOR_PID_FILE}"
|
||||||
|
|
||||||
|
- name: Upload Android retrace result
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: MobileGL-android-retrace-result-${{ env.date_today }}-${{ github.sha }}-${{ matrix.backend.name }}-${{ matrix.case.name }}
|
||||||
|
path: android-retrace-result/**
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
|
retrace-summary:
|
||||||
|
name: retrace summary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: retrace
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Set artifact metadata
|
||||||
|
run: |
|
||||||
|
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v7
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
|
||||||
|
- name: Download Android retrace results
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
pattern: MobileGL-android-retrace-result-*
|
||||||
|
path: retrace-artifacts
|
||||||
|
|
||||||
|
- name: Render retrace summary
|
||||||
|
run: |
|
||||||
|
node tools/trace_replay/render_retrace_summary.mjs \
|
||||||
|
--input retrace-artifacts \
|
||||||
|
--output-dir android-retrace-summary \
|
||||||
|
--title "MobileGL Android retrace overview" \
|
||||||
|
--group-label "Android Emulator" \
|
||||||
|
--html mobilegl-android-retrace-overview.html
|
||||||
|
|
||||||
|
- name: Upload Android retrace summary
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
path: android-retrace-summary/mobilegl-android-retrace-overview.html
|
||||||
|
archive: false
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
remove-artifact-clutter:
|
||||||
|
name: remove artifact clutter
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: retrace-summary
|
||||||
|
if: always()
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
steps:
|
||||||
|
- name: Delete intermediate Android retrace artifacts
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
declare -A failed_cases=()
|
||||||
|
while IFS= read -r job_name; do
|
||||||
|
case_name="${job_name#retrace (*, }"
|
||||||
|
case_name="${case_name%)}"
|
||||||
|
failed_cases["${case_name}"]=1
|
||||||
|
done < <(
|
||||||
|
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100" \
|
||||||
|
--jq '.jobs[] | select(.name | startswith("retrace (")) | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name'
|
||||||
|
)
|
||||||
|
|
||||||
|
if ((${#failed_cases[@]})); then
|
||||||
|
echo "Retaining fixtures for failed retrace case(s):"
|
||||||
|
printf ' %s\n' "${!failed_cases[@]}"
|
||||||
|
else
|
||||||
|
echo "All retrace jobs succeeded; no fixtures need to be retained."
|
||||||
|
fi
|
||||||
|
|
||||||
|
deleted=0
|
||||||
|
retained=0
|
||||||
|
while IFS=$'\t' read -r artifact_id artifact_name; do
|
||||||
|
if [[ "${artifact_name}" == MobileGL-trace-fixture-* ]]; then
|
||||||
|
case_name="${artifact_name#MobileGL-trace-fixture-}"
|
||||||
|
if [[ -v "failed_cases[${case_name}]" ]]; then
|
||||||
|
echo "Retaining ${artifact_name} (${artifact_id}) for failed retrace."
|
||||||
|
((retained += 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Deleting ${artifact_name} (${artifact_id})"
|
||||||
|
gh api --method DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}"
|
||||||
|
((deleted += 1))
|
||||||
|
done < <(
|
||||||
|
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
|
||||||
|
--jq '.artifacts[] | select(.name | startswith("MobileGL-trace-fixture-") or startswith("MobileGL-android-retrace-result-") or startswith("trace-fixture-") or startswith("retrace-result-")) | [.id, .name] | @tsv'
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Deleted ${deleted} intermediate Android artifact(s); retained ${retained} failed-retrace fixture(s)."
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
name: Benchmark
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- Feat/Backend-Direct-GLES
|
|
||||||
- Feat/Backend-Direct-Vulkan
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
benchmark:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
# BENCH_ROOT: ${{github.workspace}}/MobileGL/MG_Benchmark
|
|
||||||
BENCH_ROOT: ${{github.workspace}}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Set Swap Space
|
|
||||||
uses: pierotofy/set-swap-space@master
|
|
||||||
with:
|
|
||||||
swap-size-gb: 32
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Get CMake
|
|
||||||
uses: lukka/get-cmake@latest
|
|
||||||
|
|
||||||
- name: Prepare Vulkan SDK
|
|
||||||
uses: humbletim/setup-vulkan-sdk@v1.2.1
|
|
||||||
with:
|
|
||||||
vulkan-query-version: 1.4.304.1
|
|
||||||
vulkan-components: Vulkan-Headers, Vulkan-Loader
|
|
||||||
vulkan-use-cache: true
|
|
||||||
|
|
||||||
- name: Update glslang external sources
|
|
||||||
working-directory: ${{env.BENCH_ROOT}}/3rdparty/glslang
|
|
||||||
run: python update_glslang_sources.py
|
|
||||||
|
|
||||||
- name: Install clang-20
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev
|
|
||||||
|
|
||||||
- name: Show installed toolchain
|
|
||||||
run: |
|
|
||||||
clang-20 --version
|
|
||||||
clang++-20 --version
|
|
||||||
ld.lld-20 --version || ld.lld --version || true
|
|
||||||
dpkg -l 'libc++*' || true
|
|
||||||
|
|
||||||
- name: Configure CMake
|
|
||||||
working-directory: ${{env.BENCH_ROOT}}
|
|
||||||
run: cmake -S . -B build-bench -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON -DBENCHMARK_ENABLE_TESTING=OFF -DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
working-directory: ${{env.BENCH_ROOT}}/build-bench
|
|
||||||
run: cmake --build .
|
|
||||||
|
|
||||||
- name: Benchmark
|
|
||||||
working-directory: ${{env.BENCH_ROOT}}/build-bench/MobileGL/MG_Benchmark
|
|
||||||
run: ctest -V -C Release
|
|
||||||
+507
-21
@@ -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,511 @@ 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
|
||||||
|
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)."
|
||||||
|
|||||||
@@ -19,3 +19,9 @@ MobileGL/MG_Test/build
|
|||||||
.idea
|
.idea
|
||||||
MobileGL/MG*/build*
|
MobileGL/MG*/build*
|
||||||
MobileGL/MG*/cmake-build*
|
MobileGL/MG*/cmake-build*
|
||||||
|
/android-plugin/.gradle
|
||||||
|
/android-plugin/build
|
||||||
|
/android-plugin/app/build
|
||||||
|
/android-plugin/app/src/trace/jniLibs
|
||||||
|
/android-plugin/local.properties
|
||||||
|
tools/trace_replay/work/
|
||||||
|
|||||||
@@ -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
+102
-1
@@ -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)
|
||||||
@@ -179,11 +184,19 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp
|
MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp
|
MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp
|
||||||
|
|
||||||
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
MobileGL/MG_Util/BackendLoaders/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
|
||||||
|
|
||||||
@@ -211,6 +224,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
|
||||||
@@ -238,6 +252,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
|
||||||
@@ -270,6 +285,21 @@ 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()
|
||||||
|
|
||||||
set(MOBILEGL_LINK_LIBRARIES
|
set(MOBILEGL_LINK_LIBRARIES
|
||||||
glslang::glslang
|
glslang::glslang
|
||||||
spirv-cross-c
|
spirv-cross-c
|
||||||
@@ -328,8 +358,22 @@ 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(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
add_library(${CMAKE_PROJECT_NAME}_s STATIC
|
add_library(${CMAKE_PROJECT_NAME}_s STATIC
|
||||||
${SOURCE_FILES}
|
${SOURCE_FILES}
|
||||||
@@ -361,6 +405,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()
|
||||||
|
|
||||||
@@ -379,7 +424,48 @@ if (ANDROID)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT ANDROID)
|
if (APPLE AND NOT MOBILEGL_IOS)
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||||
|
"-framework Cocoa"
|
||||||
|
"-framework QuartzCore"
|
||||||
|
"-framework Foundation"
|
||||||
|
"-framework OpenGL"
|
||||||
|
objc)
|
||||||
|
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||||
|
"-framework Cocoa"
|
||||||
|
"-framework QuartzCore"
|
||||||
|
"-framework Foundation"
|
||||||
|
"-framework OpenGL"
|
||||||
|
objc)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (APPLE AND MOBILEGL_IOS)
|
||||||
|
target_compile_definitions(${CMAKE_PROJECT_NAME} PUBLIC MOBILEGL_IOS=1 _LIBCPP_DISABLE_AVAILABILITY)
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||||
|
"-framework CoreGraphics"
|
||||||
|
"-framework Foundation"
|
||||||
|
"-framework QuartzCore"
|
||||||
|
objc)
|
||||||
|
if (MOBILEGL_VULKAN_LIBRARY)
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC "${MOBILEGL_VULKAN_LIBRARY}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||||
|
target_compile_definitions(${CMAKE_PROJECT_NAME}_s PUBLIC MOBILEGL_IOS=1 _LIBCPP_DISABLE_AVAILABILITY)
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||||
|
"-framework CoreGraphics"
|
||||||
|
"-framework Foundation"
|
||||||
|
"-framework QuartzCore"
|
||||||
|
objc)
|
||||||
|
if (MOBILEGL_VULKAN_LIBRARY)
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC "${MOBILEGL_VULKAN_LIBRARY}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT ANDROID AND NOT MOBILEGL_IOS)
|
||||||
find_package(Vulkan)
|
find_package(Vulkan)
|
||||||
if (Vulkan_FOUND)
|
if (Vulkan_FOUND)
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Vulkan::Vulkan Vulkan::Headers)
|
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Vulkan::Vulkan Vulkan::Headers)
|
||||||
@@ -387,6 +473,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)
|
||||||
@@ -395,4 +492,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()
|
||||||
|
|||||||
+51
-1
@@ -14,9 +14,59 @@ 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, 3, 0, "-dev", VersionType::Development};
|
inline const Version CoreVersion = {26, 7, 0, "-dev", VersionType::Development};
|
||||||
inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
|
inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
|
||||||
inline const Uint64 CacheVersion = 0;
|
inline const Uint64 CacheVersion = 0;
|
||||||
|
|
||||||
extern BackendType ActiveBackendType;
|
extern BackendType ActiveBackendType;
|
||||||
|
|
||||||
|
// Feature toggles parsed once from environment variables in MG_ConfigLoader::Init()
|
||||||
|
// (ConfigLoader.cpp), before the accepted-env map is destroyed. All Bool fields share
|
||||||
|
// one truthy rule: the variable is set, non-empty, not "0", and not "false"
|
||||||
|
// (case-insensitive).
|
||||||
|
//
|
||||||
|
// Env variables intentionally NOT mirrored here (kept as live std::getenv at their
|
||||||
|
// call sites):
|
||||||
|
// - DISPLAY: X11 session variable, not MobileGL configuration.
|
||||||
|
// - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init
|
||||||
|
// (see MG_Util/Debug/Log.cpp).
|
||||||
|
struct FeaturesTable {
|
||||||
|
// MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries.
|
||||||
|
Bool DisableTimerQuery = false;
|
||||||
|
// MOBILEGL_USE_ANGLE: load ANGLE EGL/GLES libraries.
|
||||||
|
Bool UseAngle = false;
|
||||||
|
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||||
|
// MOBILEGL_TRACE_ANGLE_VARIANT: signed trace-APK ANGLE build short hash.
|
||||||
|
String TraceAngleVariant;
|
||||||
|
#endif
|
||||||
|
// MOBILEGL_DISABLE_SUBGROUP: force-disable Vulkan shader subgroup support.
|
||||||
|
Bool DisableSubgroup = false;
|
||||||
|
// MOBILEGL_MAGMA_R11G11B10F_FALLBACK: use fallback format for R11G11B10F on Vulkan.
|
||||||
|
Bool MagmaR11G11B10FFallback = false;
|
||||||
|
// MOBILEGL_MAGMA_FRAMESINFLIGHT: requested Magma frames in flight, defaulting to 3.
|
||||||
|
Uint32 MagmaFramesInFlight = 3;
|
||||||
|
// MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER: avoid mipmap min filters in samplers,
|
||||||
|
// resolves certain rendering bugs on ANGLE + llvmpipe.
|
||||||
|
Bool AvoidSamplerMipmapMinFilter = false;
|
||||||
|
// MOBILEGL_COHERENT_AS_FLUSH: app-compat for engines (e.g. Flywheel) that write
|
||||||
|
// GPU-read data through persistent GL_MAP_FLUSH_EXPLICIT_BIT maps they never
|
||||||
|
// flush. Persistent FLUSH_EXPLICIT map requests are rewritten to coherent
|
||||||
|
// semantics: writes reach the backend without glFlushMappedBufferRange, and
|
||||||
|
// flush calls on rewritten maps become error-free no-ops. Non-persistent maps
|
||||||
|
// keep spec FLUSH_EXPLICIT behavior.
|
||||||
|
Bool CoherentAsFlush = false;
|
||||||
|
// MOBILEGL_TRACE_SKIP_AUTODESTROY: skip teardown in the ELF destructor (Init.cpp).
|
||||||
|
Bool TraceSkipAutodestroy = false;
|
||||||
|
// MOBILEGL_DISABLE_UBO_RING: force the DirectGLES global-UBO upload back to the
|
||||||
|
// per-draw glBufferSubData path instead of the persistent-mapped ring allocator
|
||||||
|
// (negative control / driver-bug escape hatch).
|
||||||
|
Bool DisableUboRing = false;
|
||||||
|
// MOBILEGL_RELAXED_SEMANTICS: relax strict core-profile rules (e.g. VAO-0 draws,
|
||||||
|
// texture-name reuse after delete) even on contexts that explicitly requested a core
|
||||||
|
// profile. Without it, relaxed semantics still apply to every context that did not
|
||||||
|
// explicitly request a core profile via EGL_CONTEXT_OPENGL_PROFILE_MASK / a >=3.1
|
||||||
|
// version request.
|
||||||
|
Bool RelaxedSemantics = false;
|
||||||
|
};
|
||||||
|
extern FeaturesTable Features;
|
||||||
} // namespace MobileGL::MG_Config
|
} // namespace MobileGL::MG_Config
|
||||||
|
|||||||
@@ -8,6 +8,19 @@
|
|||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
extern char** environ;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Config {
|
||||||
|
// Zero/default-initialized at static-init time (all fields have constexpr-friendly
|
||||||
|
// defaults), so it is safe to read even if MG_ConfigLoader::Init has not run yet.
|
||||||
|
FeaturesTable Features;
|
||||||
|
} // namespace MobileGL::MG_Config
|
||||||
|
|
||||||
namespace MobileGL::MG_ConfigLoader {
|
namespace MobileGL::MG_ConfigLoader {
|
||||||
static UniquePtr<UnorderedMap<String, String>> acceptedEnvVariablesMap;
|
static UniquePtr<UnorderedMap<String, String>> acceptedEnvVariablesMap;
|
||||||
|
|
||||||
@@ -56,6 +69,62 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unified truthy rule for boolean feature env variables: set, non-empty, not "0",
|
||||||
|
// and not "false" (case-insensitive).
|
||||||
|
static Bool IsTruthyValue(const String& value) {
|
||||||
|
if (value.empty() || value == "0") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String lowered = value;
|
||||||
|
std::transform(lowered.begin(), lowered.end(), lowered.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
return lowered != "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Bool QueryEnvFlag(const String& key) {
|
||||||
|
auto it = acceptedEnvVariablesMap->find(key);
|
||||||
|
return it != acceptedEnvVariablesMap->end() && IsTruthyValue(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Uint32 QueryEnvUint32(const String& key, Uint32 defaultValue, Uint32 minValue, Uint32 maxValue) {
|
||||||
|
auto it = acceptedEnvVariablesMap->find(key);
|
||||||
|
if (it == acceptedEnvVariablesMap->end()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& value = it->second;
|
||||||
|
char* parseEnd = nullptr;
|
||||||
|
errno = 0;
|
||||||
|
const unsigned long parsedValue = std::strtoul(value.c_str(), &parseEnd, 10);
|
||||||
|
if (parseEnd == value.c_str() || *parseEnd != '\0' || errno == ERANGE || parsedValue < minValue ||
|
||||||
|
parsedValue > maxValue) {
|
||||||
|
MGLOG_W("Config: Ignoring invalid env variable %s='%s'; expected an integer in range [%u, %u], "
|
||||||
|
"using default %u",
|
||||||
|
key.c_str(), value.c_str(), minValue, maxValue, defaultValue);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<Uint32>(parsedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void InitFeatures() {
|
||||||
|
auto& features = MG_Config::Features;
|
||||||
|
features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY");
|
||||||
|
features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE");
|
||||||
|
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||||
|
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
||||||
|
#endif
|
||||||
|
features.DisableSubgroup = QueryEnvFlag("MOBILEGL_DISABLE_SUBGROUP");
|
||||||
|
features.MagmaR11G11B10FFallback = QueryEnvFlag("MOBILEGL_MAGMA_R11G11B10F_FALLBACK");
|
||||||
|
features.MagmaFramesInFlight = QueryEnvUint32("MOBILEGL_MAGMA_FRAMESINFLIGHT", 3, 1, 64);
|
||||||
|
features.AvoidSamplerMipmapMinFilter =
|
||||||
|
QueryEnvFlag("MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER");
|
||||||
|
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
|
||||||
|
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
|
||||||
|
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
||||||
|
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||||
|
}
|
||||||
|
|
||||||
inline void InitBackendType() {
|
inline void InitBackendType() {
|
||||||
String backendTypeStr;
|
String backendTypeStr;
|
||||||
QueryEnvVariable("MOBILEGL_BACKEND_TYPE", backendTypeStr, "DirectGLES");
|
QueryEnvVariable("MOBILEGL_BACKEND_TYPE", backendTypeStr, "DirectGLES");
|
||||||
@@ -77,6 +146,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
InitializeAcceptedEnvVariables();
|
InitializeAcceptedEnvVariables();
|
||||||
|
|
||||||
InitBackendType();
|
InitBackendType();
|
||||||
|
InitFeatures();
|
||||||
|
|
||||||
// Destroy the map since we won't need it anymore
|
// Destroy the map since we won't need it anymore
|
||||||
acceptedEnvVariablesMap.reset();
|
acceptedEnvVariablesMap.reset();
|
||||||
|
|||||||
+17
-9
@@ -32,9 +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
|
||||||
|
|
||||||
// ====================== MobileGL configurations ======================= //
|
// ====================== MobileGL configurations ======================= //
|
||||||
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_DEBUG
|
#ifndef MOBILEGL_LOG_ACTIVE_LEVEL
|
||||||
|
#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO
|
||||||
|
#endif
|
||||||
|
|
||||||
#define MOBILEGL_LOG_ENABLE_CONSOLE 0
|
#define MOBILEGL_LOG_ENABLE_CONSOLE 0
|
||||||
#define MOBILEGL_LOG_ENABLE_FILE 1
|
#define MOBILEGL_LOG_ENABLE_FILE 1
|
||||||
@@ -63,11 +67,15 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// =============================== Utils ================================ //
|
// =============================== Utils ================================ //
|
||||||
#define MOBILEGL_ASSERT(condition, ...) \
|
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||||
do { \
|
#define MOBILEGL_ASSERT(condition, ...) \
|
||||||
if (!(condition)) { \
|
do { \
|
||||||
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
|
if (!(condition)) { \
|
||||||
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
|
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
|
||||||
TRAP; \
|
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
|
||||||
} \
|
TRAP; \
|
||||||
} while (0)
|
} \
|
||||||
|
} while (0)
|
||||||
|
#else
|
||||||
|
#define MOBILEGL_ASSERT(condition, ...)
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
@@ -108,10 +110,32 @@
|
|||||||
#define VK_USE_PLATFORM_WIN32_KHR
|
#define VK_USE_PLATFORM_WIN32_KHR
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#define VK_USE_PLATFORM_METAL_EXT
|
#define VK_USE_PLATFORM_METAL_EXT
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#define VK_USE_PLATFORM_XLIB_KHR
|
||||||
|
typedef struct _XDisplay Display;
|
||||||
|
typedef unsigned long XID;
|
||||||
|
typedef XID Window;
|
||||||
|
typedef unsigned long VisualID;
|
||||||
#else
|
#else
|
||||||
#warning "VK_USE_PLATFORM_*_KHR not defined for this platform!"
|
#warning "VK_USE_PLATFORM_*_KHR not defined for this platform!"
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||||
|
#pragma push_macro("Bool")
|
||||||
|
#pragma push_macro("None")
|
||||||
|
#pragma push_macro("Always")
|
||||||
|
#pragma push_macro("Status")
|
||||||
|
#pragma push_macro("LSBFirst")
|
||||||
|
#pragma push_macro("DestroyAll")
|
||||||
|
#endif
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
|
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||||
|
#pragma pop_macro("DestroyAll")
|
||||||
|
#pragma pop_macro("LSBFirst")
|
||||||
|
#pragma pop_macro("Status")
|
||||||
|
#pragma pop_macro("Always")
|
||||||
|
#pragma pop_macro("None")
|
||||||
|
#pragma pop_macro("Bool")
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
|
|||||||
+43
-10
@@ -15,7 +15,39 @@
|
|||||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
|
namespace {
|
||||||
|
Bool g_isInitialized = false;
|
||||||
|
|
||||||
|
void DestroyImpl(Bool logLifecycle) {
|
||||||
|
if (!g_isInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logLifecycle) {
|
||||||
|
MGLOG_I("MobileGL closing...");
|
||||||
|
}
|
||||||
|
glslang::FinalizeProcess();
|
||||||
|
MG_Backend::pActiveBackendObject.reset();
|
||||||
|
MG_State::pGLContext.reset();
|
||||||
|
MG_State::pEGLContext.reset();
|
||||||
|
MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.reset();
|
||||||
|
MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.reset();
|
||||||
|
MG_Backend::gBackendFunctionsTable = {};
|
||||||
|
g_isInitialized = false;
|
||||||
|
if (logLifecycle) {
|
||||||
|
MG_Util::Debug::Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add and use Destroy functions for other subsystems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Initialize() {
|
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,19 +60,12 @@ 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 Destroy() {
|
||||||
MGLOG_I("MobileGL closing...");
|
DestroyImpl(true);
|
||||||
glslang::FinalizeProcess();
|
|
||||||
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__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
@@ -49,7 +74,15 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((destructor)) static void AutoDestroy() {
|
__attribute__((destructor)) static void AutoDestroy() {
|
||||||
Destroy();
|
if (MG_Config::Features.TraceSkipAutodestroy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
// macOS injected dylibs can run destructors after logging/backend static state is already torn down.
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
DestroyImpl(false);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,164 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "BackendObject.h"
|
#include "BackendObject.h"
|
||||||
|
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend {
|
namespace MobileGL::MG_Backend {
|
||||||
namespace {
|
namespace {
|
||||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||||
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
(void)dpy;
|
||||||
|
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread::id CurrentThreadKey() {
|
std::thread::id CurrentThreadKey() {
|
||||||
return std::this_thread::get_id();
|
return std::this_thread::get_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache,
|
||||||
|
SizeT targetIndex,
|
||||||
|
SizeT formatIndex,
|
||||||
|
FormatCapability capability) {
|
||||||
|
if (HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) return "Full";
|
||||||
|
if (HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex], capability)) return "Caveat";
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeT GetPrintedFormatNameWidth() {
|
||||||
|
SizeT width = 0;
|
||||||
|
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||||
|
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||||
|
width = std::max(width, MG_Util::ConvertTextureInternalFormatToString(format).size());
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeT GetCapabilityColumnWidth(FormatCapability capability) {
|
||||||
|
SizeT width = std::strlen(GetFormatCapabilityName(capability));
|
||||||
|
width = std::max<SizeT>(width, std::strlen("Caveat"));
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
String BuildFormatCapabilityHeader(SizeT formatNameWidth) {
|
||||||
|
std::ostringstream line;
|
||||||
|
line << std::left << std::setw(static_cast<Int>(formatNameWidth)) << "";
|
||||||
|
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||||
|
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||||
|
<< GetFormatCapabilityName(capability);
|
||||||
|
}
|
||||||
|
return line.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
String BuildFormatCapabilityRow(const FormatCapabilityCache& cache,
|
||||||
|
SizeT targetIndex,
|
||||||
|
SizeT formatIndex,
|
||||||
|
SizeT formatNameWidth) {
|
||||||
|
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||||
|
std::ostringstream line;
|
||||||
|
line << std::left << std::setw(static_cast<Int>(formatNameWidth))
|
||||||
|
<< MG_Util::ConvertTextureInternalFormatToString(format);
|
||||||
|
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||||
|
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||||
|
<< GetFormatCapabilitySupportString(cache, targetIndex, formatIndex, capability);
|
||||||
|
}
|
||||||
|
return line.str();
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void FormatCapabilityCache::Clear() {
|
||||||
|
for (auto& row : FullCaps) {
|
||||||
|
row.fill(FormatCapabilityFlags{});
|
||||||
|
}
|
||||||
|
for (auto& row : CaveatCaps) {
|
||||||
|
row.fill(FormatCapabilityFlags{});
|
||||||
|
}
|
||||||
|
for (auto& row : SampleCounts) {
|
||||||
|
for (auto& counts : row) {
|
||||||
|
counts.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability) {
|
||||||
|
return static_cast<Bool>(caps & capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeT GetFormatCapabilityTargetIndex(TextureTarget target) {
|
||||||
|
if (target == TextureTarget::Unknown || static_cast<Int>(target) < 0 ||
|
||||||
|
static_cast<SizeT>(target) >= kFormatCapabilityTextureTargetCount) {
|
||||||
|
return kFormatCapabilityTargetCount;
|
||||||
|
}
|
||||||
|
return static_cast<SizeT>(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeT GetRenderbufferFormatCapabilityTargetIndex() {
|
||||||
|
return kFormatCapabilityRenderbufferTargetIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetFormatCapabilityName(FormatCapability capability) {
|
||||||
|
switch (capability) {
|
||||||
|
case FormatCapability::Creatable:
|
||||||
|
return "Creatable";
|
||||||
|
case FormatCapability::Sampled:
|
||||||
|
return "Sampled";
|
||||||
|
case FormatCapability::LinearFilter:
|
||||||
|
return "LinearFilter";
|
||||||
|
case FormatCapability::GenerateMipmap:
|
||||||
|
return "GenerateMipmap";
|
||||||
|
case FormatCapability::TextureGather:
|
||||||
|
return "TextureGather";
|
||||||
|
case FormatCapability::TextureShadow:
|
||||||
|
return "TextureShadow";
|
||||||
|
case FormatCapability::FramebufferRenderable:
|
||||||
|
return "FramebufferRenderable";
|
||||||
|
case FormatCapability::FramebufferLayered:
|
||||||
|
return "FramebufferLayered";
|
||||||
|
case FormatCapability::MultisampleTexture:
|
||||||
|
return "MultisampleTexture";
|
||||||
|
case FormatCapability::MultisampleRenderbuffer:
|
||||||
|
return "MultisampleRenderbuffer";
|
||||||
|
case FormatCapability::ColorAttachment:
|
||||||
|
return "ColorAttachment";
|
||||||
|
case FormatCapability::DepthAttachment:
|
||||||
|
return "DepthAttachment";
|
||||||
|
case FormatCapability::StencilAttachment:
|
||||||
|
return "StencilAttachment";
|
||||||
|
case FormatCapability::TextureBuffer:
|
||||||
|
return "TextureBuffer";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
String GetFormatCapabilityTargetName(SizeT targetIndex) {
|
||||||
|
if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) {
|
||||||
|
return "Renderbuffer";
|
||||||
|
}
|
||||||
|
if (targetIndex >= kFormatCapabilityTextureTargetCount) {
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
return MG_Util::ConvertTextureTargetToString(static_cast<TextureTarget>(targetIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintFormatCapabilities(const FormatCapabilityCache& cache) {
|
||||||
|
const SizeT formatNameWidth = GetPrintedFormatNameWidth();
|
||||||
|
|
||||||
|
MGLOG_D("Backend format capabilities:");
|
||||||
|
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
|
||||||
|
MGLOG_D("");
|
||||||
|
const String targetName = GetFormatCapabilityTargetName(targetIndex);
|
||||||
|
MGLOG_D("- %s", targetName.c_str());
|
||||||
|
const String header = BuildFormatCapabilityHeader(formatNameWidth);
|
||||||
|
MGLOG_D("%s", header.c_str());
|
||||||
|
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||||
|
const String row = BuildFormatCapabilityRow(cache, targetIndex, formatIndex, formatNameWidth);
|
||||||
|
MGLOG_D("%s", row.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Bool BackendObject::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
Bool BackendObject::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
if (dpy == EGL_NO_DISPLAY) {
|
if (dpy == EGL_NO_DISPLAY) {
|
||||||
@@ -42,28 +188,116 @@ namespace MobileGL::MG_Backend {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool BackendObject::CreateEGLWindowSurface(const WindowHandle& handle) {
|
Bool BackendObject::CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
return RegisterEGLWindowSurface(surface, handle) && ActivateEGLSurface(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BackendObject::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||||
|
if (surfaceIt == m_eglSurfaces.end() || surfaceIt->second.Kind != SurfaceKind::Window) {
|
||||||
|
MGLOG_E("ResizeEGLWindowSurface failed: no window surface is initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
surfaceIt->second.Window.Width = width;
|
||||||
|
surfaceIt->second.Window.Height = height;
|
||||||
|
if (m_eglSurface == surface) {
|
||||||
|
m_windowHandle.Width = width;
|
||||||
|
m_windowHandle.Height = height;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BackendObject::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
return RegisterEGLPbufferSurface(surface, width, height) && ActivateEGLSurface(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BackendObject::RegisterEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
if (!m_eglDisplayInitialized) {
|
if (!m_eglDisplayInitialized) {
|
||||||
MGLOG_E("CreateEGLWindowSurface failed: EGL display is not initialized");
|
MGLOG_E("RegisterEGLWindowSurface failed: EGL display is not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (surface == EGL_NO_SURFACE) {
|
||||||
|
MGLOG_E("RegisterEGLWindowSurface failed: invalid EGLSurface");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (handle.Backend == WindowBackend::Unknown || !handle.Handle) {
|
if (handle.Backend == WindowBackend::Unknown || !handle.Handle) {
|
||||||
MGLOG_E("CreateEGLWindowSurface failed: invalid native window handle");
|
MGLOG_E("RegisterEGLWindowSurface failed: invalid native window handle");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_eglWindowSurfaceInitialized && m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle) {
|
auto& state = m_eglSurfaces[surface];
|
||||||
|
state = EGLSurfaceState{
|
||||||
|
.Kind = SurfaceKind::Window,
|
||||||
|
.Window = handle,
|
||||||
|
.Width = static_cast<EGLint>(std::max<Uint32>(handle.Width, 1)),
|
||||||
|
.Height = static_cast<EGLint>(std::max<Uint32>(handle.Height, 1)),
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BackendObject::RegisterEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
if (!m_eglDisplayInitialized) {
|
||||||
|
MGLOG_E("RegisterEGLPbufferSurface failed: EGL display is not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (surface == EGL_NO_SURFACE) {
|
||||||
|
MGLOG_E("RegisterEGLPbufferSurface failed: invalid EGLSurface");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
MGLOG_E("RegisterEGLPbufferSurface failed: invalid size %dx%d", width, height);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_eglSurfaces[surface] = EGLSurfaceState{
|
||||||
|
.Kind = SurfaceKind::Pbuffer,
|
||||||
|
.Width = width,
|
||||||
|
.Height = height,
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BackendObject::EGLSurfaceState* BackendObject::GetRegisteredEGLSurface(EGLSurface surface) const {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||||
|
return surfaceIt == m_eglSurfaces.end() ? nullptr : &surfaceIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BackendObject::ActivateEGLSurface(EGLSurface surface) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
const auto* surfaceState = GetRegisteredEGLSurface(surface);
|
||||||
|
if (!surfaceState) {
|
||||||
|
MGLOG_E("ActivateEGLSurface failed: EGL surface is not registered");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_eglSurfaceInitialized && m_eglSurface == surface) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetWindowHandle(handle);
|
if (surfaceState->Kind == SurfaceKind::Window) {
|
||||||
if (!InitWindowSurface()) {
|
SetWindowHandle(surfaceState->Window);
|
||||||
MGLOG_E("CreateEGLWindowSurface failed: backend InitWindowSurface failed");
|
if (!InitWindowSurface()) {
|
||||||
|
MGLOG_E("ActivateEGLSurface failed: backend InitWindowSurface failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (surfaceState->Kind == SurfaceKind::Pbuffer) {
|
||||||
|
if (!InitPbufferSurface(surfaceState->Width, surfaceState->Height)) {
|
||||||
|
MGLOG_E("ActivateEGLSurface failed: backend InitPbufferSurface failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MGLOG_E("ActivateEGLSurface failed: unsupported surface kind");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_eglWindowSurfaceInitialized = true;
|
m_eglSurface = surface;
|
||||||
|
m_eglSurfaceInitialized = true;
|
||||||
|
m_eglSurfaceKind = surfaceState->Kind;
|
||||||
m_eglCurrentThreads.clear();
|
m_eglCurrentThreads.clear();
|
||||||
m_backendCapabilitiesInitialized = false;
|
m_backendCapabilitiesInitialized = false;
|
||||||
return true;
|
return true;
|
||||||
@@ -73,7 +307,7 @@ namespace MobileGL::MG_Backend {
|
|||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
const auto threadKey = CurrentThreadKey();
|
const auto threadKey = CurrentThreadKey();
|
||||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
||||||
m_eglCurrentThreads.erase(threadKey);
|
ReleaseEGLCurrentThread(threadKey);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +315,22 @@ namespace MobileGL::MG_Backend {
|
|||||||
MGLOG_E("MakeEGLCurrent failed: EGL display mismatch or not initialized");
|
MGLOG_E("MakeEGLCurrent failed: EGL display mismatch or not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!m_eglWindowSurfaceInitialized) {
|
if (!m_eglSurfaceInitialized) {
|
||||||
MGLOG_E("MakeEGLCurrent failed: EGL window surface is not initialized");
|
if (draw != read || !ActivateEGLSurface(draw)) {
|
||||||
|
MGLOG_E("MakeEGLCurrent failed: EGL surface is not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!GetRegisteredEGLSurface(draw) || !GetRegisteredEGLSurface(read)) {
|
||||||
|
MGLOG_E("MakeEGLCurrent failed: EGL surface is not registered");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (draw != read) {
|
||||||
|
MGLOG_E("MakeEGLCurrent failed: separate draw/read surfaces are not supported");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (draw != m_eglSurface && !ActivateEGLSurface(draw)) {
|
||||||
|
MGLOG_E("MakeEGLCurrent failed: EGL surface is not backed by this backend");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE || ctx == EGL_NO_CONTEXT) {
|
if (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE || ctx == EGL_NO_CONTEXT) {
|
||||||
@@ -98,14 +346,23 @@ namespace MobileGL::MG_Backend {
|
|||||||
m_backendCapabilitiesInitialized = true;
|
m_backendCapabilitiesInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_eglCurrentThreads[threadKey] = true;
|
ReleaseEGLCurrentThread(threadKey);
|
||||||
|
m_eglCurrentThreads[threadKey] = EGLCurrentState{
|
||||||
|
.Display = dpy,
|
||||||
|
.DrawSurface = draw,
|
||||||
|
.ReadSurface = read,
|
||||||
|
.Context = ctx,
|
||||||
|
};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendObject::ResetEGLRuntimeState() {
|
void BackendObject::ResetEGLRuntimeState() {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
m_eglWindowSurfaceInitialized = false;
|
m_eglSurfaceInitialized = false;
|
||||||
m_backendCapabilitiesInitialized = false;
|
m_backendCapabilitiesInitialized = false;
|
||||||
|
m_eglSurfaceKind = SurfaceKind::None;
|
||||||
|
m_eglSurface = EGL_NO_SURFACE;
|
||||||
|
m_windowHandle = {};
|
||||||
m_eglCurrentThreads.clear();
|
m_eglCurrentThreads.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +372,17 @@ namespace MobileGL::MG_Backend {
|
|||||||
MGLOG_E("SwapEGLBuffers failed: EGL display mismatch or not initialized");
|
MGLOG_E("SwapEGLBuffers failed: EGL display mismatch or not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (m_eglCurrentThreads.find(CurrentThreadKey()) == m_eglCurrentThreads.end()) {
|
const auto currentIt = m_eglCurrentThreads.find(CurrentThreadKey());
|
||||||
|
if (currentIt == m_eglCurrentThreads.end()) {
|
||||||
MGLOG_E("SwapEGLBuffers failed: no current context attached");
|
MGLOG_E("SwapEGLBuffers failed: no current context attached");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!m_eglWindowSurfaceInitialized || draw == EGL_NO_SURFACE) {
|
if (currentIt->second.Display != dpy || currentIt->second.DrawSurface != draw ||
|
||||||
|
currentIt->second.Context == EGL_NO_CONTEXT) {
|
||||||
|
MGLOG_E("SwapEGLBuffers failed: draw surface is not current on this thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!m_eglSurfaceInitialized || draw == EGL_NO_SURFACE || draw != m_eglSurface) {
|
||||||
MGLOG_E("SwapEGLBuffers failed: invalid draw surface");
|
MGLOG_E("SwapEGLBuffers failed: invalid draw surface");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -134,8 +397,99 @@ namespace MobileGL::MG_Backend {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BackendObject::SetEGLSwapInterval(Int interval) {
|
||||||
|
const auto& backendFunctions = GetBackendFunctions();
|
||||||
|
if (backendFunctions.SetSwapInterval) {
|
||||||
|
backendFunctions.SetSwapInterval(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BackendObject::IsEGLSurfaceCurrent(EGLSurface surface) const {
|
||||||
|
if (surface == EGL_NO_SURFACE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& current : m_eglCurrentThreads) {
|
||||||
|
if (current.second.DrawSurface == surface || current.second.ReadSurface == surface) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendObject::DestroyPendingEGLSurfaceIfUnused(EGLSurface surface) {
|
||||||
|
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||||
|
if (surfaceIt == m_eglSurfaces.end() || !surfaceIt->second.DestroyPending ||
|
||||||
|
IsEGLSurfaceCurrent(surface)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_eglSurfaces.erase(surfaceIt);
|
||||||
|
if (m_eglSurface == surface) {
|
||||||
|
OnEGLSurfaceReleased(surface);
|
||||||
|
ResetEGLRuntimeState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendObject::ReleaseEGLCurrentThread(const std::thread::id& threadKey) {
|
||||||
|
auto currentIt = m_eglCurrentThreads.find(threadKey);
|
||||||
|
if (currentIt == m_eglCurrentThreads.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EGLSurface drawSurface = currentIt->second.DrawSurface;
|
||||||
|
const EGLSurface readSurface = currentIt->second.ReadSurface;
|
||||||
|
m_eglCurrentThreads.erase(currentIt);
|
||||||
|
DestroyPendingEGLSurfaceIfUnused(drawSurface);
|
||||||
|
DestroyPendingEGLSurfaceIfUnused(readSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendObject::ReleaseEGLSurface(EGLSurface surface) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
auto surfaceIt = m_eglSurfaces.find(surface);
|
||||||
|
if (surfaceIt == m_eglSurfaces.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsEGLSurfaceCurrent(surface)) {
|
||||||
|
surfaceIt->second.DestroyPending = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_eglSurfaces.erase(surfaceIt);
|
||||||
|
if (m_eglSurface == surface) {
|
||||||
|
OnEGLSurfaceReleased(surface);
|
||||||
|
ResetEGLRuntimeState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendObject::ReleaseEGLResources() {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
ResetEGLRuntimeState();
|
||||||
|
m_eglSurfaces.clear();
|
||||||
|
m_eglDisplay = EGL_NO_DISPLAY;
|
||||||
|
m_eglDisplayInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
void BackendObject::SetWindowHandle(const WindowHandle& handle) {
|
void BackendObject::SetWindowHandle(const WindowHandle& handle) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
m_windowHandle = handle;
|
m_windowHandle = handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FormatCapabilityCache& BackendObject::GetFormatCapabilities() const {
|
||||||
|
return m_formatCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatCapabilityCache& BackendObject::MutableFormatCapabilities() {
|
||||||
|
return m_formatCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BackendObject::InitPbufferSurface(EGLint width, EGLint height) {
|
||||||
|
(void)width;
|
||||||
|
(void)height;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendObject::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||||
|
(void)surface;
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Backend
|
} // namespace MobileGL::MG_Backend
|
||||||
|
|||||||
@@ -8,8 +8,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
|
namespace MG_State::GLState {
|
||||||
|
class FramebufferObject;
|
||||||
|
class ITextureObject;
|
||||||
|
}
|
||||||
|
|
||||||
enum class BackendType {
|
enum class BackendType {
|
||||||
DirectGLES,
|
DirectGLES,
|
||||||
DirectVulkan,
|
DirectVulkan,
|
||||||
@@ -18,11 +24,88 @@ namespace MobileGL {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace MG_Backend {
|
namespace MG_Backend {
|
||||||
|
enum class FormatCapability : Uint64 {
|
||||||
|
Creatable = 1ull << 0,
|
||||||
|
|
||||||
|
Sampled = 1ull << 1,
|
||||||
|
LinearFilter = 1ull << 2,
|
||||||
|
GenerateMipmap = 1ull << 3,
|
||||||
|
TextureGather = 1ull << 4,
|
||||||
|
TextureShadow = 1ull << 5,
|
||||||
|
|
||||||
|
FramebufferRenderable = 1ull << 6,
|
||||||
|
FramebufferLayered = 1ull << 7,
|
||||||
|
MultisampleTexture = 1ull << 8,
|
||||||
|
MultisampleRenderbuffer = 1ull << 9,
|
||||||
|
|
||||||
|
ColorAttachment = 1ull << 10,
|
||||||
|
DepthAttachment = 1ull << 11,
|
||||||
|
StencilAttachment = 1ull << 12,
|
||||||
|
|
||||||
|
TextureBuffer = 1ull << 13
|
||||||
|
};
|
||||||
|
|
||||||
|
using FormatCapabilityFlags = Flags<FormatCapability>;
|
||||||
|
|
||||||
|
inline constexpr Array<FormatCapability, 14> kReportedFormatCapabilities = {
|
||||||
|
FormatCapability::Creatable,
|
||||||
|
FormatCapability::Sampled,
|
||||||
|
FormatCapability::LinearFilter,
|
||||||
|
FormatCapability::GenerateMipmap,
|
||||||
|
FormatCapability::TextureGather,
|
||||||
|
FormatCapability::TextureShadow,
|
||||||
|
FormatCapability::FramebufferRenderable,
|
||||||
|
FormatCapability::FramebufferLayered,
|
||||||
|
FormatCapability::MultisampleTexture,
|
||||||
|
FormatCapability::MultisampleRenderbuffer,
|
||||||
|
FormatCapability::ColorAttachment,
|
||||||
|
FormatCapability::DepthAttachment,
|
||||||
|
FormatCapability::StencilAttachment,
|
||||||
|
FormatCapability::TextureBuffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr SizeT kFormatCapabilityTextureTargetCount =
|
||||||
|
static_cast<SizeT>(TextureTarget::TextureTargetCount);
|
||||||
|
inline constexpr SizeT kFormatCapabilityRenderbufferTargetIndex = kFormatCapabilityTextureTargetCount;
|
||||||
|
inline constexpr SizeT kFormatCapabilityTargetCount = kFormatCapabilityTextureTargetCount + 1;
|
||||||
|
inline constexpr SizeT kFormatCapabilityFormatCount =
|
||||||
|
static_cast<SizeT>(TextureInternalFormat::TextureInternalFormatCount);
|
||||||
|
|
||||||
|
using FormatCapabilityTable =
|
||||||
|
Array<Array<FormatCapabilityFlags, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>;
|
||||||
|
using FormatSampleCountTable =
|
||||||
|
Array<Array<Vector<Int>, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>;
|
||||||
|
|
||||||
|
struct FormatCapabilityCache {
|
||||||
|
FormatCapabilityTable FullCaps{};
|
||||||
|
FormatCapabilityTable CaveatCaps{};
|
||||||
|
FormatSampleCountTable SampleCounts{};
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability);
|
||||||
|
SizeT GetFormatCapabilityTargetIndex(TextureTarget target);
|
||||||
|
SizeT GetRenderbufferFormatCapabilityTargetIndex();
|
||||||
|
const char* GetFormatCapabilityName(FormatCapability capability);
|
||||||
|
String GetFormatCapabilityTargetName(SizeT targetIndex);
|
||||||
|
void PrintFormatCapabilities(const FormatCapabilityCache& cache);
|
||||||
|
|
||||||
|
// Opaque backend fence-sync handle, created by GLFunctionsTable::FenceSync
|
||||||
|
// and released by GLFunctionsTable::DeleteSync.
|
||||||
|
using BackendSyncHandle = void*;
|
||||||
|
|
||||||
|
// Opaque backend timer-query handle, created by
|
||||||
|
// GLFunctionsTable::BeginTimeElapsedQuery / QueryCounterTimestamp and
|
||||||
|
// released by GLFunctionsTable::DeleteBackendQuery.
|
||||||
|
using BackendQueryHandle = void*;
|
||||||
|
|
||||||
struct GLFunctionsTable {
|
struct GLFunctionsTable {
|
||||||
void (*DrawArrays)(GLenum mode, GLint first, GLsizei count);
|
void (*DrawArrays)(GLenum mode, GLint first, GLsizei count);
|
||||||
void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||||
void (*DrawElementsBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void (*DrawElementsBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLint basevertex);
|
GLint basevertex);
|
||||||
|
void (*MultiDrawArrays)(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||||
void (*MultiDrawElements)(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
void (*MultiDrawElements)(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount);
|
GLsizei drawcount);
|
||||||
void (*MultiDrawElementsBaseVertex)(GLenum mode, const GLsizei* count, GLenum type,
|
void (*MultiDrawElementsBaseVertex)(GLenum mode, const GLsizei* count, GLenum type,
|
||||||
@@ -31,6 +114,10 @@ namespace MobileGL {
|
|||||||
void (*MultiDrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
void (*MultiDrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
||||||
GLsizei stride);
|
GLsizei stride);
|
||||||
void (*MultiDrawArraysIndirect)(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void (*MultiDrawArraysIndirect)(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
|
void (*MultiDrawElementsIndirectCount)(GLenum mode, GLenum type, const void* indirect,
|
||||||
|
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
|
||||||
|
void (*MultiDrawArraysIndirectCount)(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
void (*DrawRangeElementsBaseVertex)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void (*DrawRangeElementsBaseVertex)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex);
|
const void* indices, GLint basevertex);
|
||||||
void (*DrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void (*DrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
@@ -54,29 +141,163 @@ namespace MobileGL {
|
|||||||
void (*ClearBufferfv)(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void (*ClearBufferfv)(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void (*ClearBufferuiv)(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
void (*ClearBufferuiv)(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
void (*ClearBufferiv)(GLenum buffer, GLint drawbuffer, const GLint* value);
|
void (*ClearBufferiv)(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
|
void (*ClearNamedFramebufferfv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
|
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void (*BlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
void (*BlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
||||||
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||||
|
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||||
|
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||||
|
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||||
|
GLbitfield mask, GLenum filter);
|
||||||
void (*CopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
void (*CopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height, GLint border);
|
GLsizei height, GLint border);
|
||||||
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
||||||
GLsizei width, GLsizei height);
|
GLsizei width, GLsizei height);
|
||||||
|
void (*CopyImageSubData)(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||||
|
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||||
|
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||||
|
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||||
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||||
void (*GenerateMipmap)(GLenum target);
|
void (*GenerateMipmap)(GLenum target);
|
||||||
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||||
void* pixels);
|
void* pixels);
|
||||||
void (*GetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
void (*GetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||||
|
void (*GetTextureImage)(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||||
|
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
||||||
|
GLsizei bufSize, GLvoid* pixels);
|
||||||
|
void (*DispatchCompute)(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
|
void (*DispatchComputeIndirect)(GLintptr indirect);
|
||||||
|
void (*MemoryBarrier)(GLbitfield barriers);
|
||||||
|
void (*MemoryBarrierByRegion)(GLbitfield barriers);
|
||||||
|
void (*BindImageTexture)(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer,
|
||||||
|
GLenum access, GLenum format);
|
||||||
|
void (*GetIntegeri_v)(GLenum target, GLuint index, GLint* data);
|
||||||
|
void (*GetInteger64i_v)(GLenum target, GLuint index, GLint64* data);
|
||||||
|
void (*GetProgramiv)(GLuint program, GLenum pname, GLint* params);
|
||||||
|
void (*GetProgramInterfaceiv)(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||||
|
GLuint (*GetProgramResourceIndex)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
void (*GetProgramResourceName)(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
||||||
|
GLsizei* length, GLchar* name);
|
||||||
|
void (*GetProgramResourceiv)(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||||
|
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||||
|
GLint (*GetProgramResourceLocation)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
GLint (*GetProgramResourceLocationIndex)(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
void (*ShaderStorageBlockBinding)(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||||
|
// GL fence sync objects. All entries are optional (may be null); the
|
||||||
|
// frontend then falls back to always-signaled sync semantics.
|
||||||
|
// FenceSync may itself return null when the backend cannot create a
|
||||||
|
// fence right now (e.g. the calling thread does not own the backend
|
||||||
|
// context); the frontend treats such a sync as always signaled.
|
||||||
|
BackendSyncHandle (*FenceSync)();
|
||||||
|
GLenum (*ClientWaitSync)(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||||
|
void (*WaitSync)(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||||
|
void (*DeleteSync)(BackendSyncHandle sync);
|
||||||
|
Bool (*GetSyncStatus)(BackendSyncHandle sync); // true = signaled
|
||||||
|
// GL timer-query objects (GL_ARB_timer_query). All entries are
|
||||||
|
// optional (may be null); the frontend then falls back to zero
|
||||||
|
// results and reports GL_QUERY_COUNTER_BITS == 0.
|
||||||
|
// BeginTimeElapsedQuery / QueryCounterTimestamp may themselves
|
||||||
|
// return null when the backend cannot create a query right now;
|
||||||
|
// the frontend treats such a query as immediately available with
|
||||||
|
// a zero result.
|
||||||
|
// Dynamic support check: true only when the live backend can
|
||||||
|
// actually time at the moment of the call (extension / entry
|
||||||
|
// points / timestamp valid bits are known then, not at table
|
||||||
|
// init). Gates the advertised GL_QUERY_COUNTER_BITS.
|
||||||
|
Bool (*IsTimerQuerySupported)();
|
||||||
|
BackendQueryHandle (*BeginTimeElapsedQuery)(); // starts a TIME_ELAPSED span
|
||||||
|
void (*EndTimeElapsedQuery)(BackendQueryHandle query); // ends the span
|
||||||
|
BackendQueryHandle (*QueryCounterTimestamp)(); // glQueryCounter(GL_TIMESTAMP) one-shot
|
||||||
|
Bool (*IsQueryResultAvailable)(BackendQueryHandle query); // non-blocking
|
||||||
|
// Returns true when a final value was produced (*outNanoseconds
|
||||||
|
// written; the frontend may cache it and release the handle).
|
||||||
|
// Returns false when the result could not be obtained YET - e.g.
|
||||||
|
// a Vulkan wait that refuses to block on a not-yet-submitted
|
||||||
|
// frame serial - in which case the frontend must keep the handle
|
||||||
|
// and leave the query readable later.
|
||||||
|
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||||
|
void (*DeleteBackendQuery)(BackendQueryHandle query);
|
||||||
|
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
||||||
};
|
};
|
||||||
struct GlobalBackendFunctionsTable {
|
struct GlobalBackendFunctionsTable {
|
||||||
GLFunctionsTable GL;
|
GLFunctionsTable GL;
|
||||||
void (*Present)();
|
void (*Present)();
|
||||||
|
// Optional: applies the app-requested eglSwapInterval to the native
|
||||||
|
// presentation path (null = backend keeps its own pacing policy).
|
||||||
|
void (*SetSwapInterval)(Int interval);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DynamicBackendParameters {
|
struct DynamicBackendParameters {
|
||||||
SizeT UniformBufferOffsetAlignment = 256;
|
SizeT UniformBufferOffsetAlignment = 256;
|
||||||
|
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT. 1.0 means the backend cannot filter anisotropically,
|
||||||
|
// which is also why the extension is not advertised in that case.
|
||||||
|
Float MaxTextureMaxAnisotropy = 1.0f;
|
||||||
|
Float AliasedLineWidthRangeMin = 1.0f;
|
||||||
|
Float AliasedLineWidthRangeMax = 1.0f;
|
||||||
|
Float SmoothLineWidthRangeMin = 1.0f;
|
||||||
|
Float SmoothLineWidthRangeMax = 1.0f;
|
||||||
|
Float SmoothLineWidthGranularity = 1.0f;
|
||||||
|
Float PointSizeRangeMin = 1.0f;
|
||||||
|
Float PointSizeRangeMax = 1.0f;
|
||||||
|
Float PointSizeGranularity = 1.0f;
|
||||||
|
Int Max3DTextureSize = 16384;
|
||||||
|
Int MaxArrayTextureLayers = 2048;
|
||||||
|
Int MaxCubeMapTextureSize = 16384;
|
||||||
|
Int MaxFramebufferWidth = 16384;
|
||||||
|
Int MaxFramebufferHeight = 16384;
|
||||||
|
Int MaxFramebufferLayers = 2048;
|
||||||
|
Int MaxRenderbufferSize = 16384;
|
||||||
|
Int MaxTextureSize = 16384;
|
||||||
|
Int MaxColorTextureSamples = 1;
|
||||||
|
Int MaxDepthTextureSamples = 1;
|
||||||
|
Int MaxFramebufferSamples = 1;
|
||||||
|
Int MaxIntegerSamples = 1;
|
||||||
|
Int MaxSamples = 1;
|
||||||
|
Int MaxSampleMaskWords = 1;
|
||||||
|
Int MaxTextureImageUnits = 32;
|
||||||
|
Int MaxVertexTextureImageUnits = 32;
|
||||||
|
Int MaxComputeTextureImageUnits = 32;
|
||||||
|
Int MaxCombinedTextureImageUnits = 192;
|
||||||
|
Int MaxVertexAttribs = 16;
|
||||||
|
Int MaxComputeShaderStorageBlocks = 8;
|
||||||
|
Int MaxCombinedShaderStorageBlocks = 32;
|
||||||
|
Int MaxComputeUniformBlocks = 12;
|
||||||
|
Int MaxComputeWorkGroupInvocations = 128;
|
||||||
|
Int MaxShaderStorageBufferBindings = 8;
|
||||||
|
Int MaxTextureBufferSize = 65536;
|
||||||
|
Int MaxUniformBufferBindings = 24;
|
||||||
|
Int MaxUniformBlockSize = 16384;
|
||||||
|
Int MaxImageUnits = 8;
|
||||||
|
Int MaxCombinedImageUniforms = 8;
|
||||||
|
Int MaxVertexImageUniforms = 0;
|
||||||
|
Int MaxGeometryImageUniforms = 0;
|
||||||
|
Int MaxFragmentImageUniforms = 8;
|
||||||
|
Int MaxComputeImageUniforms = 8;
|
||||||
|
Int MaxDrawBuffers = 8;
|
||||||
|
Int MaxColorAttachments = 8;
|
||||||
|
Int MaxClipDistances = 8;
|
||||||
|
Int MaxViewports = 16;
|
||||||
|
Int MaxViewportWidth = 16384;
|
||||||
|
Int MaxViewportHeight = 16384;
|
||||||
|
Float ViewportBoundsRangeMin = 0.0f;
|
||||||
|
Float ViewportBoundsRangeMax = 0.0f;
|
||||||
|
Int ViewportSubpixelBits = 0;
|
||||||
|
Bool SupportsWideLines = false;
|
||||||
|
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||||
|
Uint32 SubgroupSize = 0;
|
||||||
|
Uint32 SubgroupSupportedStages = 0;
|
||||||
|
Uint32 SubgroupSupportedFeatures = 0;
|
||||||
|
Bool SubgroupQuadOperationsInAllStages = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class WindowBackend {
|
enum class WindowBackend {
|
||||||
Android,
|
Android,
|
||||||
// TODO: X11, Wayland, Windows, macOS, etc.
|
X11,
|
||||||
|
MetalLayer,
|
||||||
|
// TODO: Wayland, Windows, etc.
|
||||||
WindowBackendCount,
|
WindowBackendCount,
|
||||||
Unknown = -1
|
Unknown = -1
|
||||||
};
|
};
|
||||||
@@ -84,6 +305,8 @@ namespace MobileGL {
|
|||||||
struct WindowHandle {
|
struct WindowHandle {
|
||||||
WindowBackend Backend = WindowBackend::Unknown;
|
WindowBackend Backend = WindowBackend::Unknown;
|
||||||
void* Handle = nullptr;
|
void* Handle = nullptr;
|
||||||
|
Uint32 Width = 0;
|
||||||
|
Uint32 Height = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BackendObject {
|
class BackendObject {
|
||||||
@@ -95,9 +318,16 @@ namespace MobileGL {
|
|||||||
virtual Bool InitWindowSurface() = 0;
|
virtual Bool InitWindowSurface() = 0;
|
||||||
|
|
||||||
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
||||||
virtual Bool CreateEGLWindowSurface(const WindowHandle& handle);
|
virtual Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
||||||
|
virtual Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height);
|
||||||
|
virtual Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
||||||
virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||||
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
||||||
|
// Forwards the app-requested eglSwapInterval to the backend's native
|
||||||
|
// presentation path (no-op for backends without a SetSwapInterval hook).
|
||||||
|
virtual void SetEGLSwapInterval(Int interval);
|
||||||
|
virtual void ReleaseEGLSurface(EGLSurface surface);
|
||||||
|
virtual void ReleaseEGLResources();
|
||||||
|
|
||||||
void SetWindowHandle(const WindowHandle& handle);
|
void SetWindowHandle(const WindowHandle& handle);
|
||||||
|
|
||||||
@@ -105,18 +335,56 @@ namespace MobileGL {
|
|||||||
virtual String GetBackendAPIVersionString() const = 0;
|
virtual String GetBackendAPIVersionString() const = 0;
|
||||||
virtual const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0;
|
virtual const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0;
|
||||||
virtual const DynamicBackendParameters& GetDynamicParameters() const = 0;
|
virtual const DynamicBackendParameters& GetDynamicParameters() const = 0;
|
||||||
|
const FormatCapabilityCache& GetFormatCapabilities() const;
|
||||||
virtual BackendType GetBackendType() const = 0;
|
virtual BackendType GetBackendType() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
enum class SurfaceKind {
|
||||||
|
None,
|
||||||
|
Window,
|
||||||
|
Pbuffer
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EGLCurrentState {
|
||||||
|
EGLDisplay Display = EGL_NO_DISPLAY;
|
||||||
|
EGLSurface DrawSurface = EGL_NO_SURFACE;
|
||||||
|
EGLSurface ReadSurface = EGL_NO_SURFACE;
|
||||||
|
EGLContext Context = EGL_NO_CONTEXT;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EGLSurfaceState {
|
||||||
|
SurfaceKind Kind = SurfaceKind::None;
|
||||||
|
Bool DestroyPending = false;
|
||||||
|
WindowHandle Window;
|
||||||
|
EGLint Width = 1;
|
||||||
|
EGLint Height = 1;
|
||||||
|
};
|
||||||
|
|
||||||
void ResetEGLRuntimeState();
|
void ResetEGLRuntimeState();
|
||||||
|
Bool RegisterEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
||||||
|
Bool RegisterEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
||||||
|
const EGLSurfaceState* GetRegisteredEGLSurface(EGLSurface surface) const;
|
||||||
|
Bool ActivateEGLSurface(EGLSurface surface);
|
||||||
|
virtual Bool InitPbufferSurface(EGLint width, EGLint height);
|
||||||
|
virtual void OnEGLSurfaceReleased(EGLSurface surface);
|
||||||
|
FormatCapabilityCache& MutableFormatCapabilities();
|
||||||
|
|
||||||
mutable std::recursive_mutex m_eglStateMutex;
|
mutable std::recursive_mutex m_eglStateMutex;
|
||||||
|
FormatCapabilityCache m_formatCapabilities;
|
||||||
WindowHandle m_windowHandle;
|
WindowHandle m_windowHandle;
|
||||||
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
||||||
|
EGLSurface m_eglSurface = EGL_NO_SURFACE;
|
||||||
Bool m_eglDisplayInitialized = false;
|
Bool m_eglDisplayInitialized = false;
|
||||||
Bool m_eglWindowSurfaceInitialized = false;
|
Bool m_eglSurfaceInitialized = false;
|
||||||
Bool m_backendCapabilitiesInitialized = false;
|
Bool m_backendCapabilitiesInitialized = false;
|
||||||
UnorderedMap<std::thread::id, Bool> m_eglCurrentThreads;
|
SurfaceKind m_eglSurfaceKind = SurfaceKind::None;
|
||||||
|
UnorderedMap<std::thread::id, EGLCurrentState> m_eglCurrentThreads;
|
||||||
|
UnorderedMap<EGLSurface, EGLSurfaceState> m_eglSurfaces;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Bool IsEGLSurfaceCurrent(EGLSurface surface) const;
|
||||||
|
void DestroyPendingEGLSurfaceIfUnused(EGLSurface surface);
|
||||||
|
void ReleaseEGLCurrentThread(const std::thread::id& threadKey);
|
||||||
};
|
};
|
||||||
} // namespace MG_Backend
|
} // namespace MG_Backend
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,12 @@
|
|||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
namespace MobileGL::MG_Backend::DirectGLES {
|
||||||
|
// Populates the same format-capability cache used by backend startup. The caller
|
||||||
|
// must keep the supplied GLES context current for the duration of this call.
|
||||||
|
void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl,
|
||||||
|
const MG_External::GLESCapabilities& capabilities,
|
||||||
|
FormatCapabilityCache& cache);
|
||||||
|
|
||||||
class BackendObject_DirectGLES : public BackendObject {
|
class BackendObject_DirectGLES : public BackendObject {
|
||||||
public:
|
public:
|
||||||
~BackendObject_DirectGLES() override;
|
~BackendObject_DirectGLES() override;
|
||||||
@@ -20,9 +26,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Bool InitCapabilities() override;
|
Bool InitCapabilities() override;
|
||||||
Bool InitWindowSurface() override;
|
Bool InitWindowSurface() override;
|
||||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||||
Bool CreateEGLWindowSurface(const WindowHandle& handle) override;
|
Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override;
|
||||||
|
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
|
||||||
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||||
|
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||||
|
void ReleaseEGLResources() override;
|
||||||
|
|
||||||
const RendererInfo& GetRendererInfo() const override;
|
const RendererInfo& GetRendererInfo() const override;
|
||||||
String GetBackendAPIVersionString() const override;
|
String GetBackendAPIVersionString() const override;
|
||||||
@@ -32,9 +41,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
const MG_External::GLESFunctionsTable& GetGLESFunctions() const;
|
const MG_External::GLESFunctionsTable& GetGLESFunctions() const;
|
||||||
const MG_External::EGLFunctionsTable& GetEGLFunctions() const;
|
const MG_External::EGLFunctionsTable& GetEGLFunctions() const;
|
||||||
|
void ApplyGLESCapabilitiesForTesting(const MG_External::GLESCapabilities& capabilities);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateDynamicBackendParameters();
|
void UpdateDynamicBackendParameters();
|
||||||
|
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||||
|
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||||
|
|
||||||
Bool m_initialized = false;
|
Bool m_initialized = false;
|
||||||
MG_External::EGLFunctionsTable m_EGLFunctions;
|
MG_External::EGLFunctionsTable m_EGLFunctions;
|
||||||
@@ -42,4 +54,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MG_External::GLESCapabilities m_GLESCapabilities;
|
MG_External::GLESCapabilities m_GLESCapabilities;
|
||||||
DynamicBackendParameters m_dynamicParameters;
|
DynamicBackendParameters m_dynamicParameters;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Single-source-of-truth helpers shared with the driver POST
|
||||||
|
// (MG_Util/SelfTest/DriverPost.cpp), so the identity strings and extension list
|
||||||
|
// MobileGL reports to applications on this backend cannot drift from what the
|
||||||
|
// POST screen shows.
|
||||||
|
|
||||||
|
// Static identity of the Espryt renderer (renderer/backend names, target GL/GLSL
|
||||||
|
// versions, ExtraVendor). The Extensions vector inside is live backend state that
|
||||||
|
// is reconciled after capability init; callers that need the advertised list for
|
||||||
|
// a known capability set must use BuildAdvertisedExtensions instead.
|
||||||
|
const RendererInfo& GetRendererIdentity();
|
||||||
|
|
||||||
|
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
|
||||||
|
// for a device whose timer queries / anisotropic filtering are (or are not) usable.
|
||||||
|
// The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
|
||||||
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported);
|
||||||
|
|
||||||
|
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
|
||||||
|
// initialized backend returns from GetBackendAPIVersionString (and that ends up
|
||||||
|
// inside the application-visible GL_RENDERER string).
|
||||||
|
String FormatBackendAPIVersionString(const String& glesRendererString, Int glesMajor, Int glesMinor);
|
||||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <MG_Backend/BackendObject.h>
|
||||||
|
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||||
#include <MG_State/GLState/TextureState/TextureState.h>
|
#include <MG_State/GLState/TextureState/TextureState.h>
|
||||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
@@ -17,6 +19,10 @@
|
|||||||
operation Utils::CheckGLESError();
|
operation Utils::CheckGLESError();
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
namespace MobileGL::MG_Backend::DirectGLES {
|
||||||
|
// Re-establishes the frontend texture-unit bindings on the native ES context.
|
||||||
|
// Content uploads use scratch bindings, so draws and dispatches call this after
|
||||||
|
// texture synchronization.
|
||||||
|
void BindCurrentTextures();
|
||||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
@@ -25,11 +31,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
||||||
|
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount);
|
GLsizei drawcount);
|
||||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount, const GLint* basevertex);
|
GLsizei drawcount, const GLint* basevertex);
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
|
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex);
|
const void* indices, GLint basevertex);
|
||||||
@@ -46,18 +55,100 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
GLuint baseinstance);
|
GLuint baseinstance);
|
||||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||||
|
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||||
|
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||||
|
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||||
|
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||||
|
GLbitfield mask, GLenum filter);
|
||||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height, GLint border);
|
GLsizei height, GLint border);
|
||||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height);
|
GLsizei height);
|
||||||
|
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||||
|
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||||
|
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||||
|
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||||
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||||
void GenerateMipmap(GLenum target);
|
void GenerateMipmap(GLenum target);
|
||||||
const GLubyte* GetString(GLenum name);
|
const GLubyte* GetString(GLenum name);
|
||||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||||
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
|
void MemoryBarrier(GLbitfield barriers);
|
||||||
|
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||||
|
void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access,
|
||||||
|
GLenum format);
|
||||||
|
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||||
|
void GetInteger64i_v(GLenum target, GLuint index, GLint64* data);
|
||||||
|
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||||
|
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||||
|
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length,
|
||||||
|
GLchar* name);
|
||||||
|
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||||
|
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||||
|
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||||
Bool InitWindowSurface(NativeWindowType window);
|
Bool InitWindowSurface(NativeWindowType window);
|
||||||
|
Bool InitPbufferSurface(EGLint width, EGLint height);
|
||||||
|
Bool MakeCurrent();
|
||||||
|
Bool ReleaseCurrent();
|
||||||
|
// True when the backend ES context is current on the calling thread, i.e.
|
||||||
|
// immediate buffer ops may issue GL calls right now.
|
||||||
|
Bool IsBackendContextCurrentOnThisThread();
|
||||||
|
// GL fence sync objects, backed by native ES fences. FenceSync returns null
|
||||||
|
// (the frontend then falls back to an always-signaled sync) when the calling
|
||||||
|
// thread does not own the ES context. Waits/queries degrade to "signaled" in
|
||||||
|
// the same situation, and handles created under a since-destroyed ES context
|
||||||
|
// are always treated as signaled.
|
||||||
|
BackendSyncHandle FenceSync();
|
||||||
|
GLenum ClientWaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||||
|
void WaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||||
|
void DeleteSync(BackendSyncHandle sync);
|
||||||
|
Bool GetSyncStatus(BackendSyncHandle sync);
|
||||||
|
// True when GL_EXT_disjoint_timer_query and every entry point the timer
|
||||||
|
// hooks below need are present. Also gates the E_GL_ARB_timer_query
|
||||||
|
// advertisement in BackendObject_DirectGLES::InitCapabilities, and is
|
||||||
|
// registered as the GLFunctionsTable::IsTimerQuerySupported hook: a pure
|
||||||
|
// capability read needs no current ES context, and it stays false until
|
||||||
|
// the ES capabilities have been filled in.
|
||||||
|
Bool AreTimerQueriesSupported();
|
||||||
|
// GL timer-query objects, backed by GL_EXT_disjoint_timer_query. The
|
||||||
|
// creators return null (the frontend then falls back to an immediately
|
||||||
|
// available zero result) when the calling thread does not own the ES
|
||||||
|
// context or the extension/entry points are missing, and handles created
|
||||||
|
// under a since-destroyed ES context are always treated as complete with
|
||||||
|
// a zero result (mirrors the fence-sync handles above).
|
||||||
|
BackendQueryHandle BeginTimeElapsedQuery();
|
||||||
|
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||||
|
BackendQueryHandle QueryCounterTimestamp();
|
||||||
|
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||||
|
// Returns true when a final value landed in *outNanoseconds (a zero for
|
||||||
|
// null or stale-generation handles IS final: the frontend may cache it
|
||||||
|
// and release the handle). Returns false only when the calling thread
|
||||||
|
// does not own the ES context, so the value is genuinely unobtainable
|
||||||
|
// right now; the handle stays alive and readable later.
|
||||||
|
Bool GetQueryResult64(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||||
|
void DeleteBackendQuery(BackendQueryHandle query);
|
||||||
|
Int64 GetGpuTimestampNs();
|
||||||
void Present();
|
void Present();
|
||||||
|
// Frame-completion watermarks for the buffer-storage pool: CurrentFrameSerial()
|
||||||
|
// is bumped once per Present(); CompletedFrameSerial() is the newest frame whose
|
||||||
|
// GPU work has provably finished (advanced by polling a one-fence-per-frame ring).
|
||||||
|
// A buffer retired during frame N is safe to recycle once CompletedFrameSerial() >= N.
|
||||||
|
Uint64 CurrentFrameSerial();
|
||||||
|
Uint64 CompletedFrameSerial();
|
||||||
|
// Applies (or defers until the window surface exists) the app-requested
|
||||||
|
// eglSwapInterval on the native EGL surface.
|
||||||
|
void SetSwapInterval(Int interval);
|
||||||
void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
|
void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
|
||||||
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
|
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
|
||||||
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,13 +8,19 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
#include "DirectGLES.h"
|
#include "DirectGLES.h"
|
||||||
#include "MG_State/GLState/SamplerState/SamplerObject.h"
|
#include "MG_State/GLState/SamplerState/SamplerObject.h"
|
||||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
namespace MobileGL::MG_Backend::DirectGLES {
|
||||||
|
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||||
|
String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType);
|
||||||
|
|
||||||
template <typename StateObject, typename BackendObject>
|
template <typename StateObject, typename BackendObject>
|
||||||
class StateBackendObjectRegistry {
|
class StateBackendObjectRegistry {
|
||||||
public:
|
public:
|
||||||
@@ -50,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(); }
|
||||||
|
|
||||||
@@ -111,38 +119,130 @@ 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 cache for INDEXED buffer bindings (glBindBufferBase/Range on
|
||||||
|
// GL_UNIFORM_BUFFER / GL_SHADER_STORAGE_BUFFER): skips the GL call when the
|
||||||
|
// (id, range) already at that index matches, like the array-buffer/texture/
|
||||||
|
// sampler caches already do. Invalidated on MakeCurrent (context may reset).
|
||||||
|
void BindBufferBaseCached(GLenum glTarget, Uint index, Uint id);
|
||||||
|
void BindBufferRangeCached(GLenum glTarget, Uint index, Uint id, GLintptr offset, GLsizeiptr size);
|
||||||
|
void InvalidateIndexedBufferBindingCache();
|
||||||
|
// Buffer-storage pool maintenance. TrimBufferPool evicts over-budget entries
|
||||||
|
// (called once per frame from Present); ClearBufferPool drops all pooled ids
|
||||||
|
// without glDeleteBuffers (called when the ES context is going away).
|
||||||
|
void TrimBufferPool();
|
||||||
|
void ClearBufferPool();
|
||||||
|
|
||||||
|
// --- Global-UBO ring ------------------------------------------------------
|
||||||
|
// One persistently+coherently mapped buffer (EXT_buffer_storage) shared by
|
||||||
|
// every program's lowered default-uniform block. Each content change is
|
||||||
|
// bump-allocated into a fresh slot and bound with glBindBufferRange, so the
|
||||||
|
// CPU never rewrites bytes the GPU may still be reading — the per-draw
|
||||||
|
// glBufferSubData into one static UBO forced Adreno to resolve that
|
||||||
|
// write-after-read hazard on every uniform-dirtying draw (MC dirties
|
||||||
|
// uniforms every draw). Reclamation rides the Present() frame-fence
|
||||||
|
// watermark; no ring bytes are recycled before their frame's GPU work
|
||||||
|
// completed.
|
||||||
|
//
|
||||||
|
// A program's cached slot, reusable within one frame while the frontend UBO
|
||||||
|
// content version is unchanged. Cross-frame reuse is intentionally not
|
||||||
|
// attempted: later same-frame allocations may recycle bytes of completed
|
||||||
|
// frames, so re-referencing them would need per-bind pinning — rewriting
|
||||||
|
// GetUBOSize() bytes once per program per frame is far cheaper.
|
||||||
|
struct UboRingAllocation {
|
||||||
|
Uint32 contentVersion = ~0u; // frontend UBO content version held at `offset`
|
||||||
|
Uint32 ringGeneration = 0; // ring identity the slot lives in (0 = never valid)
|
||||||
|
Uint64 frameSerial = ~Uint64{0}; // frame the slot was written in
|
||||||
|
SizeT offset = 0;
|
||||||
|
};
|
||||||
|
// False when the feature is disabled, EXT_buffer_storage / fences are
|
||||||
|
// missing, the ES context is not current, or ring creation already failed
|
||||||
|
// under this context (callers then take the legacy glBufferSubData path).
|
||||||
|
Bool UboRingAvailable();
|
||||||
|
// Bump-allocate `size` bytes aligned to GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT.
|
||||||
|
// Grows the ring (new GL store, generation bump) when the in-flight span
|
||||||
|
// would be overrun. Returns false when storage (re)creation fails.
|
||||||
|
Bool UboRingAllocate(SizeT size, SizeT& outOffset);
|
||||||
|
void* UboRingMappedPtr();
|
||||||
|
Uint UboRingBufferId();
|
||||||
|
Uint32 UboRingGeneration();
|
||||||
|
// Present()-time upkeep: records the frame's high-water mark for reclamation
|
||||||
|
// and deletes grown-away ring stores once the GPU is done with them.
|
||||||
|
void UboRingOnPresent();
|
||||||
} // namespace BufferImpl
|
} // namespace BufferImpl
|
||||||
|
|
||||||
namespace VertexArrayImpl {
|
namespace VertexArrayImpl {
|
||||||
class BackendVertexArrayObject {
|
class BackendVertexArrayObject {
|
||||||
public:
|
public:
|
||||||
BackendVertexArrayObject();
|
BackendVertexArrayObject();
|
||||||
|
~BackendVertexArrayObject();
|
||||||
void SyncToBackend(const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
|
void SyncToBackend(const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
|
||||||
|
void SyncClientSideAttributesForDrawArrays(
|
||||||
|
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
|
||||||
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
||||||
void Bind() const;
|
void Bind() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Uint m_backendVAOId = 0;
|
Uint m_backendVAOId = 0;
|
||||||
|
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
Uint16 m_syncedIndexBufferVersion = 0;
|
Uint16 m_syncedIndexBufferVersion = 0;
|
||||||
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
||||||
@@ -155,11 +255,57 @@ 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 ||
|
// Rectangle textures need non-normalized sampling ES cannot express; everything else is
|
||||||
target == TextureTarget::Texture2DMultisampleArray || target == TextureTarget::Texture1DArray ||
|
// either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see
|
||||||
target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DArray)
|
// MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and
|
||||||
return false;
|
// coordinate padding for 1D/1D-array shaders.
|
||||||
return true;
|
return target != TextureTarget::TextureRectangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ES has no 1D targets: 1D textures are stored as 2D (height 1) and 1D arrays as 2D arrays
|
||||||
|
// (height 1, layers in depth). Must match SPIRV-Cross's ES 1D-as-2D shader emulation.
|
||||||
|
inline TextureTarget MapToBackendTextureTarget(TextureTarget target) {
|
||||||
|
switch (target) {
|
||||||
|
case TextureTarget::Texture1D:
|
||||||
|
return TextureTarget::Texture2D;
|
||||||
|
case TextureTarget::Texture1DArray:
|
||||||
|
return TextureTarget::Texture2DArray;
|
||||||
|
default:
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GLenum ConvertTextureTargetToBackendGLEnum(TextureTarget target) {
|
||||||
|
return MG_Util::ConvertTextureTargetToGLEnum(MapToBackendTextureTarget(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) {
|
||||||
|
switch (uploadTarget) {
|
||||||
|
case TextureUploadTarget::Texture1D:
|
||||||
|
return GL_TEXTURE_2D;
|
||||||
|
case TextureUploadTarget::Texture1DArray:
|
||||||
|
return GL_TEXTURE_2D_ARRAY;
|
||||||
|
default:
|
||||||
|
return MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1D arrays store layers in the state-side height; the ES 2D-array image keeps height 1 and
|
||||||
|
// moves the layer count into depth.
|
||||||
|
inline IntVec3 GetBackendUploadSize(TextureTarget stateTarget, const IntVec3& texelSize) {
|
||||||
|
if (stateTarget == TextureTarget::Texture1DArray) {
|
||||||
|
return {texelSize.x(), 1, texelSize.y()};
|
||||||
|
}
|
||||||
|
return texelSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Bool IsMultisampleTextureTarget(TextureTarget target) {
|
||||||
|
return target == TextureTarget::Texture2DMultisample ||
|
||||||
|
target == TextureTarget::Texture2DMultisampleArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Bool SupportsWrapR(TextureTarget target) {
|
||||||
|
return target == TextureTarget::Texture3D || target == TextureTarget::TextureCubeMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StateTextureBasicInfo { // Used for tracking texture state changes
|
struct StateTextureBasicInfo { // Used for tracking texture state changes
|
||||||
@@ -169,11 +315,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); }
|
||||||
@@ -186,12 +335,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
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;
|
||||||
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};
|
||||||
@@ -206,6 +360,9 @@ 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;
|
||||||
@@ -218,6 +375,7 @@ 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);
|
||||||
|
void InvalidateSyncedState();
|
||||||
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
|
Uint GetBackendFramebufferId() const { return m_backendFBOId; }
|
||||||
void Bind(FramebufferTarget target) const;
|
void Bind(FramebufferTarget target) const;
|
||||||
// FramebufferAttachmentType GetCompactedAttachmentTypeAtDrawBufferIndex(Int index);
|
// FramebufferAttachmentType GetCompactedAttachmentTypeAtDrawBufferIndex(Int index);
|
||||||
@@ -250,24 +408,109 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
||||||
g_backendFramebufferObjects;
|
g_backendFramebufferObjects;
|
||||||
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
|
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
|
||||||
|
// Tracks the bound FBO's object version (bumped on any attachment/drawbuffer change)
|
||||||
|
// per target: re-attaching textures or changing draw buffers on an already-bound FBO
|
||||||
|
// must re-sync it even when the binding-slot version has not moved.
|
||||||
|
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedObjectVersions;
|
||||||
|
extern Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
|
||||||
|
g_fboSyncedObjects;
|
||||||
} // namespace FramebufferImpl
|
} // namespace FramebufferImpl
|
||||||
|
|
||||||
|
// Image uniforms take their unit from the layout(binding=N) qualifier baked into
|
||||||
|
// the transpiled ESSL; unlike samplers they must not (and in ES cannot) be
|
||||||
|
// assigned through glUniform1i.
|
||||||
|
inline Bool IsImageUniformType(GLenum type) {
|
||||||
|
switch (type) {
|
||||||
|
case 0x904D: /*GL_IMAGE_2D*/
|
||||||
|
case 0x904E: /*GL_IMAGE_3D*/
|
||||||
|
case 0x9050: /*GL_IMAGE_CUBE*/
|
||||||
|
case 0x9051: /*GL_IMAGE_BUFFER*/
|
||||||
|
case 0x9053: /*GL_IMAGE_2D_ARRAY*/
|
||||||
|
case 0x9058: /*GL_INT_IMAGE_2D*/
|
||||||
|
case 0x9059: /*GL_INT_IMAGE_3D*/
|
||||||
|
case 0x905B: /*GL_INT_IMAGE_CUBE*/
|
||||||
|
case 0x905C: /*GL_INT_IMAGE_BUFFER*/
|
||||||
|
case 0x905E: /*GL_INT_IMAGE_2D_ARRAY*/
|
||||||
|
case 0x9063: /*GL_UNSIGNED_INT_IMAGE_2D*/
|
||||||
|
case 0x9064: /*GL_UNSIGNED_INT_IMAGE_3D*/
|
||||||
|
case 0x9066: /*GL_UNSIGNED_INT_IMAGE_CUBE*/
|
||||||
|
case 0x9067: /*GL_UNSIGNED_INT_IMAGE_BUFFER*/
|
||||||
|
case 0x9069: /*GL_UNSIGNED_INT_IMAGE_2D_ARRAY*/
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
class BackendProgramObjectImpl {
|
class BackendProgramObjectImpl {
|
||||||
public:
|
public:
|
||||||
|
// Per-link cache of a sampler-style uniform's backend location: built once in
|
||||||
|
// SyncToBackend so draws stop issuing glGetUniformLocation string queries.
|
||||||
|
// lastAssignedUnit mirrors the program-state value set through glUniform1i
|
||||||
|
// (program state persists across binds, so caching per program is exact).
|
||||||
|
struct SamplerUniformBinding {
|
||||||
|
Uint frontendLocation = 0;
|
||||||
|
Int backendLocation = -1;
|
||||||
|
GLenum uniformType = 0;
|
||||||
|
Int lastAssignedUnit = -1;
|
||||||
|
};
|
||||||
|
|
||||||
BackendProgramObjectImpl();
|
BackendProgramObjectImpl();
|
||||||
~BackendProgramObjectImpl();
|
~BackendProgramObjectImpl();
|
||||||
void SyncToBackend(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 SetBaseInstanceWordIndex(Int32 wordIndex) const;
|
||||||
|
void SetDrawID(Uint32 drawId) const;
|
||||||
|
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
||||||
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
||||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||||
|
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||||
|
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||||
|
|
||||||
|
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
||||||
|
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
||||||
|
Vector<SamplerUniformBinding>& GetSamplerUniformBindings() { return m_samplerUniformBindings; }
|
||||||
|
Uint32 GetLastUploadedGlobalUboVersion() const { return m_lastUploadedGlobalUboVersion; }
|
||||||
|
void SetLastUploadedGlobalUboVersion(Uint32 version) { m_lastUploadedGlobalUboVersion = version; }
|
||||||
|
// Backend-reported GL_UNIFORM_BLOCK_DATA_SIZE of the global block; ring
|
||||||
|
// bindings must span at least this much (may exceed the frontend's
|
||||||
|
// reflected size when the transpiled block pads differently).
|
||||||
|
Int GetGlobalUboBackendBlockSize() const { return m_globalUboBackendBlockSize; }
|
||||||
|
BufferImpl::UboRingAllocation& GetGlobalUboRingAllocation() { return m_globalUboRingAllocation; }
|
||||||
|
// Frontend link version this backend program (and its resource caches) was
|
||||||
|
// built from; a mismatch means every link-derived cache here is stale.
|
||||||
|
Uint32 GetSyncedLinkVersion() const { return m_syncedLinkVersion; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void CacheResourceLocations(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||||
|
|
||||||
Uint m_backendProgramId = 0;
|
Uint m_backendProgramId = 0;
|
||||||
Uint m_backendGlobalUBOId = 0;
|
Uint m_backendGlobalUBOId = 0;
|
||||||
|
Int m_baseInstanceUniformLocation = -1;
|
||||||
|
Int m_drawIdUniformLocation = -1;
|
||||||
|
Int m_baseInstanceWordIndexUniformLocation = -1;
|
||||||
|
Int m_indirectParamsBinding = -1;
|
||||||
|
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||||
|
Uint32 m_unormFallbackClampOutputMask = 0;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
|
|
||||||
|
Int m_globalUboBackendBlockIndex = -1;
|
||||||
|
Int m_globalUboBackendBlockSize = 0;
|
||||||
|
Vector<Int> m_uniformBlockBackendIndices; // frontend block index -> backend index (-1 = absent)
|
||||||
|
Vector<SamplerUniformBinding> m_samplerUniformBindings;
|
||||||
|
Uint32 m_lastUploadedGlobalUboVersion = ~0u;
|
||||||
|
BufferImpl::UboRingAllocation m_globalUboRingAllocation;
|
||||||
|
Uint32 m_syncedLinkVersion = ~0u;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern Uint32 g_snormFallbackClampOutputMask;
|
||||||
|
extern Uint32 g_unormFallbackClampOutputMask;
|
||||||
|
// Backend id of the last glUseProgram issued through this backend; lets Use()
|
||||||
|
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||||
|
// ES context is recreated.
|
||||||
|
extern Uint g_lastUsedBackendProgramId;
|
||||||
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
||||||
g_backendProgramObjects;
|
g_backendProgramObjects;
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
@@ -309,6 +552,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,135 @@
|
|||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||||
|
#include <MG_Util/Math/HalfFloat.h>
|
||||||
|
#include <MG_Util/Math/SmallFloat.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
namespace MobileGL::MG_Backend::DirectGLES {
|
||||||
|
namespace {
|
||||||
|
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) {
|
||||||
|
using namespace MG_Util::TextureFormatProcessor;
|
||||||
|
const Flags<PixelFormatNormalizeOptionBit> forcedOptions =
|
||||||
|
GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, GetForcedPixelFormatNormalizeOptions());
|
||||||
|
if (forcedOptions) {
|
||||||
|
return forcedOptions;
|
||||||
|
}
|
||||||
|
return GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
|
||||||
|
GetDriverPixelFormatNormalizeOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool HasCachedFormatCapability(TextureInternalFormat internalFormat,
|
||||||
|
SizeT targetIndex,
|
||||||
|
Bool caveat,
|
||||||
|
FormatCapability capability) {
|
||||||
|
if (!pActiveBackendObject || targetIndex >= kFormatCapabilityTargetCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const SizeT formatIndex = static_cast<SizeT>(internalFormat);
|
||||||
|
if (formatIndex >= kFormatCapabilityFormatCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormatCapabilityCache& cache = pActiveBackendObject->GetFormatCapabilities();
|
||||||
|
const FormatCapabilityFlags caps =
|
||||||
|
caveat ? cache.CaveatCaps[targetIndex][formatIndex] : cache.FullCaps[targetIndex][formatIndex];
|
||||||
|
return HasFormatCapability(caps, capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool HasAnyCachedFormatCapability(TextureInternalFormat internalFormat,
|
||||||
|
Bool caveat,
|
||||||
|
FormatCapability capability) {
|
||||||
|
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
|
||||||
|
if (HasCachedFormatCapability(internalFormat, targetIndex, caveat, capability)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ShouldUseCaveatFormat(TextureInternalFormat internalFormat, SizeT targetIndex) {
|
||||||
|
if (targetIndex < kFormatCapabilityTargetCount) {
|
||||||
|
const Bool fullCreatable =
|
||||||
|
HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::Creatable);
|
||||||
|
const Bool caveatCreatable =
|
||||||
|
HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::Creatable);
|
||||||
|
const Bool fullRenderable =
|
||||||
|
HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::FramebufferRenderable);
|
||||||
|
const Bool caveatRenderable =
|
||||||
|
HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::FramebufferRenderable);
|
||||||
|
return (!fullCreatable && caveatCreatable) || (!fullRenderable && caveatRenderable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasAnyCachedFormatCapability(internalFormat, false, FormatCapability::Creatable)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return HasAnyCachedFormatCapability(internalFormat, true, FormatCapability::Creatable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenerateFormatInfo(TextureInternalFormat internalFormat,
|
||||||
|
SizeT targetIndex,
|
||||||
|
GLenum* outInternalFormat,
|
||||||
|
GLenum* outFormat,
|
||||||
|
GLenum* outType) {
|
||||||
|
using namespace MobileGL::MG_Util::TextureFormatProcessor;
|
||||||
|
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
||||||
|
Flags<PixelFormatNormalizeOptionBit> options;
|
||||||
|
if (!pActiveBackendObject || ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
||||||
|
options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat);
|
||||||
|
}
|
||||||
|
NormalizePixelFormat(requestedInternalFormat, options, outInternalFormat, outFormat, outType);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace TextureImpl {
|
namespace TextureImpl {
|
||||||
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||||
GLenum* outFormat, GLenum* outType) {
|
GLenum* outFormat, GLenum* outType, TextureTarget target) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
using namespace MobileGL::MG_Util::TextureFormatProcessor;
|
const SizeT targetIndex =
|
||||||
auto options = (g_GLESCapabilities.SupportsNorm16Texture) ? PixelFormatNormalizeOptionBit::None
|
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||||
: PixelFormatNormalizeOptionBit::NoNorm16;
|
GenerateFormatInfo(internalFormat, targetIndex, outInternalFormat, outFormat, outType);
|
||||||
NormalizePixelFormat(MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat), options,
|
}
|
||||||
outInternalFormat, outFormat, outType);
|
|
||||||
|
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||||
|
GLenum* outFormat, GLenum* outType) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
GenerateFormatInfo(internalFormat, GetRenderbufferFormatCapabilityTargetIndex(), outInternalFormat,
|
||||||
|
outFormat, outType);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target) {
|
||||||
|
const SizeT targetIndex =
|
||||||
|
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||||
|
return ShouldUseCaveatFormat(internalFormat, targetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat) {
|
||||||
|
return ShouldUseCaveatFormat(internalFormat, GetRenderbufferFormatCapabilityTargetIndex());
|
||||||
}
|
}
|
||||||
} // namespace TextureImpl
|
} // namespace TextureImpl
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
@@ -101,14 +218,128 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||||
|
Uint32 unormOutputMask) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
const Uint32 outputMask = snormOutputMask | unormOutputMask;
|
||||||
|
if (shaderType != GL_FRAGMENT_SHADER || outputMask == 0) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::regex outputPattern(
|
||||||
|
R"(layout\s*\(\s*location\s*=\s*([0-9]+)\s*\)\s*out\s+(?:(?:lowp|mediump|highp)\s+)?vec4\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
|
||||||
|
std::sregex_iterator outputIt(glslCode.begin(), glslCode.end(), outputPattern);
|
||||||
|
std::sregex_iterator outputEnd;
|
||||||
|
struct OutputClamp {
|
||||||
|
String Name;
|
||||||
|
Bool Signed;
|
||||||
|
};
|
||||||
|
Vector<OutputClamp> outputClamps;
|
||||||
|
for (; outputIt != outputEnd; ++outputIt) {
|
||||||
|
const Uint location = static_cast<Uint>(std::stoul((*outputIt)[1].str()));
|
||||||
|
if (location < 32 && (outputMask & (1u << location))) {
|
||||||
|
outputClamps.push_back({(*outputIt)[2].str(), static_cast<Bool>(snormOutputMask & (1u << location))});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (outputClamps.empty()) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::regex mainPattern(R"(void\s+main\s*\([^)]*\)\s*\{)");
|
||||||
|
std::smatch mainMatch;
|
||||||
|
if (!std::regex_search(glslCode, mainMatch, mainPattern)) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeT bracePos = static_cast<SizeT>(mainMatch.position(0) + mainMatch.length(0) - 1);
|
||||||
|
Int depth = 0;
|
||||||
|
for (SizeT pos = bracePos; pos < glslCode.size(); ++pos) {
|
||||||
|
if (glslCode[pos] == '{') {
|
||||||
|
++depth;
|
||||||
|
} else if (glslCode[pos] == '}') {
|
||||||
|
--depth;
|
||||||
|
if (depth == 0) {
|
||||||
|
String clampLine;
|
||||||
|
for (const OutputClamp& outputClamp : outputClamps) {
|
||||||
|
const String minValue = outputClamp.Signed ? "-1.0" : "0.0";
|
||||||
|
clampLine += "\n " + outputClamp.Name + " = clamp(" + outputClamp.Name +
|
||||||
|
", vec4(" + minValue + "), vec4(1.0));";
|
||||||
|
}
|
||||||
|
clampLine += "\n";
|
||||||
|
glslCode.insert(pos, clampLine);
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
String result = glslCode;
|
||||||
|
const String integerType = R"((?:(?:lowp|mediump|highp)\s+)?(?:u?int|[iu]vec[234])\b)";
|
||||||
|
|
||||||
|
auto addFlatQualifier = [&result, &integerType](const String& qualifier) {
|
||||||
|
const std::regex pattern("(layout\\s*\\([^)]*\\)\\s*)(?!(?:flat|smooth|noperspective)\\s)(" +
|
||||||
|
qualifier + "\\s+" + integerType + ")");
|
||||||
|
result = std::regex_replace(result, pattern, "$1flat $2");
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (shaderType) {
|
||||||
|
case GL_VERTEX_SHADER:
|
||||||
|
addFlatQualifier("out");
|
||||||
|
break;
|
||||||
|
case GL_GEOMETRY_SHADER:
|
||||||
|
addFlatQualifier("in");
|
||||||
|
addFlatQualifier("out");
|
||||||
|
break;
|
||||||
|
case GL_FRAGMENT_SHADER:
|
||||||
|
addFlatQualifier("in");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
String RemoveLayoutBinding(const String& glslCode) {
|
String RemoveLayoutBinding(const String& glslCode) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
|
// Sampler and uniform-block bindings are re-established at draw time through the
|
||||||
|
// API, so their layout qualifiers are stripped (they may exceed ES limits). SSBO
|
||||||
|
// blocks and image uniforms are different: ES has no glShaderStorageBlockBinding,
|
||||||
|
// and image units cannot be set with glUniform1i, so for those declarations the
|
||||||
|
// binding qualifier is the only binding mechanism and must be preserved.
|
||||||
static std::regex bindingRegex(R"(layout\s*\(\s*binding\s*=\s*\d+\s*\)\s*)");
|
static std::regex bindingRegex(R"(layout\s*\(\s*binding\s*=\s*\d+\s*\)\s*)");
|
||||||
String result = std::regex_replace(glslCode, bindingRegex, "");
|
|
||||||
static std::regex bindingRegex2(R"(layout\s*\(\s*binding\s*=\s*\d+\s*,)");
|
static std::regex bindingRegex2(R"(layout\s*\(\s*binding\s*=\s*\d+\s*,)");
|
||||||
result = std::regex_replace(result, bindingRegex2, "layout(");
|
static std::regex keepBindingRegex(R"(\b(buffer|[iu]?image[A-Za-z0-9]*)\b)");
|
||||||
|
|
||||||
|
String result;
|
||||||
|
result.reserve(glslCode.size());
|
||||||
|
SizeT lineStart = 0;
|
||||||
|
while (lineStart <= glslCode.size()) {
|
||||||
|
SizeT lineEnd = glslCode.find('\n', lineStart);
|
||||||
|
const Bool lastLine = lineEnd == String::npos;
|
||||||
|
String line = glslCode.substr(lineStart, lastLine ? String::npos : lineEnd - lineStart);
|
||||||
|
|
||||||
|
if (!std::regex_search(line, keepBindingRegex)) {
|
||||||
|
line = std::regex_replace(line, bindingRegex, "");
|
||||||
|
line = std::regex_replace(line, bindingRegex2, "layout(");
|
||||||
|
}
|
||||||
|
|
||||||
|
result += line;
|
||||||
|
if (lastLine) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result += '\n';
|
||||||
|
lineStart = lineEnd + 1;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
@@ -118,7 +349,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,4 +452,317 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace ReadbackImpl
|
||||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||||
|
|||||||
@@ -35,14 +35,67 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
namespace TextureImpl {
|
namespace TextureImpl {
|
||||||
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
void GenerateTextureFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||||
GLenum* outFormat, GLenum* outType);
|
GLenum* outFormat, GLenum* outType,
|
||||||
|
TextureTarget target = TextureTarget::Unknown);
|
||||||
|
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||||
|
GLenum* outFormat, GLenum* outType);
|
||||||
|
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target);
|
||||||
|
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat);
|
||||||
} // namespace TextureImpl
|
} // namespace TextureImpl
|
||||||
|
|
||||||
namespace FramebufferImpl {} // namespace FramebufferImpl
|
namespace FramebufferImpl {} // namespace FramebufferImpl
|
||||||
|
|
||||||
|
// Pure CPU helpers of the client-format readback conversion (ReadPixels/GetTexImage repack a wide
|
||||||
|
// RGBA(_INTEGER) read into the caller's (format, type) layout). Kept context-free so unit tests can
|
||||||
|
// exercise the exact packing the GL CTS packed_pixels oracle compares against.
|
||||||
|
namespace ReadbackImpl {
|
||||||
|
struct ReadbackChannelMapping {
|
||||||
|
Int sourceChannel[4]; // RGBA source channel feeding each destination component
|
||||||
|
Int channelCount; // destination component count
|
||||||
|
Bool isInteger;
|
||||||
|
};
|
||||||
|
Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping);
|
||||||
|
|
||||||
|
// Byte size of one destination component of `type`; packed types report the packed word size.
|
||||||
|
// 0 = type not supported by the conversion path.
|
||||||
|
SizeT GetReadbackComponentSize(GLenum type);
|
||||||
|
|
||||||
|
// Bit-field layout of a GL packed pixel type. width/shift are indexed in the client format's
|
||||||
|
// component order (matching ReadbackChannelMapping); shift is the LSB position of the field in
|
||||||
|
// the packed word: non-REV types pack the first component from the MSB, *_REV types from the
|
||||||
|
// LSB (GL 3.3 table 3.6; field positions mirror the GL CTS glcPackedPixelsTests pack_* oracle).
|
||||||
|
struct PackedReadbackLayout {
|
||||||
|
Int fieldCount; // format components stored in the packed word
|
||||||
|
Int width[4]; // bit width of each component's field
|
||||||
|
Int shift[4]; // LSB bit position of each component's field
|
||||||
|
SizeT byteSize; // packed word size in bytes (1, 2 or 4)
|
||||||
|
Bool isFloatPacked; // 10F_11F_11F_REV / 5_9_9_9_REV: fields hold unsigned small floats
|
||||||
|
};
|
||||||
|
Bool GetPackedReadbackLayout(GLenum type, PackedReadbackLayout& out);
|
||||||
|
|
||||||
|
// Unsigned small-float encoders (EXT_packed_float / EXT_texture_shared_exponent semantics).
|
||||||
|
Uint32 EncodeFloatToUnsignedF11(Float value);
|
||||||
|
Uint32 EncodeFloatToUnsignedF10(Float value);
|
||||||
|
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]);
|
||||||
|
|
||||||
|
// Destination bytes per pixel for a (format mapping, type) readback pair; 0 when the pair is
|
||||||
|
// not convertible (unknown type, packed field count != format component count, floating-point
|
||||||
|
// or packed-float type with an integer format).
|
||||||
|
SizeT GetReadbackDstPixelSize(const ReadbackChannelMapping& mapping, GLenum type);
|
||||||
|
|
||||||
|
// Repacks one row of wide RGBA(_INTEGER) texels (4 components of wideType each) into the
|
||||||
|
// client's (format, type) layout. src holds width * 4 * GetReadbackComponentSize(wideType)
|
||||||
|
// bytes, dst receives width * GetReadbackDstPixelSize(mapping, type) bytes.
|
||||||
|
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
|
||||||
|
const ReadbackChannelMapping& mapping, GLenum type);
|
||||||
|
} // namespace ReadbackImpl
|
||||||
|
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
String ProcessOutColorLocations(const String& glslCode);
|
String ProcessOutColorLocations(const String& glslCode);
|
||||||
String ForceSupporterOutput(const String& glslCode);
|
String ForceSupporterOutput(const String& glslCode);
|
||||||
|
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||||
|
Uint32 unormOutputMask);
|
||||||
|
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType);
|
||||||
String RemoveLayoutBinding(const String& glslCode);
|
String RemoveLayoutBinding(const String& glslCode);
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,320 @@
|
|||||||
#include "BackendObject_DirectVulkan.h"
|
#include "BackendObject_DirectVulkan.h"
|
||||||
#include "MG_Backend/BackendObject.h"
|
#include "MG_Backend/BackendObject.h"
|
||||||
#include "DirectVulkan.h"
|
#include "DirectVulkan.h"
|
||||||
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
|
#include "MG_State/GLState/TextureState/TextureState.h"
|
||||||
|
#include "MG_Util/Classifiers/TextureEnumClassifier.h"
|
||||||
|
#include "MG_Util/Converters/MGToGL/TextureEnumConverter.h"
|
||||||
|
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||||
|
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
||||||
|
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
namespace {
|
namespace {
|
||||||
|
Bool IsR11G11B10FFallbackEnabled() {
|
||||||
|
return MG_Config::Features.MagmaR11G11B10FFallback;
|
||||||
|
}
|
||||||
|
|
||||||
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
Bool IsReleaseCurrentRequest(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||||
return dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
(void)dpy;
|
||||||
|
return draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool IsFormatIndexValid(TextureInternalFormat format) {
|
||||||
|
return format != TextureInternalFormat::Unknown && static_cast<Int>(format) >= 0 &&
|
||||||
|
static_cast<SizeT>(format) < kFormatCapabilityFormatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool IsLayeredTarget(TextureTarget target) {
|
||||||
|
return target == TextureTarget::Texture3D || target == TextureTarget::Texture1DArray ||
|
||||||
|
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMap ||
|
||||||
|
target == TextureTarget::TextureCubeMapArray ||
|
||||||
|
target == TextureTarget::Texture2DMultisampleArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool IsMultisampleTarget(TextureTarget target) {
|
||||||
|
return target == TextureTarget::Texture2DMultisample ||
|
||||||
|
target == TextureTarget::Texture2DMultisampleArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool IsTextureBufferTarget(TextureTarget target) {
|
||||||
|
return target == TextureTarget::TextureBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool IsIntegerInternalFormat(TextureInternalFormat format) {
|
||||||
|
const GLenum glFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format);
|
||||||
|
GLenum normalizedInternalFormat = glFormat;
|
||||||
|
GLenum imageFormat = GL_RGBA;
|
||||||
|
GLenum imageType = GL_UNSIGNED_BYTE;
|
||||||
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||||
|
glFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &imageFormat, &imageType);
|
||||||
|
return imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER || imageFormat == GL_RGB_INTEGER ||
|
||||||
|
imageFormat == GL_RGBA_INTEGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatCapabilityFlags GetAttachmentCaps(TextureInternalFormat format) {
|
||||||
|
FormatCapabilityFlags caps = FormatCapability::FramebufferRenderable;
|
||||||
|
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(format);
|
||||||
|
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(format);
|
||||||
|
if (!isDepth && !isStencil) {
|
||||||
|
caps |= FormatCapability::ColorAttachment;
|
||||||
|
}
|
||||||
|
if (isDepth) {
|
||||||
|
caps |= FormatCapability::DepthAttachment;
|
||||||
|
}
|
||||||
|
if (isStencil) {
|
||||||
|
caps |= FormatCapability::StencilAttachment;
|
||||||
|
}
|
||||||
|
return caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatCapabilityFlags BuildVulkanCaps(TextureInternalFormat logicalFormat,
|
||||||
|
TextureTarget target,
|
||||||
|
VkFormatFeatureFlags features) {
|
||||||
|
FormatCapabilityFlags caps;
|
||||||
|
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||||
|
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
|
||||||
|
const Bool isInteger = IsIntegerInternalFormat(logicalFormat);
|
||||||
|
|
||||||
|
if (IsTextureBufferTarget(target)) {
|
||||||
|
if ((features & VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) != 0) {
|
||||||
|
caps |= FormatCapability::Creatable;
|
||||||
|
caps |= FormatCapability::Sampled;
|
||||||
|
caps |= FormatCapability::TextureBuffer;
|
||||||
|
}
|
||||||
|
return caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Bool sampled = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
|
||||||
|
const Bool linearFilter = (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
|
||||||
|
const Bool colorRenderable = (features & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
|
||||||
|
const Bool depthStencilRenderable =
|
||||||
|
(features & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
|
||||||
|
const Bool renderable = (isDepth || isStencil) ? depthStencilRenderable : colorRenderable;
|
||||||
|
|
||||||
|
if (sampled || renderable) {
|
||||||
|
caps |= FormatCapability::Creatable;
|
||||||
|
}
|
||||||
|
if (sampled) {
|
||||||
|
caps |= FormatCapability::Sampled;
|
||||||
|
if (linearFilter && !isInteger && !isStencil) {
|
||||||
|
caps |= FormatCapability::LinearFilter;
|
||||||
|
}
|
||||||
|
if (!isStencil && (features & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0 &&
|
||||||
|
(features & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0) {
|
||||||
|
caps |= FormatCapability::GenerateMipmap;
|
||||||
|
}
|
||||||
|
if (!isInteger && !isDepth && !isStencil) {
|
||||||
|
caps |= FormatCapability::TextureGather;
|
||||||
|
}
|
||||||
|
if (isDepth && !isStencil) {
|
||||||
|
caps |= FormatCapability::TextureShadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (renderable) {
|
||||||
|
caps |= GetAttachmentCaps(logicalFormat);
|
||||||
|
if (IsLayeredTarget(target)) {
|
||||||
|
caps |= FormatCapability::FramebufferLayered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (IsMultisampleTarget(target)) {
|
||||||
|
caps |= FormatCapability::MultisampleTexture;
|
||||||
|
}
|
||||||
|
return caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<TextureInternalFormat> ResolveVulkanFallbackLogicalFormat(TextureInternalFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureInternalFormat::RGB:
|
||||||
|
case TextureInternalFormat::RGB8:
|
||||||
|
return TextureInternalFormat::RGBA8;
|
||||||
|
case TextureInternalFormat::SRGB8:
|
||||||
|
return TextureInternalFormat::SRGB8Alpha8;
|
||||||
|
case TextureInternalFormat::RGB8Snorm:
|
||||||
|
return TextureInternalFormat::RGBA8Snorm;
|
||||||
|
case TextureInternalFormat::RGB16:
|
||||||
|
return TextureInternalFormat::RGBA16;
|
||||||
|
case TextureInternalFormat::RGB16Snorm:
|
||||||
|
return TextureInternalFormat::RGBA16Snorm;
|
||||||
|
case TextureInternalFormat::RGB16F:
|
||||||
|
return TextureInternalFormat::RGBA16F;
|
||||||
|
case TextureInternalFormat::R11FG11FB10F:
|
||||||
|
if (IsR11G11B10FFallbackEnabled()) {
|
||||||
|
return TextureInternalFormat::RGBA16F;
|
||||||
|
}
|
||||||
|
return Nullopt;
|
||||||
|
case TextureInternalFormat::RGB32F:
|
||||||
|
return TextureInternalFormat::RGBA32F;
|
||||||
|
case TextureInternalFormat::RGB8I:
|
||||||
|
return TextureInternalFormat::RGBA8I;
|
||||||
|
case TextureInternalFormat::RGB8UI:
|
||||||
|
return TextureInternalFormat::RGBA8UI;
|
||||||
|
case TextureInternalFormat::RGB16I:
|
||||||
|
return TextureInternalFormat::RGBA16I;
|
||||||
|
case TextureInternalFormat::RGB16UI:
|
||||||
|
return TextureInternalFormat::RGBA16UI;
|
||||||
|
case TextureInternalFormat::RGB32I:
|
||||||
|
return TextureInternalFormat::RGBA32I;
|
||||||
|
case TextureInternalFormat::RGB32UI:
|
||||||
|
return TextureInternalFormat::RGBA32UI;
|
||||||
|
default:
|
||||||
|
return Nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<VkFormat> ResolveVulkanFallbackFormat(TextureInternalFormat format) {
|
||||||
|
const Optional<TextureInternalFormat> fallbackLogicalFormat = ResolveVulkanFallbackLogicalFormat(format);
|
||||||
|
if (!fallbackLogicalFormat) {
|
||||||
|
return Nullopt;
|
||||||
|
}
|
||||||
|
return MG_Util::ConvertTextureInternalFormatToVkEnum(*fallbackLogicalFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool HasNewCaveatFormatCaps(FormatCapabilityFlags nativeCaps, FormatCapabilityFlags fallbackCaps) {
|
||||||
|
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||||
|
if (HasFormatCapability(fallbackCaps, capability) &&
|
||||||
|
!HasFormatCapability(nativeCaps, capability)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogVulkanFormatCaveat(TextureInternalFormat logicalFormat,
|
||||||
|
SizeT targetIndex,
|
||||||
|
TextureInternalFormat fallbackFormat) {
|
||||||
|
MGLOG_D("Caveat: %s %s not fully supported. Reason: native Vulkan format is not fully supported. Fallback: %s",
|
||||||
|
GetFormatCapabilityTargetName(targetIndex).c_str(),
|
||||||
|
MG_Util::ConvertTextureInternalFormatToString(logicalFormat).c_str(),
|
||||||
|
MG_Util::ConvertTextureInternalFormatToString(fallbackFormat).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Int> BuildSampleCounts(Int maxSamples) {
|
||||||
|
Vector<Int> counts;
|
||||||
|
for (Int samples = std::max(maxSamples, 1); samples > 1; samples >>= 1) {
|
||||||
|
counts.push_back(samples);
|
||||||
|
}
|
||||||
|
counts.push_back(1);
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopulateFormatCapabilitiesImpl(VkPhysicalDevice physicalDevice,
|
||||||
|
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||||
|
const MG_External::VulkanCapabilities& capabilities,
|
||||||
|
FormatCapabilityCache& cache) {
|
||||||
|
cache.Clear();
|
||||||
|
if (physicalDevice == VK_NULL_HANDLE || getFormatProperties == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||||
|
const auto logicalFormat = static_cast<TextureInternalFormat>(formatIndex);
|
||||||
|
if (!IsFormatIndexValid(logicalFormat)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFormat nativeFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(logicalFormat);
|
||||||
|
const Optional<TextureInternalFormat> fallbackLogicalFormat =
|
||||||
|
ResolveVulkanFallbackLogicalFormat(logicalFormat);
|
||||||
|
VkFormat fallbackFormat = ResolveVulkanFallbackFormat(logicalFormat).value_or(VK_FORMAT_UNDEFINED);
|
||||||
|
|
||||||
|
VkFormatProperties nativeProperties{};
|
||||||
|
if (nativeFormat != VK_FORMAT_UNDEFINED) {
|
||||||
|
getFormatProperties(physicalDevice, nativeFormat, &nativeProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFormatProperties fallbackProperties{};
|
||||||
|
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||||
|
getFormatProperties(physicalDevice, fallbackFormat, &fallbackProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
||||||
|
const auto target = static_cast<TextureTarget>(targetIndex);
|
||||||
|
const VkFormatFeatureFlags nativeFeatures =
|
||||||
|
IsTextureBufferTarget(target) ? nativeProperties.bufferFeatures
|
||||||
|
: nativeProperties.optimalTilingFeatures;
|
||||||
|
FormatCapabilityFlags nativeCaps = BuildVulkanCaps(logicalFormat, target, nativeFeatures);
|
||||||
|
cache.FullCaps[targetIndex][formatIndex] |= nativeCaps;
|
||||||
|
|
||||||
|
const VkFormatFeatureFlags fallbackFeatures =
|
||||||
|
IsTextureBufferTarget(target) ? fallbackProperties.bufferFeatures
|
||||||
|
: fallbackProperties.optimalTilingFeatures;
|
||||||
|
FormatCapabilityFlags fallbackCaps = BuildVulkanCaps(logicalFormat, target, fallbackFeatures);
|
||||||
|
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||||
|
cache.CaveatCaps[targetIndex][formatIndex] |= fallbackCaps;
|
||||||
|
if (fallbackLogicalFormat && HasNewCaveatFormatCaps(nativeCaps, fallbackCaps)) {
|
||||||
|
LogVulkanFormatCaveat(logicalFormat, targetIndex, *fallbackLogicalFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasFormatCapability(nativeCaps | fallbackCaps, FormatCapability::MultisampleTexture)) {
|
||||||
|
const Bool isDepth = MG_Util::IsDepthFormatInternalFormat(logicalFormat);
|
||||||
|
const Bool isStencil = MG_Util::IsStencilFormatInternalFormat(logicalFormat);
|
||||||
|
const Bool isInteger = IsIntegerInternalFormat(logicalFormat);
|
||||||
|
Int maxSamples = capabilities.MaxColorTextureSamples;
|
||||||
|
if (isDepth || isStencil) {
|
||||||
|
maxSamples = capabilities.MaxDepthTextureSamples;
|
||||||
|
} else if (isInteger) {
|
||||||
|
maxSamples = capabilities.MaxIntegerSamples;
|
||||||
|
}
|
||||||
|
cache.SampleCounts[targetIndex][formatIndex] = BuildSampleCounts(maxSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex();
|
||||||
|
FormatCapabilityFlags renderbufferCaps =
|
||||||
|
BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D, nativeProperties.optimalTilingFeatures);
|
||||||
|
renderbufferCaps &= FormatCapability::Creatable;
|
||||||
|
if ((nativeProperties.optimalTilingFeatures &
|
||||||
|
(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0) {
|
||||||
|
renderbufferCaps |= GetAttachmentCaps(logicalFormat);
|
||||||
|
renderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
||||||
|
}
|
||||||
|
cache.FullCaps[renderbufferTargetIndex][formatIndex] |= renderbufferCaps;
|
||||||
|
|
||||||
|
if (fallbackFormat != VK_FORMAT_UNDEFINED && fallbackFormat != nativeFormat) {
|
||||||
|
FormatCapabilityFlags fallbackRenderbufferCaps =
|
||||||
|
BuildVulkanCaps(logicalFormat, TextureTarget::Texture2D,
|
||||||
|
fallbackProperties.optimalTilingFeatures);
|
||||||
|
fallbackRenderbufferCaps &= FormatCapability::Creatable;
|
||||||
|
if ((fallbackProperties.optimalTilingFeatures &
|
||||||
|
(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) !=
|
||||||
|
0) {
|
||||||
|
fallbackRenderbufferCaps |= GetAttachmentCaps(logicalFormat);
|
||||||
|
fallbackRenderbufferCaps |= FormatCapability::MultisampleRenderbuffer;
|
||||||
|
}
|
||||||
|
cache.CaveatCaps[renderbufferTargetIndex][formatIndex] |= fallbackRenderbufferCaps;
|
||||||
|
if (fallbackLogicalFormat &&
|
||||||
|
HasNewCaveatFormatCaps(renderbufferCaps, fallbackRenderbufferCaps)) {
|
||||||
|
LogVulkanFormatCaveat(logicalFormat, renderbufferTargetIndex, *fallbackLogicalFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormatCapabilityFlags rbCaps = cache.FullCaps[renderbufferTargetIndex][formatIndex] |
|
||||||
|
cache.CaveatCaps[renderbufferTargetIndex][formatIndex];
|
||||||
|
if (HasFormatCapability(rbCaps, FormatCapability::MultisampleRenderbuffer)) {
|
||||||
|
cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
|
||||||
|
BuildSampleCounts(capabilities.MaxFramebufferSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
||||||
|
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||||
|
const MG_External::VulkanCapabilities& capabilities,
|
||||||
|
FormatCapabilityCache& cache) {
|
||||||
|
PopulateFormatCapabilitiesImpl(physicalDevice, getFormatProperties, capabilities, cache);
|
||||||
|
}
|
||||||
|
|
||||||
BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default;
|
BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default;
|
||||||
|
|
||||||
|
BackendObject_DirectVulkan::BackendObject_DirectVulkan(): m_rendererInfo{GetRendererIdentity()} {}
|
||||||
|
|
||||||
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
Bool BackendObject_DirectVulkan::InitWindowSurface() {
|
||||||
if (!m_windowHandle.Handle) {
|
if (!m_windowHandle.Handle) {
|
||||||
MGLOG_E("Cannot initialize DirectVulkan window surface: native window handle is null");
|
MGLOG_E("Cannot initialize DirectVulkan window surface: native window handle is null");
|
||||||
@@ -27,12 +331,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;
|
||||||
}
|
}
|
||||||
@@ -47,8 +367,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MG_Util::BackendLoader::FillInVulkanCapabilities(m_vulkanCaps, pVulkanRenderer->GetPhysicalDevice().properties);
|
const auto& physicalDevice = pVulkanRenderer->GetPhysicalDevice();
|
||||||
|
if (!MG_Util::BackendLoader::QueryVulkanCapabilities(m_vulkanCaps, pVulkanRenderer->GetInstance(),
|
||||||
|
physicalDevice.handle)) {
|
||||||
|
MGLOG_W("DirectVulkan: failed to query extended Vulkan capabilities, using basic properties");
|
||||||
|
MG_Util::BackendLoader::FillInVulkanCapabilities(m_vulkanCaps, physicalDevice.properties);
|
||||||
|
}
|
||||||
UpdateDynamicBackendParameters();
|
UpdateDynamicBackendParameters();
|
||||||
|
UpdateAdvertisedExtensions();
|
||||||
|
PopulateFormatCapabilities(physicalDevice.handle, vkGetPhysicalDeviceFormatProperties, m_vulkanCaps,
|
||||||
|
MutableFormatCapabilities());
|
||||||
|
PrintFormatCapabilities(GetFormatCapabilities());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,40 +389,48 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return BackendObject::InitializeEGLDisplay(dpy, major, minor);
|
return BackendObject::InitializeEGLDisplay(dpy, major, minor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool BackendObject_DirectVulkan::CreateEGLWindowSurface(const WindowHandle& handle) {
|
Bool BackendObject_DirectVulkan::CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
if (!m_initialized) {
|
if (!m_initialized) {
|
||||||
MGLOG_E("DirectVulkan backend not initialized");
|
MGLOG_E("DirectVulkan backend not initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (handle.Backend != WindowBackend::Android || !handle.Handle) {
|
if (!handle.Handle || (handle.Backend != WindowBackend::Android &&
|
||||||
MGLOG_E("DirectVulkan backend only supports Android native windows");
|
handle.Backend != WindowBackend::X11 &&
|
||||||
|
handle.Backend != WindowBackend::MetalLayer)) {
|
||||||
|
MGLOG_E("DirectVulkan backend only supports Android, X11, and CAMetalLayer native windows");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bool sameHandle =
|
return RegisterEGLWindowSurface(surface, handle);
|
||||||
m_eglWindowSurfaceInitialized && m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle;
|
}
|
||||||
if (sameHandle) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_eglWindowSurfaceInitialized || pVulkanRenderer) {
|
Bool BackendObject_DirectVulkan::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||||
pVulkanRenderer.reset();
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
ResetEGLRuntimeState();
|
if (!m_initialized) {
|
||||||
|
MGLOG_E("DirectVulkan backend not initialized");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!BackendObject::ResizeEGLWindowSurface(surface, width, height)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pVulkanRenderer && m_eglSurface == surface) {
|
||||||
|
pVulkanRenderer->RequestSwapchainResize(width, height);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return BackendObject::CreateEGLWindowSurface(handle);
|
Bool BackendObject_DirectVulkan::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
if (!m_initialized) {
|
||||||
|
MGLOG_E("DirectVulkan backend not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return RegisterEGLPbufferSurface(surface, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool BackendObject_DirectVulkan::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
Bool BackendObject_DirectVulkan::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
if (IsReleaseCurrentRequest(dpy, draw, read, ctx)) {
|
|
||||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
|
||||||
}
|
|
||||||
if (!pVulkanRenderer) {
|
|
||||||
MGLOG_E("DirectVulkan renderer is not initialized");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
return BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,33 +443,95 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return BackendObject::SwapEGLBuffers(dpy, draw);
|
return BackendObject::SwapEGLBuffers(dpy, draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BackendObject_DirectVulkan::ReleaseEGLSurface(EGLSurface surface) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
BackendObject::ReleaseEGLSurface(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendObject_DirectVulkan::ReleaseEGLResources() {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||||
|
// Outstanding fence/timer-query handles now refer to a dead renderer;
|
||||||
|
// treat them as signaled/available with zero results from here on.
|
||||||
|
BumpRendererGeneration();
|
||||||
|
pVulkanRenderer.reset();
|
||||||
|
BackendObject::ReleaseEGLResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendObject_DirectVulkan::OnEGLSurfaceReleased(EGLSurface surface) {
|
||||||
|
(void)surface;
|
||||||
|
// Outstanding fence/timer-query handles now refer to a dead renderer;
|
||||||
|
// treat them as signaled/available with zero results from here on.
|
||||||
|
BumpRendererGeneration();
|
||||||
|
pVulkanRenderer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const {
|
||||||
static RendererInfo RendererInfo = {
|
return m_rendererInfo;
|
||||||
.RendererName = "Magma", // Renderer Name
|
|
||||||
.BackendName = "Direct (Vulkan)", // Backend Name
|
|
||||||
.ExtraVendor = Nullopt, // Extra vendor
|
|
||||||
.RendererGLInfo =
|
|
||||||
{
|
|
||||||
.TargetGLVersion = {3, 3, 0}, // Target OpenGL Version
|
|
||||||
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
|
||||||
.Extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32, // OpenGL Extensions
|
|
||||||
V_OpenGL33},
|
|
||||||
.IsCompatibilityProfile = false // Is Compatibility Profile
|
|
||||||
},
|
|
||||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
|
|
||||||
};
|
|
||||||
return RendererInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String BackendObject_DirectVulkan::GetBackendAPIVersionString() const {
|
String BackendObject_DirectVulkan::GetBackendAPIVersionString() const {
|
||||||
if (!m_initialized) {
|
if (!m_initialized) {
|
||||||
return "<uninitialized DirectVulkan backend>";
|
return "<uninitialized DirectVulkan backend>";
|
||||||
}
|
}
|
||||||
|
return FormatBackendAPIVersionString(m_vulkanCaps.DeviceName, m_vulkanCaps.VulkanAPIVersion.toString(),
|
||||||
|
m_vulkanCaps.DriverVersionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RendererInfo& GetRendererIdentity() {
|
||||||
|
static const RendererInfo rendererInfo = {
|
||||||
|
.RendererName = "Magma",
|
||||||
|
.BackendName = "Direct (Vulkan)",
|
||||||
|
.ExtraVendor = Nullopt,
|
||||||
|
.RendererGLInfo =
|
||||||
|
{
|
||||||
|
.TargetGLVersion = {3, 3, 0},
|
||||||
|
.TargetGLSLVersion = {4, 6, 0},
|
||||||
|
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||||
|
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||||
|
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
||||||
|
.IsCompatibilityProfile = false
|
||||||
|
},
|
||||||
|
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
||||||
|
return rendererInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||||
|
Bool anisotropicFilteringSupported) {
|
||||||
|
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||||
|
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||||
|
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||||
|
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object,
|
||||||
|
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||||
|
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||||
|
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample,
|
||||||
|
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||||
|
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug,
|
||||||
|
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
|
||||||
|
E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size};
|
||||||
|
if (shaderSubgroupSupported && !MG_Config::Features.DisableSubgroup) {
|
||||||
|
extensions.push_back(E_GL_KHR_shader_subgroup);
|
||||||
|
}
|
||||||
|
// GL_ARB_timer_query gates MC's F3 GPU% (LWJGL checks the extension string);
|
||||||
|
// only advertised when the device actually supports timestamp queries and the
|
||||||
|
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is off.
|
||||||
|
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
|
||||||
|
extensions.push_back(E_GL_ARB_timer_query);
|
||||||
|
}
|
||||||
|
// Only advertised when the samplerAnisotropy device feature was granted: without it the
|
||||||
|
// sampler state is accepted but never applied, and an app trusting the string (LWJGL builds
|
||||||
|
// GLCapabilities from it) would think it enabled anisotropic filtering.
|
||||||
|
if (anisotropicFilteringSupported) {
|
||||||
|
extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
|
||||||
|
extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
|
||||||
|
}
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
String FormatBackendAPIVersionString(const String& deviceName, const String& vulkanApiVersionString,
|
||||||
|
const String& driverVersionString) {
|
||||||
// Format:
|
// Format:
|
||||||
// <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version>
|
// <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version>
|
||||||
String str = m_vulkanCaps.DeviceName + ", Vulkan " + m_vulkanCaps.VulkanAPIVersion.toString() + ", Driver " +
|
return deviceName + ", Vulkan " + vulkanApiVersionString + ", Driver " + driverVersionString;
|
||||||
m_vulkanCaps.DriverVersionString;
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BackendType BackendObject_DirectVulkan::GetBackendType() const {
|
BackendType BackendObject_DirectVulkan::GetBackendType() const {
|
||||||
@@ -147,10 +546,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.DrawArrays = DrawArrays;
|
funcsTable.GL.DrawArrays = DrawArrays;
|
||||||
funcsTable.GL.DrawElements = DrawElements;
|
funcsTable.GL.DrawElements = DrawElements;
|
||||||
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
||||||
|
funcsTable.GL.MultiDrawArrays = MultiDrawArrays;
|
||||||
funcsTable.GL.MultiDrawElements = MultiDrawElements;
|
funcsTable.GL.MultiDrawElements = MultiDrawElements;
|
||||||
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
||||||
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
||||||
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
||||||
|
funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
|
||||||
|
funcsTable.GL.MultiDrawArraysIndirectCount = MultiDrawArraysIndirectCount;
|
||||||
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
|
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
|
||||||
funcsTable.GL.DrawRangeElements = DrawRangeElements;
|
funcsTable.GL.DrawRangeElements = DrawRangeElements;
|
||||||
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
|
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
|
||||||
@@ -166,12 +568,51 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.ClearBufferfv = ClearBufferfv;
|
funcsTable.GL.ClearBufferfv = ClearBufferfv;
|
||||||
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
||||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||||
|
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||||
|
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||||
|
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||||
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
||||||
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
||||||
|
funcsTable.GL.CopyImageSubData = CopyImageSubData;
|
||||||
funcsTable.GL.GenerateMipmap = GenerateMipmap;
|
funcsTable.GL.GenerateMipmap = GenerateMipmap;
|
||||||
funcsTable.GL.ReadPixels = ReadPixels;
|
funcsTable.GL.ReadPixels = ReadPixels;
|
||||||
funcsTable.GL.GetTexImage = GetTexImage;
|
funcsTable.GL.GetTexImage = GetTexImage;
|
||||||
|
funcsTable.GL.GetTextureImage = GetTextureImage;
|
||||||
|
funcsTable.GL.DispatchCompute = DispatchCompute;
|
||||||
|
funcsTable.GL.DispatchComputeIndirect = DispatchComputeIndirect;
|
||||||
|
funcsTable.GL.MemoryBarrier = MemoryBarrier;
|
||||||
|
funcsTable.GL.MemoryBarrierByRegion = MemoryBarrierByRegion;
|
||||||
|
funcsTable.GL.BindImageTexture = BindImageTexture;
|
||||||
|
funcsTable.GL.GetIntegeri_v = GetIntegeri_v;
|
||||||
|
funcsTable.GL.GetInteger64i_v = GetInteger64i_v;
|
||||||
|
funcsTable.GL.GetProgramiv = GetProgramiv;
|
||||||
|
funcsTable.GL.GetProgramInterfaceiv = GetProgramInterfaceiv;
|
||||||
|
funcsTable.GL.GetProgramResourceIndex = GetProgramResourceIndex;
|
||||||
|
funcsTable.GL.GetProgramResourceName = GetProgramResourceName;
|
||||||
|
funcsTable.GL.GetProgramResourceiv = GetProgramResourceiv;
|
||||||
|
funcsTable.GL.GetProgramResourceLocation = GetProgramResourceLocation;
|
||||||
|
funcsTable.GL.GetProgramResourceLocationIndex = GetProgramResourceLocationIndex;
|
||||||
|
funcsTable.GL.ShaderStorageBlockBinding = ShaderStorageBlockBinding;
|
||||||
|
funcsTable.GL.FenceSync = FenceSync;
|
||||||
|
funcsTable.GL.ClientWaitSync = ClientWaitSync;
|
||||||
|
funcsTable.GL.WaitSync = WaitSync;
|
||||||
|
funcsTable.GL.DeleteSync = DeleteSync;
|
||||||
|
funcsTable.GL.GetSyncStatus = GetSyncStatus;
|
||||||
|
// Optional timer-query group: left null (the frontend then falls
|
||||||
|
// back) when disabled via MOBILEGL_DISABLE_TIMERQUERY. The hooks
|
||||||
|
// themselves additionally degrade to null handles when the device
|
||||||
|
// lacks timestamp support.
|
||||||
|
if (!MG_Config::Features.DisableTimerQuery) {
|
||||||
|
funcsTable.GL.IsTimerQuerySupported = IsTimerQuerySupported;
|
||||||
|
funcsTable.GL.BeginTimeElapsedQuery = BeginTimeElapsedQuery;
|
||||||
|
funcsTable.GL.EndTimeElapsedQuery = EndTimeElapsedQuery;
|
||||||
|
funcsTable.GL.QueryCounterTimestamp = QueryCounterTimestamp;
|
||||||
|
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||||
|
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
||||||
|
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||||
|
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
||||||
|
}
|
||||||
funcsTableInitialized = true;
|
funcsTableInitialized = true;
|
||||||
}
|
}
|
||||||
return funcsTable;
|
return funcsTable;
|
||||||
@@ -181,7 +622,177 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return m_dynamicParameters;
|
return m_dynamicParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BackendObject_DirectVulkan::ApplyVulkanCapabilitiesForTesting(
|
||||||
|
const MG_External::VulkanCapabilities& capabilities) {
|
||||||
|
m_vulkanCaps = capabilities;
|
||||||
|
UpdateDynamicBackendParameters();
|
||||||
|
UpdateAdvertisedExtensions();
|
||||||
|
MutableFormatCapabilities().Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendObject_DirectVulkan::UpdateAdvertisedExtensions() {
|
||||||
|
// GL_ARB_timer_query gates MC's F3 GPU% (LWJGL checks the extension
|
||||||
|
// string). InitCapabilities runs after InitWindowSurface has created
|
||||||
|
// and initialized the renderer, so the advertisement can be gated on
|
||||||
|
// real device timestamp support. ApplyVulkanCapabilitiesForTesting may
|
||||||
|
// run without a renderer; no timer query is advertised then. Rebuilding
|
||||||
|
// the whole list keeps re-runs idempotent.
|
||||||
|
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
|
||||||
|
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
|
||||||
|
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported());
|
||||||
|
}
|
||||||
|
|
||||||
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
||||||
|
const auto mapShaderStages = [](Uint32 vkStages) {
|
||||||
|
Uint32 glStages = 0;
|
||||||
|
if ((vkStages & VK_SHADER_STAGE_VERTEX_BIT) != 0) glStages |= GL_VERTEX_SHADER_BIT;
|
||||||
|
if ((vkStages & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) != 0) glStages |= GL_TESS_CONTROL_SHADER_BIT;
|
||||||
|
if ((vkStages & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) != 0) {
|
||||||
|
glStages |= GL_TESS_EVALUATION_SHADER_BIT;
|
||||||
|
}
|
||||||
|
if ((vkStages & VK_SHADER_STAGE_GEOMETRY_BIT) != 0) glStages |= GL_GEOMETRY_SHADER_BIT;
|
||||||
|
if ((vkStages & VK_SHADER_STAGE_FRAGMENT_BIT) != 0) glStages |= GL_FRAGMENT_SHADER_BIT;
|
||||||
|
if ((vkStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0) glStages |= GL_COMPUTE_SHADER_BIT;
|
||||||
|
return glStages;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto mapSubgroupFeatures = [](Uint32 vkFeatures) {
|
||||||
|
Uint32 glFeatures = 0;
|
||||||
|
if ((vkFeatures & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0) {
|
||||||
|
glFeatures |= GL_SUBGROUP_FEATURE_BASIC_BIT_KHR;
|
||||||
|
}
|
||||||
|
if ((vkFeatures & VK_SUBGROUP_FEATURE_VOTE_BIT) != 0) {
|
||||||
|
glFeatures |= GL_SUBGROUP_FEATURE_VOTE_BIT_KHR;
|
||||||
|
}
|
||||||
|
if ((vkFeatures & VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) != 0) {
|
||||||
|
glFeatures |= GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR;
|
||||||
|
}
|
||||||
|
if ((vkFeatures & VK_SUBGROUP_FEATURE_BALLOT_BIT) != 0) {
|
||||||
|
glFeatures |= GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR;
|
||||||
|
}
|
||||||
|
if ((vkFeatures & VK_SUBGROUP_FEATURE_SHUFFLE_BIT) != 0) {
|
||||||
|
glFeatures |= GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR;
|
||||||
|
}
|
||||||
|
if ((vkFeatures & VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT) != 0) {
|
||||||
|
glFeatures |= GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR;
|
||||||
|
}
|
||||||
|
if ((vkFeatures & VK_SUBGROUP_FEATURE_CLUSTERED_BIT) != 0) {
|
||||||
|
glFeatures |= GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR;
|
||||||
|
}
|
||||||
|
if ((vkFeatures & VK_SUBGROUP_FEATURE_QUAD_BIT) != 0) {
|
||||||
|
glFeatures |= GL_SUBGROUP_FEATURE_QUAD_BIT_KHR;
|
||||||
|
}
|
||||||
|
return glFeatures;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr SizeT kMaxAdvertisedShaderStorageBlockSize = 512ull * 1024ull * 1024ull;
|
||||||
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
|
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
|
||||||
|
m_dynamicParameters.AliasedLineWidthRangeMin = m_vulkanCaps.AliasedLineWidthRangeMin;
|
||||||
|
m_dynamicParameters.AliasedLineWidthRangeMax = m_vulkanCaps.AliasedLineWidthRangeMax;
|
||||||
|
// Without the samplerAnisotropy feature the limit is unusable, so report 1.0 (no anisotropy)
|
||||||
|
// rather than a maximum the sampler manager will never apply.
|
||||||
|
m_dynamicParameters.MaxTextureMaxAnisotropy =
|
||||||
|
(pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()) ? m_vulkanCaps.MaxSamplerAnisotropy
|
||||||
|
: 1.0f;
|
||||||
|
m_dynamicParameters.SmoothLineWidthRangeMin = m_vulkanCaps.SmoothLineWidthRangeMin;
|
||||||
|
m_dynamicParameters.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
|
||||||
|
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
|
||||||
|
m_dynamicParameters.PointSizeRangeMin = m_vulkanCaps.PointSizeRangeMin;
|
||||||
|
m_dynamicParameters.PointSizeRangeMax = m_vulkanCaps.PointSizeRangeMax;
|
||||||
|
m_dynamicParameters.PointSizeGranularity = m_vulkanCaps.PointSizeGranularity;
|
||||||
|
m_dynamicParameters.Max3DTextureSize = m_vulkanCaps.Max3DTextureSize;
|
||||||
|
m_dynamicParameters.MaxArrayTextureLayers = m_vulkanCaps.MaxArrayTextureLayers;
|
||||||
|
m_dynamicParameters.MaxCubeMapTextureSize = m_vulkanCaps.MaxCubeMapTextureSize;
|
||||||
|
m_dynamicParameters.MaxFramebufferWidth = m_vulkanCaps.MaxFramebufferWidth;
|
||||||
|
m_dynamicParameters.MaxFramebufferHeight = m_vulkanCaps.MaxFramebufferHeight;
|
||||||
|
m_dynamicParameters.MaxFramebufferLayers = m_vulkanCaps.MaxFramebufferLayers;
|
||||||
|
m_dynamicParameters.MaxRenderbufferSize = m_vulkanCaps.MaxRenderbufferSize;
|
||||||
|
m_dynamicParameters.MaxTextureSize = m_vulkanCaps.MaxTextureSize;
|
||||||
|
m_dynamicParameters.MaxColorTextureSamples = m_vulkanCaps.MaxColorTextureSamples;
|
||||||
|
m_dynamicParameters.MaxDepthTextureSamples = m_vulkanCaps.MaxDepthTextureSamples;
|
||||||
|
m_dynamicParameters.MaxFramebufferSamples = m_vulkanCaps.MaxFramebufferSamples;
|
||||||
|
m_dynamicParameters.MaxIntegerSamples = m_vulkanCaps.MaxIntegerSamples;
|
||||||
|
m_dynamicParameters.MaxSamples = m_vulkanCaps.MaxSamples;
|
||||||
|
m_dynamicParameters.MaxSampleMaskWords = m_vulkanCaps.MaxSampleMaskWords;
|
||||||
|
const Int maxSupportedTextureUnits =
|
||||||
|
static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||||
|
// GL_MAX_TEXTURE_IMAGE_UNITS is a *per-stage* sampler limit. Adreno/Qualcomm report a huge
|
||||||
|
// maxPerStageDescriptorSampledImages (descriptor-indexing scale), so clamping it only to our
|
||||||
|
// combined array capacity (192) still advertises 192 per stage. Host code treats this value as
|
||||||
|
// an array bound: Minecraft's Blaze3D GlStateManager.TEXTURES[] holds 128 entries and Iris
|
||||||
|
// iterates [0, GL_MAX_TEXTURE_IMAGE_UNITS) over it (CompositeRenderer.renderAll), so any value
|
||||||
|
// > 128 throws ArrayIndexOutOfBoundsException. Match desktop drivers (32) for the per-stage
|
||||||
|
// limits while keeping the combined limit at our texture-unit array capacity.
|
||||||
|
constexpr Int maxPerStageTextureUnits =
|
||||||
|
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
|
||||||
|
m_dynamicParameters.MaxTextureImageUnits =
|
||||||
|
std::min(m_vulkanCaps.MaxTextureImageUnits, maxPerStageTextureUnits);
|
||||||
|
m_dynamicParameters.MaxVertexTextureImageUnits =
|
||||||
|
std::min(m_vulkanCaps.MaxVertexTextureImageUnits, maxPerStageTextureUnits);
|
||||||
|
m_dynamicParameters.MaxComputeTextureImageUnits =
|
||||||
|
std::min(m_vulkanCaps.MaxComputeTextureImageUnits, maxPerStageTextureUnits);
|
||||||
|
m_dynamicParameters.MaxCombinedTextureImageUnits =
|
||||||
|
std::min(m_vulkanCaps.MaxCombinedTextureImageUnits, maxSupportedTextureUnits);
|
||||||
|
// Never advertise more attributes than the state layer can store: the current-value array and
|
||||||
|
// the Uint32 attribute masks the draw path passes around are both bounded by MAX_VERTEX_ATTRIBS.
|
||||||
|
m_dynamicParameters.MaxVertexAttribs =
|
||||||
|
std::min(m_vulkanCaps.MaxVertexAttribs,
|
||||||
|
static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS));
|
||||||
|
m_dynamicParameters.MaxComputeShaderStorageBlocks = m_vulkanCaps.MaxComputeShaderStorageBlocks;
|
||||||
|
m_dynamicParameters.MaxCombinedShaderStorageBlocks = m_vulkanCaps.MaxCombinedShaderStorageBlocks;
|
||||||
|
m_dynamicParameters.MaxComputeUniformBlocks = m_vulkanCaps.MaxComputeUniformBlocks;
|
||||||
|
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations;
|
||||||
|
m_dynamicParameters.MaxShaderStorageBufferBindings = m_vulkanCaps.MaxShaderStorageBufferBindings;
|
||||||
|
m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize;
|
||||||
|
m_dynamicParameters.MaxUniformBufferBindings = m_vulkanCaps.MaxUniformBufferBindings;
|
||||||
|
m_dynamicParameters.MaxUniformBlockSize = m_vulkanCaps.MaxUniformBlockSize;
|
||||||
|
m_dynamicParameters.MaxImageUnits =
|
||||||
|
std::max(std::min(m_vulkanCaps.MaxImageUnits, maxSupportedTextureUnits), 0);
|
||||||
|
m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_vulkanCaps.MaxCombinedImageUniforms, 0);
|
||||||
|
const Int maxPerStageImageUniforms =
|
||||||
|
std::min(m_dynamicParameters.MaxImageUnits, m_dynamicParameters.MaxCombinedImageUniforms);
|
||||||
|
// Vulkan uses one descriptor limit for every stage, but non-compute stores/atomics are
|
||||||
|
// optional device features. VulkanRenderer enables each feature whenever the physical
|
||||||
|
// device reports it, so these are the exact limits the logical device can compile and run.
|
||||||
|
m_dynamicParameters.MaxVertexImageUniforms =
|
||||||
|
m_vulkanCaps.SupportsVertexPipelineStoresAndAtomics ? maxPerStageImageUniforms : 0;
|
||||||
|
m_dynamicParameters.MaxGeometryImageUniforms =
|
||||||
|
m_vulkanCaps.SupportsVertexPipelineStoresAndAtomics && m_vulkanCaps.SupportsGeometryShader
|
||||||
|
? maxPerStageImageUniforms
|
||||||
|
: 0;
|
||||||
|
m_dynamicParameters.MaxFragmentImageUniforms =
|
||||||
|
m_vulkanCaps.SupportsFragmentStoresAndAtomics ? maxPerStageImageUniforms : 0;
|
||||||
|
m_dynamicParameters.MaxComputeImageUniforms =
|
||||||
|
std::min(std::max(m_vulkanCaps.MaxComputeImageUniforms, 0), maxPerStageImageUniforms);
|
||||||
|
const Int maxSupportedDrawBuffers =
|
||||||
|
static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);
|
||||||
|
m_dynamicParameters.MaxDrawBuffers = std::min(m_vulkanCaps.MaxDrawBuffers, maxSupportedDrawBuffers);
|
||||||
|
m_dynamicParameters.MaxColorAttachments = std::min(m_vulkanCaps.MaxColorAttachments, maxSupportedDrawBuffers);
|
||||||
|
m_dynamicParameters.MaxClipDistances = m_vulkanCaps.MaxClipDistances;
|
||||||
|
m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports;
|
||||||
|
m_dynamicParameters.MaxViewportWidth = m_vulkanCaps.MaxViewportWidth;
|
||||||
|
m_dynamicParameters.MaxViewportHeight = m_vulkanCaps.MaxViewportHeight;
|
||||||
|
m_dynamicParameters.ViewportBoundsRangeMin = m_vulkanCaps.ViewportBoundsRangeMin;
|
||||||
|
m_dynamicParameters.ViewportBoundsRangeMax = m_vulkanCaps.ViewportBoundsRangeMax;
|
||||||
|
m_dynamicParameters.ViewportSubpixelBits = m_vulkanCaps.ViewportSubpixelBits;
|
||||||
|
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
||||||
|
m_dynamicParameters.MaxShaderStorageBlockSize =
|
||||||
|
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
||||||
|
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
||||||
|
m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize;
|
||||||
|
m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages);
|
||||||
|
m_dynamicParameters.SubgroupSupportedFeatures = mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations);
|
||||||
|
m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages;
|
||||||
|
} else {
|
||||||
|
m_dynamicParameters.SubgroupSize = 0;
|
||||||
|
m_dynamicParameters.SubgroupSupportedStages = 0;
|
||||||
|
m_dynamicParameters.SubgroupSupportedFeatures = 0;
|
||||||
|
m_dynamicParameters.SubgroupQuadOperationsInAllStages = false;
|
||||||
|
}
|
||||||
|
if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) {
|
||||||
|
MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu",
|
||||||
|
m_vulkanCaps.MaxShaderStorageBlockSize,
|
||||||
|
m_dynamicParameters.MaxShaderStorageBlockSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -12,29 +12,73 @@
|
|||||||
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
|
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
// Populates the same format-capability cache used by backend startup. Passing the
|
||||||
|
// instance-resolved function keeps standalone callers independent of global loader
|
||||||
|
// initialization; the physical device must remain valid for the duration of the call.
|
||||||
|
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
|
||||||
|
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||||
|
const MG_External::VulkanCapabilities& capabilities,
|
||||||
|
FormatCapabilityCache& cache);
|
||||||
|
|
||||||
class BackendObject_DirectVulkan : public BackendObject {
|
class BackendObject_DirectVulkan : public BackendObject {
|
||||||
public:
|
public:
|
||||||
|
BackendObject_DirectVulkan();
|
||||||
~BackendObject_DirectVulkan() override;
|
~BackendObject_DirectVulkan() override;
|
||||||
|
|
||||||
void Initialize() override;
|
void Initialize() override;
|
||||||
Bool InitWindowSurface() override;
|
Bool InitWindowSurface() override;
|
||||||
Bool InitCapabilities() override;
|
Bool InitCapabilities() override;
|
||||||
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
|
||||||
Bool CreateEGLWindowSurface(const WindowHandle& handle) override;
|
Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override;
|
||||||
|
Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) override;
|
||||||
|
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
|
||||||
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
|
||||||
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
|
||||||
|
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||||
|
void ReleaseEGLResources() override;
|
||||||
|
|
||||||
const RendererInfo& GetRendererInfo() const override;
|
const RendererInfo& GetRendererInfo() const override;
|
||||||
String GetBackendAPIVersionString() const override;
|
String GetBackendAPIVersionString() const override;
|
||||||
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
|
const GlobalBackendFunctionsTable& GetBackendFunctions() const override;
|
||||||
const DynamicBackendParameters& GetDynamicParameters() const override;
|
const DynamicBackendParameters& GetDynamicParameters() const override;
|
||||||
BackendType GetBackendType() const override;
|
BackendType GetBackendType() const override;
|
||||||
|
void ApplyVulkanCapabilitiesForTesting(const MG_External::VulkanCapabilities& capabilities);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||||
|
void OnEGLSurfaceReleased(EGLSurface surface) override;
|
||||||
|
void UpdateAdvertisedExtensions();
|
||||||
void UpdateDynamicBackendParameters();
|
void UpdateDynamicBackendParameters();
|
||||||
|
|
||||||
Bool m_initialized = false;
|
Bool m_initialized = false;
|
||||||
DynamicBackendParameters m_dynamicParameters;
|
DynamicBackendParameters m_dynamicParameters;
|
||||||
MG_External::VulkanCapabilities m_vulkanCaps;
|
MG_External::VulkanCapabilities m_vulkanCaps;
|
||||||
|
RendererInfo m_rendererInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Single-source-of-truth helpers shared with the driver POST
|
||||||
|
// (MG_Util/SelfTest/DriverPost.cpp), so the identity strings and extension list
|
||||||
|
// MobileGL reports to applications on this backend cannot drift from what the
|
||||||
|
// POST screen shows.
|
||||||
|
|
||||||
|
// Static identity of the Magma renderer (renderer/backend names, target GL/GLSL
|
||||||
|
// versions, ExtraVendor) with the baseline extension advertisement (no shader
|
||||||
|
// subgroup, no timer queries). A live backend copies this in its constructor and
|
||||||
|
// reconciles the Extensions in UpdateAdvertisedExtensions once real capabilities
|
||||||
|
// exist; callers that need the advertised list for a known capability set must
|
||||||
|
// use BuildAdvertisedExtensions instead.
|
||||||
|
const RendererInfo& GetRendererIdentity();
|
||||||
|
|
||||||
|
// The full OpenGL extension list Magma advertises (glGetString(GL_EXTENSIONS)) for
|
||||||
|
// a device with the given raw capabilities. The MOBILEGL_DISABLE_SUBGROUP and
|
||||||
|
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
|
||||||
|
// the detected device support (passing an already-gated value is harmless).
|
||||||
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||||
|
Bool anisotropicFilteringSupported);
|
||||||
|
|
||||||
|
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
|
||||||
|
// string an initialized backend returns from GetBackendAPIVersionString (and that
|
||||||
|
// ends up inside the application-visible GL_RENDERER string).
|
||||||
|
String FormatBackendAPIVersionString(const String& deviceName, const String& vulkanApiVersionString,
|
||||||
|
const String& driverVersionString);
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,25 +8,44 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <MG_Backend/BackendObject.h>
|
||||||
#include "Renderer/VulkanRenderer.h"
|
#include "Renderer/VulkanRenderer.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
extern UniquePtr<VulkanRenderer> pVulkanRenderer;
|
extern UniquePtr<VulkanRenderer> pVulkanRenderer;
|
||||||
|
|
||||||
|
// Generation of the live VulkanRenderer instance, mirroring DirectGLES's
|
||||||
|
// g_syncContextGeneration. BackendObject_DirectVulkan bumps it wherever
|
||||||
|
// pVulkanRenderer is reset or recreated; fence and timer-query handles
|
||||||
|
// stamped with an older generation are stale and resolve as signaled /
|
||||||
|
// available with zero results instead of dereferencing the destroyed
|
||||||
|
// renderer's frame serials and query-pool slots.
|
||||||
|
Uint64 GetRendererGeneration();
|
||||||
|
void BumpRendererGeneration();
|
||||||
|
|
||||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
|
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
|
GLint drawbuffer, const GLfloat* value);
|
||||||
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||||
|
GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void Clear(GLbitfield mask);
|
void Clear(GLbitfield mask);
|
||||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
||||||
|
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount);
|
GLsizei drawcount);
|
||||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||||
GLsizei drawcount, const GLint* basevertex);
|
GLsizei drawcount, const GLint* basevertex);
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
|
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
|
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex);
|
const void* indices, GLint basevertex);
|
||||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
||||||
@@ -44,12 +63,72 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||||
|
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||||
|
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||||
|
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||||
|
GLbitfield mask, GLenum filter);
|
||||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height, GLint border);
|
GLsizei height, GLint border);
|
||||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height);
|
GLsizei height);
|
||||||
|
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||||
|
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||||
|
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||||
|
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||||
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||||
void GenerateMipmap(GLenum target);
|
void GenerateMipmap(GLenum target);
|
||||||
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
|
void MemoryBarrier(GLbitfield barriers);
|
||||||
|
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||||
|
void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access,
|
||||||
|
GLenum format);
|
||||||
|
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||||
|
void GetInteger64i_v(GLenum target, GLuint index, GLint64* data);
|
||||||
|
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
|
||||||
|
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
|
||||||
|
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
||||||
|
GLsizei* length, GLchar* name);
|
||||||
|
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||||
|
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||||
|
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||||
|
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget uploadTarget,
|
||||||
|
GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels);
|
||||||
|
// GL fence sync objects, mapped onto the renderer's frame-serial busy
|
||||||
|
// tracking: a fence captures the frame serial current at creation and is
|
||||||
|
// signaled once every command recorded under that serial has completed on
|
||||||
|
// the GPU.
|
||||||
|
BackendSyncHandle FenceSync();
|
||||||
|
GLenum ClientWaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||||
|
void WaitSync(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
||||||
|
void DeleteSync(BackendSyncHandle sync);
|
||||||
|
Bool GetSyncStatus(BackendSyncHandle sync);
|
||||||
|
// GPU timer queries (GL_TIME_ELAPSED spans and GL_TIMESTAMP one-shots),
|
||||||
|
// backed by per-frame VkQueryPool timestamp slots. All hooks degrade
|
||||||
|
// gracefully: null handles when the renderer is absent, the device lacks
|
||||||
|
// timestamp support, or the frame's pool is exhausted.
|
||||||
|
// Dynamic support check (GLFunctionsTable::IsTimerQuerySupported): true
|
||||||
|
// only while a live renderer exists whose device can actually time.
|
||||||
|
Bool IsTimerQuerySupported();
|
||||||
|
BackendQueryHandle BeginTimeElapsedQuery();
|
||||||
|
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||||
|
BackendQueryHandle QueryCounterTimestamp();
|
||||||
|
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||||
|
// Returns true when a final value was produced (outNanoseconds set; the
|
||||||
|
// frontend may cache it and release the handle), false when the result
|
||||||
|
// cannot be obtained yet (e.g. a wait refused because the records' frame
|
||||||
|
// serial is the current unsubmitted frame) - the handle then stays
|
||||||
|
// readable later.
|
||||||
|
Bool GetQueryResult64(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||||
|
void DeleteBackendQuery(BackendQueryHandle query);
|
||||||
|
// Always 0: Vulkan cannot synchronously sample the GPU clock (timestamps
|
||||||
|
// only exist as vkCmdWriteTimestamp results); the frontend falls back.
|
||||||
|
Int64 GetGpuTimestampNs();
|
||||||
void Present();
|
void Present();
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_State::GLState {
|
||||||
|
class ProgramObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name);
|
||||||
|
GLuint GetShaderStorageBlockBinding(const MG_State::GLState::ProgramObject& program, GLuint blockIndex);
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Destroy(device, commandPool);
|
Destroy(device, commandPool);
|
||||||
m_frames.assign(frameCount, {});
|
m_frames.assign(frameCount, {});
|
||||||
currentFrameIndex = 0;
|
currentFrameIndex = 0;
|
||||||
|
m_device = device;
|
||||||
|
m_commandPool = commandPool;
|
||||||
|
|
||||||
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
Vector<VkCommandBuffer> commandBuffers(frameCount, VK_NULL_HANDLE);
|
||||||
VkCommandBufferAllocateInfo allocInfo{};
|
VkCommandBufferAllocateInfo allocInfo{};
|
||||||
@@ -55,10 +57,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
DestroySwapchainSemaphores(device);
|
DestroySwapchainSemaphores(device);
|
||||||
if (device != VK_NULL_HANDLE && commandPool != VK_NULL_HANDLE && !m_frames.empty()) {
|
if (device != VK_NULL_HANDLE && commandPool != VK_NULL_HANDLE && !m_frames.empty()) {
|
||||||
|
for (auto& frame : m_frames) {
|
||||||
|
FreeRetiredCommandBuffers(frame);
|
||||||
|
}
|
||||||
vkFreeCommandBuffers(device, commandPool, frameCount, commandBuffers.data());
|
vkFreeCommandBuffers(device, commandPool, frameCount, commandBuffers.data());
|
||||||
}
|
}
|
||||||
m_frames.clear();
|
m_frames.clear();
|
||||||
currentFrameIndex = 0;
|
currentFrameIndex = 0;
|
||||||
|
m_device = VK_NULL_HANDLE;
|
||||||
|
m_commandPool = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameContext::FrameData& FrameContext::GetCurrent() {
|
FrameContext::FrameData& FrameContext::GetCurrent() {
|
||||||
@@ -97,6 +104,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VK_VERIFY(vkBeginCommandBuffer(frame.commandBuffer, &beginInfo), "BeginCommandRecording, vkBeginCommandBuffer");
|
VK_VERIFY(vkBeginCommandBuffer(frame.commandBuffer, &beginInfo), "BeginCommandRecording, vkBeginCommandBuffer");
|
||||||
|
|
||||||
frame.isCommandRecording = true;
|
frame.isCommandRecording = true;
|
||||||
|
if (m_recordingObserver != nullptr) {
|
||||||
|
m_recordingObserver->OnFrameCommandRecordingBegan(frame.commandBuffer);
|
||||||
|
}
|
||||||
return frame.commandBuffer;
|
return frame.commandBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,9 +188,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
packet.signalSemaphore = m_swapchainImageRenderFinishedSemaphores[swapchainImageIndex];
|
||||||
packet.commandBuffer = frame.commandBuffer;
|
packet.commandBuffer = frame.commandBuffer;
|
||||||
|
|
||||||
packet.submitInfo.waitSemaphoreCount = 1;
|
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
|
||||||
packet.submitInfo.pWaitSemaphores = &packet.waitSemaphore;
|
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
|
||||||
packet.submitInfo.pWaitDstStageMask = &packet.waitDstStageMask;
|
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
|
||||||
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
|
packet.submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U;
|
||||||
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
|
packet.submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &packet.commandBuffer : nullptr;
|
||||||
packet.submitInfo.signalSemaphoreCount = 1;
|
packet.submitInfo.signalSemaphoreCount = 1;
|
||||||
@@ -188,18 +198,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameContext::PresentInfoPacket FrameContext::GetPresentInfo(VkSwapchainKHR swapchain, const Uint32& imageIndex) const {
|
FrameContext::PresentInfoPacket FrameContext::GetPresentInfo(VkSwapchainKHR swapchain, Uint32 imageIndex) const {
|
||||||
AssertValidSwapchainImageIndex(imageIndex);
|
AssertValidSwapchainImageIndex(imageIndex);
|
||||||
PresentInfoPacket packet{};
|
PresentInfoPacket packet{};
|
||||||
packet.waitSemaphore = m_swapchainImageRenderFinishedSemaphores[imageIndex];
|
packet.waitSemaphore = m_swapchainImageRenderFinishedSemaphores[imageIndex];
|
||||||
packet.swapchain = swapchain;
|
packet.swapchain = swapchain;
|
||||||
packet.imageIndex = &imageIndex;
|
packet.imageIndex = imageIndex;
|
||||||
|
|
||||||
packet.presentInfo.waitSemaphoreCount = 1;
|
packet.presentInfo.waitSemaphoreCount = 1;
|
||||||
packet.presentInfo.pWaitSemaphores = &packet.waitSemaphore;
|
packet.presentInfo.pWaitSemaphores = &packet.waitSemaphore;
|
||||||
packet.presentInfo.swapchainCount = 1;
|
packet.presentInfo.swapchainCount = 1;
|
||||||
packet.presentInfo.pSwapchains = &packet.swapchain;
|
packet.presentInfo.pSwapchains = &packet.swapchain;
|
||||||
packet.presentInfo.pImageIndices = packet.imageIndex;
|
packet.presentInfo.pImageIndices = &packet.imageIndex;
|
||||||
packet.presentInfo.pResults = nullptr;
|
packet.presentInfo.pResults = nullptr;
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
@@ -211,14 +221,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
// The slot's fence has been waited: every command buffer this slot
|
||||||
|
// submitted (including mid-frame flushes) has finished executing.
|
||||||
|
FreeRetiredCommandBuffers(frame);
|
||||||
|
|
||||||
result = vkResetFences(device, 1, &frame.imageInFlightFence);
|
result = vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
||||||
|
&outImageIndex);
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
frame.imageAvailableSemaphoreConsumed = false;
|
||||||
&outImageIndex);
|
return vkResetFences(device, 1, &frame.imageInFlightFence);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
Uint32 FrameContext::GetCurrentFrameIndex() const {
|
||||||
@@ -229,6 +243,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return static_cast<Uint32>(m_frames.size());
|
return static_cast<Uint32>(m_frames.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FrameContext::SetRecordingObserver(IRecordingObserver* observer) {
|
||||||
|
m_recordingObserver = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkResult FrameContext::RetireCurrentCommandBuffer() {
|
||||||
|
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE,
|
||||||
|
"RetireCurrentCommandBuffer requires an initialized FrameContext");
|
||||||
|
auto& frame = GetCurrent();
|
||||||
|
MOBILEGL_ASSERT(!frame.isCommandRecording,
|
||||||
|
"RetireCurrentCommandBuffer called while the command buffer is still recording");
|
||||||
|
|
||||||
|
VkCommandBufferAllocateInfo allocInfo{};
|
||||||
|
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||||
|
allocInfo.commandPool = m_commandPool;
|
||||||
|
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
|
allocInfo.commandBufferCount = 1;
|
||||||
|
VkCommandBuffer replacement = VK_NULL_HANDLE;
|
||||||
|
const VkResult result = vkAllocateCommandBuffers(m_device, &allocInfo, &replacement);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
frame.retiredCommandBuffers.push_back(frame.commandBuffer);
|
||||||
|
frame.commandBuffer = replacement;
|
||||||
|
return VK_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameContext::FreeRetiredCommandBuffers(FrameData& frame) {
|
||||||
|
if (frame.retiredCommandBuffers.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_device != VK_NULL_HANDLE && m_commandPool != VK_NULL_HANDLE) {
|
||||||
|
vkFreeCommandBuffers(m_device, m_commandPool, static_cast<Uint32>(frame.retiredCommandBuffers.size()),
|
||||||
|
frame.retiredCommandBuffers.data());
|
||||||
|
}
|
||||||
|
frame.retiredCommandBuffers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void FrameContext::AssertValidFrameIndex(Uint32 frameIndex) const {
|
void FrameContext::AssertValidFrameIndex(Uint32 frameIndex) const {
|
||||||
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "FrameContext index out of range");
|
MOBILEGL_ASSERT(frameIndex < m_frames.size(), "FrameContext index out of range");
|
||||||
}
|
}
|
||||||
@@ -260,6 +311,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
frame.hasCommandBufferRecorded = false;
|
frame.hasCommandBufferRecorded = false;
|
||||||
frame.isCommandRecording = false;
|
frame.isCommandRecording = false;
|
||||||
|
frame.imageAvailableSemaphoreConsumed = false;
|
||||||
return VK_SUCCESS;
|
return VK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,5 +329,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frame.imageAvailableSemaphore = VK_NULL_HANDLE;
|
frame.imageAvailableSemaphore = VK_NULL_HANDLE;
|
||||||
frame.isCommandRecording = false;
|
frame.isCommandRecording = false;
|
||||||
frame.hasCommandBufferRecorded = false;
|
frame.hasCommandBufferRecorded = false;
|
||||||
|
frame.imageAvailableSemaphoreConsumed = false;
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -14,6 +14,17 @@
|
|||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
class FrameContext {
|
class FrameContext {
|
||||||
public:
|
public:
|
||||||
|
// Notified immediately after a frame command buffer begins recording
|
||||||
|
// (before any render pass has been begun); every BeginCommandRecording
|
||||||
|
// caller funnels through this single seam. Implemented by the renderer
|
||||||
|
// to prepare per-frame timer-query pools (vkCmdResetQueryPool must be
|
||||||
|
// recorded outside a render pass).
|
||||||
|
class IRecordingObserver {
|
||||||
|
public:
|
||||||
|
virtual ~IRecordingObserver() = default;
|
||||||
|
virtual void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct SubmitInfoPacket {
|
struct SubmitInfoPacket {
|
||||||
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||||
@@ -25,7 +36,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
struct PresentInfoPacket {
|
struct PresentInfoPacket {
|
||||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||||
const Uint32* imageIndex = nullptr;
|
Uint32 imageIndex = 0;
|
||||||
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,6 +46,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
||||||
Bool isCommandRecording = false;
|
Bool isCommandRecording = false;
|
||||||
Bool hasCommandBufferRecorded = false;
|
Bool hasCommandBufferRecorded = false;
|
||||||
|
Bool imageAvailableSemaphoreConsumed = false;
|
||||||
|
// Command buffers submitted mid-frame (FlushPendingCommands) whose
|
||||||
|
// execution is only known complete once this slot's fence has been
|
||||||
|
// waited again; freed at that point.
|
||||||
|
Vector<VkCommandBuffer> retiredCommandBuffers;
|
||||||
|
// Submit-tracker index of this slot's most recent queue submission
|
||||||
|
// (written by the renderer at submit time).
|
||||||
|
Uint64 lastSubmitIndex = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
VkResult Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount);
|
VkResult Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount);
|
||||||
@@ -53,13 +72,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
||||||
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||||
SubmitInfoPacket GetSubmitInfo(Bool shouldSubmitCommandBuffer, Uint32 swapchainImageIndex) const;
|
SubmitInfoPacket GetSubmitInfo(Bool shouldSubmitCommandBuffer, Uint32 swapchainImageIndex) const;
|
||||||
PresentInfoPacket GetPresentInfo(VkSwapchainKHR swapchain, const Uint32& imageIndex) const;
|
PresentInfoPacket GetPresentInfo(VkSwapchainKHR swapchain, Uint32 imageIndex) const;
|
||||||
VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
||||||
Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE);
|
Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE);
|
||||||
|
|
||||||
|
// Parks the current (already ended and submitted) command buffer on the
|
||||||
|
// slot's retired list and installs a freshly allocated one, so recording
|
||||||
|
// can restart while the submitted buffer is still executing. Retired
|
||||||
|
// buffers are freed after the slot's fence is next waited.
|
||||||
|
VkResult RetireCurrentCommandBuffer();
|
||||||
|
|
||||||
Uint32 GetCurrentFrameIndex() const;
|
Uint32 GetCurrentFrameIndex() const;
|
||||||
Uint32 GetFrameCount() const;
|
Uint32 GetFrameCount() const;
|
||||||
|
|
||||||
|
// Observer may be null (no notifications). Not owned.
|
||||||
|
void SetRecordingObserver(IRecordingObserver* observer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AssertValidFrameIndex(Uint32 frameIndex) const;
|
void AssertValidFrameIndex(Uint32 frameIndex) const;
|
||||||
void AssertValidSwapchainImageIndex(Uint32 imageIndex) const;
|
void AssertValidSwapchainImageIndex(Uint32 imageIndex) const;
|
||||||
@@ -68,9 +96,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const VkSemaphoreCreateInfo& semaphoreInfo,
|
const VkSemaphoreCreateInfo& semaphoreInfo,
|
||||||
const VkFenceCreateInfo& fenceInfo);
|
const VkFenceCreateInfo& fenceInfo);
|
||||||
void DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex);
|
void DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex);
|
||||||
|
void FreeRetiredCommandBuffers(FrameData& frame);
|
||||||
|
|
||||||
Vector<FrameData> m_frames;
|
Vector<FrameData> m_frames;
|
||||||
Vector<VkSemaphore> m_swapchainImageRenderFinishedSemaphores;
|
Vector<VkSemaphore> m_swapchainImageRenderFinishedSemaphores;
|
||||||
Uint32 currentFrameIndex = 0;
|
Uint32 currentFrameIndex = 0;
|
||||||
|
IRecordingObserver* m_recordingObserver = nullptr;
|
||||||
|
// Stored at Initialize for retired-command-buffer management.
|
||||||
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -9,8 +9,111 @@
|
|||||||
#include "PipelineFactory.h"
|
#include "PipelineFactory.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
static const char* PrimitiveTopologyToString(VkPrimitiveTopology topology) {
|
||||||
|
switch (topology) {
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_LIST)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY)
|
||||||
|
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
|
||||||
|
default:
|
||||||
|
return "VK_PRIMITIVE_TOPOLOGY_UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* SampleCountToString(VkSampleCountFlagBits sampleCount) {
|
||||||
|
switch (sampleCount) {
|
||||||
|
ENUM_STR_CASE(VK_SAMPLE_COUNT_1_BIT)
|
||||||
|
ENUM_STR_CASE(VK_SAMPLE_COUNT_2_BIT)
|
||||||
|
ENUM_STR_CASE(VK_SAMPLE_COUNT_4_BIT)
|
||||||
|
ENUM_STR_CASE(VK_SAMPLE_COUNT_8_BIT)
|
||||||
|
ENUM_STR_CASE(VK_SAMPLE_COUNT_16_BIT)
|
||||||
|
ENUM_STR_CASE(VK_SAMPLE_COUNT_32_BIT)
|
||||||
|
ENUM_STR_CASE(VK_SAMPLE_COUNT_64_BIT)
|
||||||
|
default:
|
||||||
|
return "VK_SAMPLE_COUNT_UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* CullModeToString(VkCullModeFlags cullMode) {
|
||||||
|
switch (cullMode) {
|
||||||
|
case VK_CULL_MODE_NONE:
|
||||||
|
return "VK_CULL_MODE_NONE";
|
||||||
|
case VK_CULL_MODE_FRONT_BIT:
|
||||||
|
return "VK_CULL_MODE_FRONT_BIT";
|
||||||
|
case VK_CULL_MODE_BACK_BIT:
|
||||||
|
return "VK_CULL_MODE_BACK_BIT";
|
||||||
|
case VK_CULL_MODE_FRONT_AND_BACK:
|
||||||
|
return "VK_CULL_MODE_FRONT_AND_BACK";
|
||||||
|
default:
|
||||||
|
return "VK_CULL_MODE_UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* CompareOpToString(VkCompareOp compareOp) {
|
||||||
|
switch (compareOp) {
|
||||||
|
ENUM_STR_CASE(VK_COMPARE_OP_NEVER)
|
||||||
|
ENUM_STR_CASE(VK_COMPARE_OP_LESS)
|
||||||
|
ENUM_STR_CASE(VK_COMPARE_OP_EQUAL)
|
||||||
|
ENUM_STR_CASE(VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||||
|
ENUM_STR_CASE(VK_COMPARE_OP_GREATER)
|
||||||
|
ENUM_STR_CASE(VK_COMPARE_OP_NOT_EQUAL)
|
||||||
|
ENUM_STR_CASE(VK_COMPARE_OP_GREATER_OR_EQUAL)
|
||||||
|
ENUM_STR_CASE(VK_COMPARE_OP_ALWAYS)
|
||||||
|
default:
|
||||||
|
return "VK_COMPARE_OP_UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* LogicOpToString(VkLogicOp logicOp) {
|
||||||
|
switch (logicOp) {
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_CLEAR)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_AND)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_AND_REVERSE)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_COPY)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_AND_INVERTED)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_NO_OP)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_XOR)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_OR)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_NOR)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_EQUIVALENT)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_INVERT)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_OR_REVERSE)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_COPY_INVERTED)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_OR_INVERTED)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_NAND)
|
||||||
|
ENUM_STR_CASE(VK_LOGIC_OP_SET)
|
||||||
|
default:
|
||||||
|
return "VK_LOGIC_OP_UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PipelineFactory::PipelineFactory(VkDevice device, const VulkanRendererConfig& config):
|
||||||
|
m_device(device), m_config(config) {
|
||||||
|
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "PipelineFactory: device is null");
|
||||||
|
|
||||||
|
if (m_config.DisablePipelineCache) {
|
||||||
|
MGLOG_I("DirectVulkan: pipeline cache disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPipelineCacheCreateInfo pipelineCacheInfo{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO};
|
||||||
|
VK_VERIFY(vkCreatePipelineCache(m_device, &pipelineCacheInfo, nullptr, &m_pipelineCache),
|
||||||
|
"vkCreatePipelineCache");
|
||||||
|
}
|
||||||
|
|
||||||
PipelineFactory::~PipelineFactory() {
|
PipelineFactory::~PipelineFactory() {
|
||||||
DestroyAll();
|
DestroyAll();
|
||||||
|
if (m_pipelineCache != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyPipelineCache(m_device, m_pipelineCache, nullptr);
|
||||||
|
m_pipelineCache = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const {
|
PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const {
|
||||||
@@ -19,19 +122,42 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.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.depthTestEnable, sizeof(payload.depthTestEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.rasterizerDiscardEnable, sizeof(payload.rasterizerDiscardEnable)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOpEnable, sizeof(payload.logicOpEnable)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.stencilTestEnable, sizeof(payload.stencilTestEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthCompareOp, sizeof(payload.depthCompareOp)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthCompareOp, sizeof(payload.depthCompareOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.blendEnable, sizeof(payload.blendEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOp, sizeof(payload.logicOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.srcColorBlendFactor, sizeof(payload.srcColorBlendFactor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.dstColorBlendFactor, sizeof(payload.dstColorBlendFactor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.srcAlphaBlendFactor, sizeof(payload.srcAlphaBlendFactor)));
|
XXHASH_VERIFY(
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.dstAlphaBlendFactor, sizeof(payload.dstAlphaBlendFactor)));
|
XXH64_update(m_hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorWriteMask, sizeof(payload.colorWriteMask)));
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilFailOp, sizeof(payload.backStencilFailOp)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilPassOp, sizeof(payload.backStencilPassOp)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
|
||||||
|
XXHASH_VERIFY(
|
||||||
|
XXH64_update(m_hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
|
||||||
|
if (payload.colorAttachmentCount > 0) {
|
||||||
|
XXHASH_VERIFY(XXH64_update(
|
||||||
|
m_hashState,
|
||||||
|
payload.colorBlendAttachments.data(),
|
||||||
|
sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount));
|
||||||
|
}
|
||||||
return XXH64_digest(m_hashState);
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,10 +187,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null");
|
||||||
MOBILEGL_ASSERT(payload.pipelineLayout != VK_NULL_HANDLE, "PipelineFactory: pipelineLayout is null");
|
MOBILEGL_ASSERT(payload.pipelineLayout != VK_NULL_HANDLE, "PipelineFactory: pipelineLayout is null");
|
||||||
MOBILEGL_ASSERT(payload.renderPass != VK_NULL_HANDLE, "PipelineFactory: renderPass is null");
|
MOBILEGL_ASSERT(payload.renderPass != VK_NULL_HANDLE, "PipelineFactory: renderPass is null");
|
||||||
|
MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineCreatePayload::kMaxColorAttachments,
|
||||||
|
"PipelineFactory: colorAttachmentCount=%u is unexpectedly large",
|
||||||
|
payload.colorAttachmentCount);
|
||||||
|
MGLOG_D("PipelineFactory::CreatePipeline: programHash=0x%llx vertexInputHash=0x%llx colorAttachmentCount=%u subpass=%u",
|
||||||
|
static_cast<unsigned long long>(payload.programHash),
|
||||||
|
static_cast<unsigned long long>(payload.vertexInputHash),
|
||||||
|
payload.colorAttachmentCount,
|
||||||
|
payload.subpass);
|
||||||
|
|
||||||
static constexpr VkDynamicState kDynamicStates[] = {
|
static constexpr VkDynamicState kDynamicStates[] = {
|
||||||
VK_DYNAMIC_STATE_VIEWPORT,
|
VK_DYNAMIC_STATE_VIEWPORT,
|
||||||
VK_DYNAMIC_STATE_SCISSOR
|
VK_DYNAMIC_STATE_SCISSOR,
|
||||||
|
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||||
|
VK_DYNAMIC_STATE_DEPTH_BIAS,
|
||||||
|
VK_DYNAMIC_STATE_LINE_WIDTH,
|
||||||
|
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||||
|
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
|
||||||
|
VK_DYNAMIC_STATE_STENCIL_REFERENCE
|
||||||
};
|
};
|
||||||
|
|
||||||
VkPipelineDynamicStateCreateInfo dynamicState{};
|
VkPipelineDynamicStateCreateInfo dynamicState{};
|
||||||
@@ -74,39 +214,55 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||||
ia.topology = payload.topology;
|
ia.topology = payload.topology;
|
||||||
|
ia.primitiveRestartEnable = payload.primitiveRestartEnable ? VK_TRUE : VK_FALSE;
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
||||||
vpci.viewportCount = 1;
|
vpci.viewportCount = 1;
|
||||||
vpci.scissorCount = 1;
|
vpci.scissorCount = 1;
|
||||||
|
|
||||||
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
||||||
raster.polygonMode = VK_POLYGON_MODE_FILL;
|
raster.polygonMode = payload.polygonMode;
|
||||||
raster.cullMode = 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;
|
||||||
|
|
||||||
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||||
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
ms.rasterizationSamples = payload.rasterizationSamples;
|
||||||
|
|
||||||
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||||
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
||||||
depthStencil.depthWriteEnable = payload.depthWriteEnable ? VK_TRUE : VK_FALSE;
|
depthStencil.depthWriteEnable = payload.depthWriteEnable ? VK_TRUE : VK_FALSE;
|
||||||
depthStencil.depthCompareOp = payload.depthCompareOp;
|
depthStencil.depthCompareOp = payload.depthCompareOp;
|
||||||
depthStencil.depthBoundsTestEnable = VK_FALSE;
|
depthStencil.depthBoundsTestEnable = VK_FALSE;
|
||||||
depthStencil.stencilTestEnable = VK_FALSE;
|
depthStencil.stencilTestEnable = payload.stencilTestEnable ? VK_TRUE : VK_FALSE;
|
||||||
|
if (payload.stencilTestEnable) {
|
||||||
|
depthStencil.front.failOp = payload.frontStencilFailOp;
|
||||||
|
depthStencil.front.passOp = payload.frontStencilPassOp;
|
||||||
|
depthStencil.front.depthFailOp = payload.frontStencilDepthFailOp;
|
||||||
|
depthStencil.front.compareOp = payload.frontStencilCompareOp;
|
||||||
|
depthStencil.front.compareMask = 0xffffffffu;
|
||||||
|
depthStencil.front.writeMask = 0xffffffffu;
|
||||||
|
depthStencil.front.reference = 0;
|
||||||
|
depthStencil.back.failOp = payload.backStencilFailOp;
|
||||||
|
depthStencil.back.passOp = payload.backStencilPassOp;
|
||||||
|
depthStencil.back.depthFailOp = payload.backStencilDepthFailOp;
|
||||||
|
depthStencil.back.compareOp = payload.backStencilCompareOp;
|
||||||
|
depthStencil.back.compareMask = 0xffffffffu;
|
||||||
|
depthStencil.back.writeMask = 0xffffffffu;
|
||||||
|
depthStencil.back.reference = 0;
|
||||||
|
}
|
||||||
|
|
||||||
VkPipelineColorBlendAttachmentState colorAttach{};
|
Vector<VkPipelineColorBlendAttachmentState> colorAttachments(payload.colorAttachmentCount);
|
||||||
colorAttach.colorWriteMask = payload.colorWriteMask;
|
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
||||||
colorAttach.blendEnable = payload.blendEnable ? VK_TRUE : VK_FALSE;
|
colorAttachments[i] = payload.colorBlendAttachments[i];
|
||||||
colorAttach.srcColorBlendFactor = payload.srcColorBlendFactor;
|
}
|
||||||
colorAttach.dstColorBlendFactor = payload.dstColorBlendFactor;
|
|
||||||
colorAttach.colorBlendOp = VK_BLEND_OP_ADD;
|
|
||||||
colorAttach.srcAlphaBlendFactor = payload.srcAlphaBlendFactor;
|
|
||||||
colorAttach.dstAlphaBlendFactor = payload.dstAlphaBlendFactor;
|
|
||||||
colorAttach.alphaBlendOp = VK_BLEND_OP_ADD;
|
|
||||||
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
||||||
blend.attachmentCount = 1;
|
blend.logicOpEnable = payload.logicOpEnable ? VK_TRUE : VK_FALSE;
|
||||||
blend.pAttachments = &colorAttach;
|
blend.logicOp = payload.logicOp;
|
||||||
|
blend.attachmentCount = payload.colorAttachmentCount;
|
||||||
|
blend.pAttachments = colorAttachments.empty() ? nullptr : colorAttachments.data();
|
||||||
|
|
||||||
VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||||
gpi.stageCount = static_cast<Uint32>(payload.stages->size());
|
gpi.stageCount = static_cast<Uint32>(payload.stages->size());
|
||||||
@@ -124,8 +280,52 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
gpi.subpass = payload.subpass;
|
gpi.subpass = payload.subpass;
|
||||||
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &gpi, nullptr, &pipeline),
|
const VkResult result = vkCreateGraphicsPipelines(m_device, m_pipelineCache, 1, &gpi, nullptr, &pipeline);
|
||||||
"vkCreateGraphicsPipelines");
|
if (result != VK_SUCCESS) {
|
||||||
|
MGLOG_F("PipelineFactory::CreatePipeline failed: result=%s (%d) programHash=0x%llx vertexInputHash=0x%llx stageCount=%u topology=%s(%d) colorAttachmentCount=%u samples=%s(%d) subpass=%u",
|
||||||
|
VkResultToString(result),
|
||||||
|
result,
|
||||||
|
static_cast<unsigned long long>(payload.programHash),
|
||||||
|
static_cast<unsigned long long>(payload.vertexInputHash),
|
||||||
|
gpi.stageCount,
|
||||||
|
PrimitiveTopologyToString(payload.topology),
|
||||||
|
payload.topology,
|
||||||
|
payload.colorAttachmentCount,
|
||||||
|
SampleCountToString(payload.rasterizationSamples),
|
||||||
|
payload.rasterizationSamples,
|
||||||
|
payload.subpass);
|
||||||
|
MGLOG_F("PipelineFactory::CreatePipeline state: cullMode=%s(0x%x) frontFace=%d depthTest=%d depthWrite=%d depthCompare=%s(%d) depthBias=%d rasterizerDiscard=%d stencilTest=%d logicOpEnable=%d logicOp=%s(%d)",
|
||||||
|
CullModeToString(payload.cullMode),
|
||||||
|
static_cast<Uint32>(payload.cullMode),
|
||||||
|
payload.frontFace,
|
||||||
|
payload.depthTestEnable ? 1 : 0,
|
||||||
|
payload.depthWriteEnable ? 1 : 0,
|
||||||
|
CompareOpToString(payload.depthCompareOp),
|
||||||
|
payload.depthCompareOp,
|
||||||
|
payload.depthBiasEnable ? 1 : 0,
|
||||||
|
payload.rasterizerDiscardEnable ? 1 : 0,
|
||||||
|
payload.stencilTestEnable ? 1 : 0,
|
||||||
|
payload.logicOpEnable ? 1 : 0,
|
||||||
|
LogicOpToString(payload.logicOp),
|
||||||
|
payload.logicOp);
|
||||||
|
MGLOG_F("PipelineFactory::CreatePipeline vertex input: bindingCount=%u attributeCount=%u",
|
||||||
|
payload.vertexInputState->vertexBindingDescriptionCount,
|
||||||
|
payload.vertexInputState->vertexAttributeDescriptionCount);
|
||||||
|
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
||||||
|
const auto& attachment = payload.colorBlendAttachments[i];
|
||||||
|
MGLOG_F("PipelineFactory::CreatePipeline colorAttachment[%u]: blend=%d colorWriteMask=0x%x srcColor=%d dstColor=%d colorOp=%d srcAlpha=%d dstAlpha=%d alphaOp=%d",
|
||||||
|
i,
|
||||||
|
attachment.blendEnable == VK_TRUE ? 1 : 0,
|
||||||
|
static_cast<Uint32>(attachment.colorWriteMask),
|
||||||
|
attachment.srcColorBlendFactor,
|
||||||
|
attachment.dstColorBlendFactor,
|
||||||
|
attachment.colorBlendOp,
|
||||||
|
attachment.srcAlphaBlendFactor,
|
||||||
|
attachment.dstAlphaBlendFactor,
|
||||||
|
attachment.alphaBlendOp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VK_VERIFY(result, "vkCreateGraphicsPipelines");
|
||||||
return pipeline;
|
return pipeline;
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "../VkIncludes.h"
|
#include "../VkIncludes.h"
|
||||||
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
@@ -18,31 +19,42 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|
||||||
struct PipelineCreatePayload {
|
struct PipelineCreatePayload {
|
||||||
|
static constexpr Uint32 kMaxColorAttachments = MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS;
|
||||||
|
|
||||||
HashType programHash = 0;
|
HashType programHash = 0;
|
||||||
HashType vertexInputHash = 0;
|
HashType vertexInputHash = 0;
|
||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||||
|
Uint32 colorAttachmentCount = 1;
|
||||||
|
VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
Uint32 subpass = 0;
|
Uint32 subpass = 0;
|
||||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
|
Bool primitiveRestartEnable = false;
|
||||||
|
VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
|
||||||
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
|
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
|
||||||
VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE;
|
VkFrontFace frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||||
Bool depthTestEnable = false;
|
Bool depthTestEnable = false;
|
||||||
Bool depthWriteEnable = false;
|
Bool depthWriteEnable = false;
|
||||||
|
Bool depthBiasEnable = false;
|
||||||
|
Bool rasterizerDiscardEnable = false;
|
||||||
|
Bool logicOpEnable = false;
|
||||||
|
Bool stencilTestEnable = false;
|
||||||
VkCompareOp depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
VkCompareOp depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||||
Bool blendEnable = false;
|
VkLogicOp logicOp = VK_LOGIC_OP_COPY;
|
||||||
VkBlendFactor srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
VkStencilOp frontStencilFailOp = VK_STENCIL_OP_KEEP;
|
||||||
VkBlendFactor dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
|
VkStencilOp frontStencilPassOp = VK_STENCIL_OP_KEEP;
|
||||||
VkBlendFactor srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
VkStencilOp frontStencilDepthFailOp = VK_STENCIL_OP_KEEP;
|
||||||
VkBlendFactor dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
VkCompareOp frontStencilCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||||
VkColorComponentFlags colorWriteMask =
|
VkStencilOp backStencilFailOp = VK_STENCIL_OP_KEEP;
|
||||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
VkStencilOp backStencilPassOp = VK_STENCIL_OP_KEEP;
|
||||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
VkStencilOp backStencilDepthFailOp = VK_STENCIL_OP_KEEP;
|
||||||
|
VkCompareOp backStencilCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||||
|
Array<VkPipelineColorBlendAttachmentState, kMaxColorAttachments> colorBlendAttachments{};
|
||||||
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
||||||
const VkPipelineVertexInputStateCreateInfo* vertexInputState = nullptr;
|
const VkPipelineVertexInputStateCreateInfo* vertexInputState = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit PipelineFactory(VkDevice device, const VulkanRendererConfig& config):
|
explicit PipelineFactory(VkDevice device, const VulkanRendererConfig& config);
|
||||||
m_device(device), m_config(config) {}
|
|
||||||
~PipelineFactory();
|
~PipelineFactory();
|
||||||
PipelineFactory(const PipelineFactory&) = delete;
|
PipelineFactory(const PipelineFactory&) = delete;
|
||||||
|
|
||||||
@@ -55,6 +67,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
const VulkanRendererConfig& m_config;
|
const VulkanRendererConfig& m_config;
|
||||||
|
VkPipelineCache m_pipelineCache = VK_NULL_HANDLE;
|
||||||
UnorderedMap<HashType, VkPipeline> m_cache;
|
UnorderedMap<HashType, VkPipeline> m_cache;
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,14 +14,25 @@
|
|||||||
#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 {
|
||||||
None = 0,
|
None = 0,
|
||||||
UniformBufferDynamic,
|
UniformBufferDynamic,
|
||||||
CombinedImageSampler
|
CombinedImageSampler,
|
||||||
|
UniformTexelBuffer,
|
||||||
|
StorageBuffer,
|
||||||
|
StorageImage
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class CompileOptionBit : Uint {
|
enum class CompileOptionBit : Uint {
|
||||||
@@ -36,6 +47,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|
||||||
struct VkProgramObject {
|
struct VkProgramObject {
|
||||||
|
static constexpr Uint32 kMaxVertexInputLocations = 32;
|
||||||
|
|
||||||
HashType hash = 0;
|
HashType hash = 0;
|
||||||
Vector<VkPipelineShaderStageCreateInfo> stages;
|
Vector<VkPipelineShaderStageCreateInfo> stages;
|
||||||
Vector<VkShaderModule> modules;
|
Vector<VkShaderModule> modules;
|
||||||
@@ -45,9 +58,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
Vector<DescriptorBindingKind> bindingKinds;
|
Vector<DescriptorBindingKind> bindingKinds;
|
||||||
Vector<Uint32> dynamicBindings;
|
Vector<Uint32> dynamicBindings;
|
||||||
|
Vector<Int> uniformBlockIndexByBinding;
|
||||||
|
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<Int> storageBlockIndexByBinding;
|
||||||
Int globalUboBinding = -1;
|
Int globalUboBinding = -1;
|
||||||
|
Uint32 activeVertexInputLocationMask = 0;
|
||||||
|
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
|
||||||
|
Uint32 activeFragmentOutputLocationMask = 0;
|
||||||
|
Array<GLenum, kMaxVertexInputLocations> fragmentOutputTypes{};
|
||||||
|
ShaderStage rasterizationProducerStage = ShaderStage::Unknown;
|
||||||
|
Uint32 producerOutputComponentCount = 0;
|
||||||
|
Uint32 fragmentInputComponentCount = 0;
|
||||||
|
|
||||||
static inline VkDevice s_device = VK_NULL_HANDLE;
|
static inline VkDevice s_device = VK_NULL_HANDLE;
|
||||||
|
|
||||||
@@ -62,13 +89,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
pipelineLayout = other.pipelineLayout;
|
pipelineLayout = other.pipelineLayout;
|
||||||
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);
|
||||||
|
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);
|
||||||
|
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||||
globalUboBinding = other.globalUboBinding;
|
globalUboBinding = other.globalUboBinding;
|
||||||
|
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||||
|
vertexInputTypes = other.vertexInputTypes;
|
||||||
|
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
|
||||||
|
fragmentOutputTypes = other.fragmentOutputTypes;
|
||||||
|
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||||
|
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||||
|
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
other.globalUboBinding = -1;
|
other.globalUboBinding = -1;
|
||||||
|
other.activeVertexInputLocationMask = 0;
|
||||||
|
other.activeFragmentOutputLocationMask = 0;
|
||||||
|
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||||
|
other.producerOutputComponentCount = 0;
|
||||||
|
other.fragmentInputComponentCount = 0;
|
||||||
}
|
}
|
||||||
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
||||||
if (this == &other) {
|
if (this == &other) {
|
||||||
@@ -82,13 +129,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
pipelineLayout = other.pipelineLayout;
|
pipelineLayout = other.pipelineLayout;
|
||||||
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);
|
||||||
|
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);
|
||||||
|
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
|
||||||
globalUboBinding = other.globalUboBinding;
|
globalUboBinding = other.globalUboBinding;
|
||||||
|
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||||
|
vertexInputTypes = other.vertexInputTypes;
|
||||||
|
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
|
||||||
|
fragmentOutputTypes = other.fragmentOutputTypes;
|
||||||
|
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||||
|
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||||
|
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
other.globalUboBinding = -1;
|
other.globalUboBinding = -1;
|
||||||
|
other.activeVertexInputLocationMask = 0;
|
||||||
|
other.activeFragmentOutputLocationMask = 0;
|
||||||
|
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||||
|
other.producerOutputComponentCount = 0;
|
||||||
|
other.fragmentInputComponentCount = 0;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,8 +185,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16)
|
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16,
|
||||||
: m_device(device), m_config(config), m_maxBindings(maxBindings) {
|
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;
|
||||||
@@ -130,15 +201,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
|
||||||
|
|
||||||
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
|
||||||
|
static VkFormat ConvertSpirvImageFormatToVkFormat(SpvImageFormat format);
|
||||||
|
static SamplerNumericDomain UniformTypeToSamplerNumericDomain(GLenum glType);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct ProgramLookupCache {
|
||||||
|
const MG_State::GLState::ProgramObject* program = nullptr;
|
||||||
|
Uint32 backendStateVersion = 0;
|
||||||
|
CompileOptionFlags flags{};
|
||||||
|
HashType hash = 0;
|
||||||
|
};
|
||||||
|
|
||||||
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
|
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
|
||||||
void ReflectLayout(const MG_State::GLState::ProgramObject& program, VkProgramObject& entry) const;
|
void ReflectVertexInputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
|
||||||
|
const Vector<Vector<Uint>>& spirv,
|
||||||
|
VkProgramObject& entry) const;
|
||||||
|
void ReflectFragmentOutputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
|
||||||
|
const Vector<Vector<Uint>>& spirv,
|
||||||
|
VkProgramObject& entry) const;
|
||||||
|
void ReflectLayout(const MG_State::GLState::ProgramObject& program, const Vector<Vector<Uint>>& spirv,
|
||||||
|
VkProgramObject& entry) const;
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
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;
|
||||||
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);
|
||||||
@@ -234,11 +266,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 +295,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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +352,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
imageInfo.format = m_depthStencilFormat;
|
imageInfo.format = m_depthStencilFormat;
|
||||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
VK_VERIFY(vkCreateImage(device, &imageInfo, nullptr, &m_depthStencilImages[i]), "vkCreateImage(depth)");
|
VK_VERIFY(vkCreateImage(device, &imageInfo, nullptr, &m_depthStencilImages[i]), "vkCreateImage(depth)");
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector<VkPresentModeKHR>& availablePresentModes);
|
static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector<VkPresentModeKHR>& availablePresentModes);
|
||||||
|
|
||||||
void Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, Uint32 graphicsQueueFamily,
|
void Create(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, Uint32 graphicsQueueFamily,
|
||||||
Uint32 presentQueueFamily, Uint32 minImageCountHint);
|
Uint32 presentQueueFamily, Uint32 minImageCountHint, VkExtent2D desiredExtent);
|
||||||
void Shutdown(VkDevice device);
|
void Shutdown(VkDevice device);
|
||||||
|
|
||||||
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
VkSwapchainKHR GetHandle() const { return m_swapchain; }
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 binding = 0;
|
Uint32 binding = 0;
|
||||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||||
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
||||||
|
VkImageView imageView = VK_NULL_HANDLE;
|
||||||
};
|
};
|
||||||
|
|
||||||
Bool Initialize(VkDevice device, VkBufferManager* bufferManager,
|
Bool Initialize(VkDevice device, VkBufferManager* bufferManager,
|
||||||
@@ -41,12 +42,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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,
|
||||||
Uint32 frameIndex,
|
Uint32 frameIndex,
|
||||||
|
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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct DescriptorPoolBucket {
|
struct DescriptorPoolBucket {
|
||||||
VkDescriptorPool handle = VK_NULL_HANDLE;
|
VkDescriptorPool handle = VK_NULL_HANDLE;
|
||||||
@@ -54,27 +65,65 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 allocatedSets = 0;
|
Uint32 allocatedSets = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DescriptorSetCacheEntry {
|
||||||
|
Vector<VkDescriptorSet> sets;
|
||||||
|
Uint32 cursor = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct FrameResources {
|
struct FrameResources {
|
||||||
Vector<DescriptorPoolBucket> descriptorPools;
|
Vector<DescriptorPoolBucket> descriptorPools;
|
||||||
|
UnorderedMap<VkDescriptorSetLayout, DescriptorSetCacheEntry> descriptorSetCacheByLayout;
|
||||||
|
Vector<VkBufferView> texelBufferViews;
|
||||||
Uint32 activeDescriptorPoolIndex = 0;
|
Uint32 activeDescriptorPoolIndex = 0;
|
||||||
Uint32 allocatedSetsThisFrame = 0;
|
Uint32 allocatedSetsThisFrame = 0;
|
||||||
Uint32 peakAllocatedSetsThisFrame = 0;
|
Uint32 peakAllocatedSetsThisFrame = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
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) const;
|
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||||
|
// Raw-pointer variant for the per-draw sampled-texture walk (CollectSampledTextures):
|
||||||
|
// the bound texture stays alive through the draw via GL binding state, so callers that
|
||||||
|
// only need the pointer skip the SharedPtr copy's atomic refcount churn.
|
||||||
|
static MG_State::GLState::ITextureObject* ResolveSamplerTextureRaw(
|
||||||
|
const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding);
|
||||||
|
SharedPtr<MG_State::GLState::ITextureObject> GetFallbackTexture(TextureTarget target) const;
|
||||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
VkDescriptorImageInfo& outImageInfo) const;
|
VkDescriptorImageInfo& outImageInfo) const;
|
||||||
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
||||||
VkDescriptorImageInfo& outImageInfo) const;
|
VkDescriptorImageInfo& outImageInfo) const;
|
||||||
Bool GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector<const void*>& outData,
|
Bool ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||||
Vector<VkDeviceSize>& outSizes) const;
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
|
Uint32 frameIndex, VkBufferView& outBufferView);
|
||||||
|
Bool ResolveStorageBufferDescriptor(const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
|
VkDescriptorBufferInfo& outBufferInfo) const;
|
||||||
|
Bool ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer,
|
||||||
|
const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
|
VkDescriptorImageInfo& outImageInfo) const;
|
||||||
|
// Result of resolving a UBO binding: either a zero-copy direct bind to the app's resident
|
||||||
|
// VkBuffer (the GLES backend's approach - no per-draw copy) or the CPU payload to upload.
|
||||||
|
struct UboBindResult {
|
||||||
|
Bool directBindable = false;
|
||||||
|
VkBuffer buffer = VK_NULL_HANDLE;
|
||||||
|
VkDeviceSize range = 0; // reflected block size; constant across draws (hashed)
|
||||||
|
VkDeviceSize dynamicOffset = 0; // block range start; moves per draw (NOT hashed)
|
||||||
|
const void* payload = nullptr; // fallback UploadTransient path
|
||||||
|
VkDeviceSize payloadSize = 0;
|
||||||
|
};
|
||||||
|
Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||||
|
UboBindResult& out) const;
|
||||||
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
||||||
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
||||||
VkResult AllocateDescriptorSetsFromActivePool(
|
VkResult AllocateDescriptorSetsFromActivePool(
|
||||||
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
|
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
|
||||||
|
VkResult AcquireDescriptorSet(Uint32 frameIndex,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
|
VkDescriptorSet& outDescriptorSet);
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
VkBufferManager* m_bufferManager = nullptr;
|
VkBufferManager* m_bufferManager = nullptr;
|
||||||
@@ -88,6 +137,46 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 m_peakDescriptorSetsObserved = 0;
|
Uint32 m_peakDescriptorSetsObserved = 0;
|
||||||
VkTextureManager* m_textureManager = nullptr;
|
VkTextureManager* m_textureManager = nullptr;
|
||||||
VkSamplerManager* m_samplerManager = nullptr;
|
VkSamplerManager* m_samplerManager = nullptr;
|
||||||
|
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2D;
|
||||||
|
|
||||||
|
// Per-draw scratch buffers for BindProgramUniformBuffers: reused (clear keeps
|
||||||
|
// capacity) so the descriptor-write path stops allocating on every draw.
|
||||||
|
Vector<VkWriteDescriptorSet> m_writesScratch;
|
||||||
|
Vector<VkDescriptorBufferInfo> m_bufferInfosScratch;
|
||||||
|
Vector<VkDescriptorImageInfo> m_imageInfosScratch;
|
||||||
|
Vector<VkBufferView> m_texelBufferViewsScratch;
|
||||||
|
Vector<Uint32> m_dynamicOffsetsScratch;
|
||||||
|
|
||||||
|
// Descriptor-set reuse across consecutive draws (see BindProgramUniformBuffers).
|
||||||
|
// When a draw's resolved descriptor content is byte-identical to the previous
|
||||||
|
// draw's, reuse the same VkDescriptorSet and skip AcquireDescriptorSet +
|
||||||
|
// vkUpdateDescriptorSets - only the bind-time dynamic offsets differ. Reset each
|
||||||
|
// frame in BeginFrame because the frame's descriptor sets are recycled there.
|
||||||
|
VkDescriptorSet m_lastBoundDescriptorSet = VK_NULL_HANDLE;
|
||||||
|
Uint64 m_lastDescriptorSignature = 0;
|
||||||
|
Bool m_hasLastDescriptor = false;
|
||||||
|
|
||||||
|
// Per-binding fast path over VkSamplerManager's content-hashed sampler cache, which
|
||||||
|
// stays the source of truth: its key hashes all sampler+texture state, so two distinct
|
||||||
|
// sampler objects with identical state still resolve to one VkSampler. This memo only
|
||||||
|
// skips recomputing that hash. Across a draw batch the bound sampler set is stable, so a
|
||||||
|
// binding whose sampler (lifetime id + version, bumped on every setter) and texture
|
||||||
|
// (lifetime id + params version, bumped on the format/border-color setters that feed the
|
||||||
|
// key) are unchanged recycles the VkSampler it resolved last draw; a param change bumps
|
||||||
|
// a version and forces a re-resolve. Both objects are keyed by a never-reused monotonic
|
||||||
|
// lifetime id, so a freed-and-reallocated sampler or texture at the same heap address
|
||||||
|
// always gets a fresh id and misses (a raw pointer would false-hit that ABA) - so a
|
||||||
|
// stale guess can only miss and fall through to the hash, never resolve wrong. Still
|
||||||
|
// reset each frame alongside the descriptor-set cache. Indexed by binding.
|
||||||
|
struct SamplerResolveMemo {
|
||||||
|
Uint64 samplerLifetimeId = 0;
|
||||||
|
Uint64 textureLifetimeId = 0;
|
||||||
|
VkSampler sampler = VK_NULL_HANDLE;
|
||||||
|
Uint16 samplerVersion = 0;
|
||||||
|
Uint16 textureParamsVersion = 0;
|
||||||
|
Bool forceNearestFiltering = false;
|
||||||
|
Bool valid = false;
|
||||||
|
};
|
||||||
|
mutable Vector<SamplerResolveMemo> m_samplerResolveMemo;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsBgra, sizeof(attr.IsBgra)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||||
|
|
||||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||||
@@ -38,70 +39,122 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return XXH64_digest(m_hashState);
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VertexInputStateFactory::HashType VertexInputStateFactory::GetOrComputeHash(
|
||||||
|
const MG_State::GLState::VertexArrayObject& vao) const {
|
||||||
|
HashType hash = 0;
|
||||||
|
if (!vao.GetBackendHashMemo(hash)) {
|
||||||
|
hash = ComputeHash(vao);
|
||||||
|
vao.SetBackendHashMemo(hash);
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||||
const MG_State::GLState::VertexArrayObject& vao) {
|
const MG_State::GLState::VertexArrayObject& vao) {
|
||||||
const HashType hash = ComputeHash(vao);
|
return GetOrCreateVertexInputState(vao, GetOrComputeHash(vao));
|
||||||
|
}
|
||||||
|
|
||||||
|
const VertexInputStateFactory::BackendVertexInputState& VertexInputStateFactory::GetOrCreateVertexInputState(
|
||||||
|
const MG_State::GLState::VertexArrayObject& vao, HashType hash) {
|
||||||
auto it = m_cache.find(hash);
|
auto it = m_cache.find(hash);
|
||||||
if (it != m_cache.end()) {
|
if (it != m_cache.end()) {
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexInputStateBuilder builder;
|
VertexInputStateBuilder builder;
|
||||||
UnorderedMap<SizeT, Uint32> bindingByBufferKey;
|
|
||||||
UnorderedMap<SizeT, Uint32> strideByBufferKey;
|
|
||||||
UnorderedMap<SizeT, VkVertexInputRate> inputRateByBufferKey;
|
|
||||||
Vector<SizeT> bindingBufferKeys;
|
Vector<SizeT> bindingBufferKeys;
|
||||||
|
Vector<SizeT> bindingBaseOffsets;
|
||||||
|
Vector<Uint32> bindingAttributeLocations;
|
||||||
|
Vector<Bool> bindingUsesClientMemory;
|
||||||
|
Vector<VertexStreamConversion> bindingConversions;
|
||||||
|
Uint32 unsupportedAttribMask = 0;
|
||||||
|
|
||||||
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
|
for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) {
|
||||||
const auto& attr = vao.GetAttribute(location);
|
const auto& attr = vao.GetAttribute(location);
|
||||||
if (!attr.Enabled || !attr.Buffer) {
|
if (!attr.Enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto vkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger);
|
const VkFormat sourceVkFormat =
|
||||||
if (vkFormat == VK_FORMAT_UNDEFINED) {
|
ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra);
|
||||||
MGLOG_D("Skipping unsupported vertex attribute layout (location=%u, type=%s, size=%d)",
|
if (sourceVkFormat == VK_FORMAT_UNDEFINED) {
|
||||||
|
MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is "
|
||||||
|
"enabled but cannot be mapped to a VkFormat",
|
||||||
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
|
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
|
||||||
|
unsupportedAttribMask |= (1u << location);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SizeT componentSize = GetComponentSize(attr.Type);
|
VkFormat vkFormat = sourceVkFormat;
|
||||||
if (componentSize == 0) {
|
VertexStreamConversion conversion = VertexStreamConversion::None;
|
||||||
MGLOG_D("Skipping vertex attribute with unknown component size (location=%u, type=%s)",
|
if (!SupportsVertexBufferFormat(vkFormat)) {
|
||||||
|
if (IsScaledIntegerVertexFormat(vkFormat)) {
|
||||||
|
const VkFormat fallbackFormat = ToFloat32VertexFormat(attr.Size);
|
||||||
|
if (fallbackFormat != VK_FORMAT_UNDEFINED && SupportsVertexBufferFormat(fallbackFormat)) {
|
||||||
|
vkFormat = fallbackFormat;
|
||||||
|
conversion = VertexStreamConversion::ScaledIntegerToFloat32;
|
||||||
|
MGLOG_W("Vertex attribute location=%u format=%d lacks "
|
||||||
|
"VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT; using float32 stream format=%d "
|
||||||
|
"(type=%s size=%d normalized=%s integer=%s)",
|
||||||
|
location, static_cast<Int>(sourceVkFormat), static_cast<Int>(vkFormat),
|
||||||
|
MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size,
|
||||||
|
attr.Normalized ? "true" : "false", attr.IsInteger ? "true" : "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversion == VertexStreamConversion::None) {
|
||||||
|
MGLOG_E("Unsupported Vulkan vertex format (location=%u, format=%d, type=%s, size=%d): "
|
||||||
|
"VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT is unavailable and no semantic fallback exists",
|
||||||
|
location, static_cast<Int>(sourceVkFormat),
|
||||||
|
MG_Util::ConvertDataTypeToString(attr.Type).c_str(), attr.Size);
|
||||||
|
unsupportedAttribMask |= (1u << location);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SizeT attribByteSize = GetAttributeByteSize(attr.Type, attr.Size, attr.IsBgra);
|
||||||
|
if (attribByteSize == 0) {
|
||||||
|
MGLOG_E("Vertex attribute with unknown component size (location=%u, type=%s): the array is "
|
||||||
|
"enabled but cannot be sized",
|
||||||
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str());
|
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str());
|
||||||
|
unsupportedAttribMask |= (1u << location);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Uint32 stride = attr.Stride > 0
|
const Uint32 sourceStride =
|
||||||
? static_cast<Uint32>(attr.Stride)
|
attr.Stride > 0 ? static_cast<Uint32>(attr.Stride) : static_cast<Uint32>(attribByteSize);
|
||||||
: static_cast<Uint32>(componentSize * static_cast<SizeT>(attr.Size));
|
const Bool packedAttribute = attr.Type == DataType::Int2101010Rev ||
|
||||||
|
attr.Type == DataType::Uint2101010Rev;
|
||||||
|
const SizeT requiredAlignment = packedAttribute ? attribByteSize : GetComponentSize(attr.Type);
|
||||||
|
if (conversion == VertexStreamConversion::None && requiredAlignment > 1 &&
|
||||||
|
((sourceStride % requiredAlignment) != 0 || (attr.Offset % requiredAlignment) != 0)) {
|
||||||
|
// GL accepts arbitrary byte strides and offsets. Core Vulkan vertex fetches do not
|
||||||
|
// unless VK_EXT_legacy_vertex_attributes is available, so deinterleave this one
|
||||||
|
// attribute into a tightly packed transient stream without changing its format.
|
||||||
|
conversion = VertexStreamConversion::Repack;
|
||||||
|
MGLOG_W("Vertex attribute location=%u uses Vulkan-incompatible alignment "
|
||||||
|
"(offset=%zu stride=%u required=%zu); using a tightly packed stream",
|
||||||
|
location, attr.Offset, sourceStride, requiredAlignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 stride = sourceStride;
|
||||||
|
if (conversion == VertexStreamConversion::Repack) {
|
||||||
|
stride = static_cast<Uint32>(attribByteSize);
|
||||||
|
} else if (conversion == VertexStreamConversion::ScaledIntegerToFloat32) {
|
||||||
|
stride = static_cast<Uint32>(attr.Size * static_cast<Int>(sizeof(Float)));
|
||||||
|
}
|
||||||
const VkVertexInputRate inputRate =
|
const VkVertexInputRate inputRate =
|
||||||
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
||||||
|
|
||||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||||
Uint32 binding = 0;
|
const Uint32 binding = static_cast<Uint32>(bindingBufferKeys.size());
|
||||||
auto itBinding = bindingByBufferKey.find(bufferKey);
|
bindingBufferKeys.push_back(bufferKey);
|
||||||
if (itBinding == bindingByBufferKey.end()) {
|
bindingBaseOffsets.push_back(attr.Buffer ? attr.Offset : 0);
|
||||||
binding = static_cast<Uint32>(bindingByBufferKey.size());
|
bindingAttributeLocations.push_back(location);
|
||||||
bindingByBufferKey.emplace(bufferKey, binding);
|
bindingUsesClientMemory.push_back(attr.Buffer == nullptr);
|
||||||
strideByBufferKey.emplace(bufferKey, stride);
|
bindingConversions.push_back(conversion);
|
||||||
inputRateByBufferKey.emplace(bufferKey, inputRate);
|
builder.AddBinding(binding, stride, inputRate);
|
||||||
bindingBufferKeys.push_back(bufferKey);
|
builder.AddAttribute(location, binding, vkFormat, 0);
|
||||||
builder.AddBinding(binding, stride, inputRate);
|
|
||||||
} else {
|
|
||||||
binding = itBinding->second;
|
|
||||||
if (strideByBufferKey[bufferKey] != stride) {
|
|
||||||
MGLOG_D("Skipping vertex attribute at location %u: stride mismatch (%u vs %u) on same buffer",
|
|
||||||
location, stride, strideByBufferKey[bufferKey]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (inputRateByBufferKey[bufferKey] != inputRate) {
|
|
||||||
MGLOG_D("Skipping vertex attribute at location %u: input-rate mismatch on same buffer", location);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.AddAttribute(location, binding, vkFormat, static_cast<Uint32>(attr.Offset));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& state = builder.Build();
|
const auto& state = builder.Build();
|
||||||
@@ -111,14 +164,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
entry.bindings = builder.GetBindings();
|
entry.bindings = builder.GetBindings();
|
||||||
entry.attributes = builder.GetAttributes();
|
entry.attributes = builder.GetAttributes();
|
||||||
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
||||||
|
entry.bindingBaseOffsets = std::move(bindingBaseOffsets);
|
||||||
|
entry.bindingAttributeLocations = std::move(bindingAttributeLocations);
|
||||||
|
entry.bindingUsesClientMemory = std::move(bindingUsesClientMemory);
|
||||||
|
entry.bindingConversions = std::move(bindingConversions);
|
||||||
|
entry.unsupportedAttribMask = unsupportedAttribMask;
|
||||||
entry.state = state;
|
entry.state = state;
|
||||||
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
|
entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data();
|
||||||
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
|
entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data();
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger) {
|
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger,
|
||||||
|
Bool isBgra) {
|
||||||
|
if (isBgra) {
|
||||||
|
// GL_BGRA: four reversed-order components, always normalized (enforced at validation), only
|
||||||
|
// legal with GL_UNSIGNED_BYTE or a 2_10_10_10 type. The reversed VkFormats put the
|
||||||
|
// components back into R,G,B,A order for the shader.
|
||||||
|
switch (type) {
|
||||||
|
case DataType::Uint8:
|
||||||
|
return VK_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
case DataType::Uint2101010Rev:
|
||||||
|
return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
|
||||||
|
case DataType::Int2101010Rev:
|
||||||
|
return VK_FORMAT_A2R10G10B10_SNORM_PACK32;
|
||||||
|
default:
|
||||||
|
return VK_FORMAT_UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case DataType::Uint2101010Rev:
|
||||||
|
// Packed 2_10_10_10 travels the float-normalizing path only; size is always 4. SNORM/UNORM
|
||||||
|
// normalize, SSCALED/USCALED cast the packed field to float.
|
||||||
|
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
|
||||||
|
return normalized ? VK_FORMAT_A2B10G10R10_UNORM_PACK32 : VK_FORMAT_A2B10G10R10_USCALED_PACK32;
|
||||||
|
case DataType::Int2101010Rev:
|
||||||
|
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
|
||||||
|
return normalized ? VK_FORMAT_A2B10G10R10_SNORM_PACK32 : VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
|
||||||
case DataType::Float32:
|
case DataType::Float32:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||||
@@ -127,6 +209,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
|
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||||
default: return VK_FORMAT_UNDEFINED;
|
default: return VK_FORMAT_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
case DataType::Float16:
|
||||||
|
// GL_HALF_FLOAT is a floating-point array type: it is never an integer attribute, and
|
||||||
|
// GL_TRUE for `normalized` is ignored for float types rather than selecting a *NORM format.
|
||||||
|
if (isInteger) return VK_FORMAT_UNDEFINED;
|
||||||
|
switch (size) {
|
||||||
|
case 1: return VK_FORMAT_R16_SFLOAT;
|
||||||
|
case 2: return VK_FORMAT_R16G16_SFLOAT;
|
||||||
|
case 3: return VK_FORMAT_R16G16B16_SFLOAT;
|
||||||
|
case 4: return VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||||
|
default: return VK_FORMAT_UNDEFINED;
|
||||||
|
}
|
||||||
case DataType::Int32:
|
case DataType::Int32:
|
||||||
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
||||||
switch (size) {
|
switch (size) {
|
||||||
@@ -148,8 +241,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Int16:
|
case DataType::Int16:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1:
|
case 1:
|
||||||
return isInteger ? VK_FORMAT_R16_SINT
|
return isInteger ? VK_FORMAT_R16_SINT : (normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SSCALED);
|
||||||
: (normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SSCALED);
|
|
||||||
case 2:
|
case 2:
|
||||||
return isInteger ? VK_FORMAT_R16G16_SINT
|
return isInteger ? VK_FORMAT_R16G16_SINT
|
||||||
: (normalized ? VK_FORMAT_R16G16_SNORM : VK_FORMAT_R16G16_SSCALED);
|
: (normalized ? VK_FORMAT_R16G16_SNORM : VK_FORMAT_R16G16_SSCALED);
|
||||||
@@ -164,8 +256,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Uint16:
|
case DataType::Uint16:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1:
|
case 1:
|
||||||
return isInteger ? VK_FORMAT_R16_UINT
|
return isInteger ? VK_FORMAT_R16_UINT : (normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_USCALED);
|
||||||
: (normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_USCALED);
|
|
||||||
case 2:
|
case 2:
|
||||||
return isInteger ? VK_FORMAT_R16G16_UINT
|
return isInteger ? VK_FORMAT_R16G16_UINT
|
||||||
: (normalized ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16G16_USCALED);
|
: (normalized ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16G16_USCALED);
|
||||||
@@ -180,8 +271,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Int8:
|
case DataType::Int8:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1:
|
case 1:
|
||||||
return isInteger ? VK_FORMAT_R8_SINT
|
return isInteger ? VK_FORMAT_R8_SINT : (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED);
|
||||||
: (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED);
|
|
||||||
case 2:
|
case 2:
|
||||||
return isInteger ? VK_FORMAT_R8G8_SINT
|
return isInteger ? VK_FORMAT_R8G8_SINT
|
||||||
: (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED);
|
: (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED);
|
||||||
@@ -196,8 +286,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
case DataType::Uint8:
|
case DataType::Uint8:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1:
|
case 1:
|
||||||
return isInteger ? VK_FORMAT_R8_UINT
|
return isInteger ? VK_FORMAT_R8_UINT : (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED);
|
||||||
: (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED);
|
|
||||||
case 2:
|
case 2:
|
||||||
return isInteger ? VK_FORMAT_R8G8_UINT
|
return isInteger ? VK_FORMAT_R8G8_UINT
|
||||||
: (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED);
|
: (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED);
|
||||||
@@ -234,4 +323,57 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SizeT VertexInputStateFactory::GetAttributeByteSize(DataType type, Int size, Bool isBgra) {
|
||||||
|
// The packed 2_10_10_10 types are a single 32-bit word for all 4 components; GL_BGRA is always
|
||||||
|
// 4 components (GL_UNSIGNED_BYTE x4 = 4 bytes, or a packed word = 4 bytes) -- both are 4 bytes.
|
||||||
|
if (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev || isBgra) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
const SizeT componentSize = GetComponentSize(type);
|
||||||
|
return componentSize == 0 ? 0 : componentSize * static_cast<SizeT>(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VertexInputStateFactory::IsScaledIntegerVertexFormat(VkFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case VK_FORMAT_R8_USCALED:
|
||||||
|
case VK_FORMAT_R8_SSCALED:
|
||||||
|
case VK_FORMAT_R8G8_USCALED:
|
||||||
|
case VK_FORMAT_R8G8_SSCALED:
|
||||||
|
case VK_FORMAT_R8G8B8_USCALED:
|
||||||
|
case VK_FORMAT_R8G8B8_SSCALED:
|
||||||
|
case VK_FORMAT_R8G8B8A8_USCALED:
|
||||||
|
case VK_FORMAT_R8G8B8A8_SSCALED:
|
||||||
|
case VK_FORMAT_R16_USCALED:
|
||||||
|
case VK_FORMAT_R16_SSCALED:
|
||||||
|
case VK_FORMAT_R16G16_USCALED:
|
||||||
|
case VK_FORMAT_R16G16_SSCALED:
|
||||||
|
case VK_FORMAT_R16G16B16_USCALED:
|
||||||
|
case VK_FORMAT_R16G16B16_SSCALED:
|
||||||
|
case VK_FORMAT_R16G16B16A16_USCALED:
|
||||||
|
case VK_FORMAT_R16G16B16A16_SSCALED:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFormat VertexInputStateFactory::ToFloat32VertexFormat(Int componentCount) {
|
||||||
|
switch (componentCount) {
|
||||||
|
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||||
|
case 2: return VK_FORMAT_R32G32_SFLOAT;
|
||||||
|
case 3: return VK_FORMAT_R32G32B32_SFLOAT;
|
||||||
|
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||||
|
default: return VK_FORMAT_UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VertexInputStateFactory::SupportsVertexBufferFormat(VkFormat format) const {
|
||||||
|
if (m_physicalDevice == VK_NULL_HANDLE || format == VK_FORMAT_UNDEFINED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VkFormatProperties properties{};
|
||||||
|
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties);
|
||||||
|
return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0;
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -19,29 +19,56 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
public:
|
public:
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|
||||||
|
enum class VertexStreamConversion : Uint8 {
|
||||||
|
None = 0,
|
||||||
|
Repack,
|
||||||
|
ScaledIntegerToFloat32,
|
||||||
|
};
|
||||||
|
|
||||||
struct BackendVertexInputState {
|
struct BackendVertexInputState {
|
||||||
HashType hash = 0;
|
HashType hash = 0;
|
||||||
Vector<VkVertexInputBindingDescription> bindings;
|
Vector<VkVertexInputBindingDescription> bindings;
|
||||||
Vector<VkVertexInputAttributeDescription> attributes;
|
Vector<VkVertexInputAttributeDescription> attributes;
|
||||||
Vector<SizeT> bindingBufferKeys;
|
Vector<SizeT> bindingBufferKeys;
|
||||||
|
Vector<SizeT> bindingBaseOffsets;
|
||||||
|
Vector<Uint32> bindingAttributeLocations;
|
||||||
|
Vector<Bool> bindingUsesClientMemory;
|
||||||
|
Vector<VertexStreamConversion> bindingConversions;
|
||||||
|
// Locations whose array is ENABLED but whose GL format has no VkFormat mapping. They are
|
||||||
|
// absent from `attributes`, so without this mask the draw path cannot tell them apart from
|
||||||
|
// a genuinely disabled array and would silently feed the shader the current attribute value.
|
||||||
|
Uint32 unsupportedAttribMask = 0;
|
||||||
VkPipelineVertexInputStateCreateInfo state{
|
VkPipelineVertexInputStateCreateInfo state{
|
||||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit VertexInputStateFactory(const VulkanRendererConfig& config):
|
VertexInputStateFactory(const VulkanRendererConfig& config, VkPhysicalDevice physicalDevice):
|
||||||
m_config(config) {}
|
m_config(config), m_physicalDevice(physicalDevice) {}
|
||||||
~VertexInputStateFactory() = default;
|
~VertexInputStateFactory() = default;
|
||||||
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
|
||||||
|
|
||||||
HashType ComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
HashType ComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
||||||
|
// Memoized ComputeHash: reuses the VAO's cached hash while its config version
|
||||||
|
// is unchanged. Use this on per-draw paths.
|
||||||
|
HashType GetOrComputeHash(const MG_State::GLState::VertexArrayObject& vao) const;
|
||||||
|
const BackendVertexInputState& GetOrCreateVertexInputState(
|
||||||
|
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
||||||
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
||||||
|
static SizeT GetComponentSize(DataType type);
|
||||||
|
// Tightly-packed byte size of one vertex element for this attribute: componentSize * size for
|
||||||
|
// normal types, and 4 (one packed word) for the 2_10_10_10 types and GL_BGRA. Returns 0 for
|
||||||
|
// an unknown/unsupported type.
|
||||||
|
static SizeT GetAttributeByteSize(DataType type, Int size, Bool isBgra);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger);
|
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false);
|
||||||
static SizeT GetComponentSize(DataType type);
|
static Bool IsScaledIntegerVertexFormat(VkFormat format);
|
||||||
|
static VkFormat ToFloat32VertexFormat(Int componentCount);
|
||||||
|
Bool SupportsVertexBufferFormat(VkFormat format) const;
|
||||||
|
|
||||||
const VulkanRendererConfig& m_config;
|
const VulkanRendererConfig& m_config;
|
||||||
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||||
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,9 +10,73 @@
|
|||||||
|
|
||||||
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;
|
||||||
|
// The app writes into the persistent map with no explicit flush, so its memory must
|
||||||
|
// be host-coherent (Adreno host-visible memory is; requiring it keeps us portable).
|
||||||
|
constexpr VkMemoryPropertyFlags kPersistentBackedRequiredFlags =
|
||||||
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||||
|
|
||||||
|
using MG_State::GLState::BackendBufferResource;
|
||||||
|
using MG_State::GLState::BufferBackendOps;
|
||||||
|
using MG_State::GLState::BufferObject;
|
||||||
|
|
||||||
|
// The manager owned by the active VulkanRenderer; immediate ops route here.
|
||||||
|
VkBufferManager* g_activeBufferManager = nullptr;
|
||||||
|
|
||||||
|
void Ops_Respecify(BufferObject& bufferObject) {
|
||||||
|
if (g_activeBufferManager) {
|
||||||
|
g_activeBufferManager->OnRespecify(bufferObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ops_SubData(BufferObject& bufferObject, SizeT offset, SizeT size) {
|
||||||
|
if (g_activeBufferManager) {
|
||||||
|
g_activeBufferManager->OnSubData(bufferObject, offset, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range,
|
||||||
|
Flags<BufferMappingAccessBit> appAccess) {
|
||||||
|
if (g_activeBufferManager) {
|
||||||
|
g_activeBufferManager->OnFlushMappedRange(bufferObject, range, appAccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Ops_AcquirePersistentMap(BufferObject& bufferObject) {
|
||||||
|
if (g_activeBufferManager) {
|
||||||
|
return g_activeBufferManager->AcquirePersistentMap(bufferObject);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
|
||||||
|
if (g_activeBufferManager) {
|
||||||
|
g_activeBufferManager->OnResourceDestroyed(std::move(resource));
|
||||||
|
}
|
||||||
|
// No active manager: the device/allocator is gone or going away and
|
||||||
|
// Shutdown() already destroyed the storage; dropping the handle here
|
||||||
|
// must not touch Vulkan. VkBufferResource's dtor destroys via VMA only
|
||||||
|
// when the allocation is still valid, which Shutdown() cleared.
|
||||||
|
}
|
||||||
|
|
||||||
|
const BufferBackendOps g_vulkanBufferBackendOps = {
|
||||||
|
.Respecify = Ops_Respecify,
|
||||||
|
.SubData = Ops_SubData,
|
||||||
|
.FlushMappedRange = Ops_FlushMappedRange,
|
||||||
|
.OnDestroy = Ops_OnDestroy,
|
||||||
|
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
|
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
|
||||||
@@ -22,40 +86,93 @@ 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::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 +184,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_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,115 +193,394 @@ 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 only supports resident vertex/index buffers");
|
|
||||||
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) {
|
||||||
|
// 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));
|
||||||
|
if (!CreateResidentStorage(*resource, size, kPersistentBackedUsage, kPersistentBackedRequiredFlags)) {
|
||||||
|
resource->persistentMapped = false;
|
||||||
|
resource->storageSize = 0;
|
||||||
|
resource->usageFlags = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const Uint8* seed = bufferObject.MappedData();
|
||||||
|
if (seed != nullptr) {
|
||||||
|
resource->buffer.Upload(seed, size, 0);
|
||||||
|
}
|
||||||
|
resource->persistentMapped = true;
|
||||||
|
resource->pendingFullUpload = false;
|
||||||
|
resource->storageSize = size;
|
||||||
|
resource->lastUseSerial = 0;
|
||||||
|
return resource->buffer.GetMappedData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VkBufferManager::AcquireResidentSlice(BufferKind kind,
|
||||||
|
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
||||||
|
BufferSlice& outSlice) {
|
||||||
|
const VkBufferUsageFlags requiredUsage = GetVkBufferUsage(kind);
|
||||||
|
MOBILEGL_ASSERT(requiredUsage != 0, "VkBufferManager::AcquireResidentSlice unsupported buffer kind");
|
||||||
|
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::AcquireResidentSlice requires valid buffer object");
|
||||||
|
|
||||||
|
auto resource = GetOrCreateResource(bufferObject);
|
||||||
|
bufferObject->SyncPersistentMappedRange();
|
||||||
|
|
||||||
|
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject->GetSize());
|
||||||
|
if (size == 0) {
|
||||||
|
MGLOG_E("VkBufferManager::AcquireResidentSlice failed: buffer size is zero");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero-copy persistent buffers already hold the app's live coherent writes in
|
||||||
|
// host-visible storage carrying every usage; bind directly, no re-upload/staging.
|
||||||
|
if (resource->persistentMapped && resource->buffer.IsValid() && resource->storageSize == size) {
|
||||||
|
resource->lastUseSerial = m_frameSerial;
|
||||||
|
outSlice = resource->buffer.GetSlice(0, size);
|
||||||
|
return outSlice.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Bool needsRecreate = !resource->buffer.IsValid() || resource->storageSize != size ||
|
||||||
|
((resource->usageFlags & requiredUsage) != requiredUsage) ||
|
||||||
|
resource->pendingFullUpload;
|
||||||
|
if (needsRecreate) {
|
||||||
|
const VkBufferUsageFlags usage = resource->usageFlags | requiredUsage;
|
||||||
|
DeferRelease(std::move(resource->buffer));
|
||||||
|
if (!CreateResidentStorage(*resource, size, usage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!resource->buffer.Upload(bufferObject->MappedData(), size, 0)) {
|
||||||
|
MGLOG_E("VkBufferManager::AcquireResidentSlice failed: initial upload failed");
|
||||||
|
resource->buffer.Destroy();
|
||||||
|
resource->storageSize = 0;
|
||||||
|
resource->usageFlags = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
resource->pendingFullUpload = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
resource->lastUseSerial = m_frameSerial;
|
||||||
|
outSlice = resource->buffer.GetSlice(0, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VkBufferManager::AcquireStreamedSlice(BufferKind kind,
|
||||||
|
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
||||||
|
BufferSlice& outSlice) {
|
||||||
|
(void)kind;
|
||||||
|
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::AcquireStreamedSlice requires valid buffer object");
|
||||||
|
|
||||||
|
auto resource = GetOrCreateResource(bufferObject);
|
||||||
|
bufferObject->SyncPersistentMappedRange();
|
||||||
|
|
||||||
|
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject->GetSize());
|
||||||
|
if (size == 0) {
|
||||||
|
MGLOG_E("VkBufferManager::AcquireStreamedSlice failed: buffer size is zero");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Uint64 changeSerial = bufferObject->GetChangeSerial();
|
||||||
|
if (resource->transientFrameSerial == m_frameSerial && resource->transientChangeSerial == changeSerial &&
|
||||||
|
resource->transientSize == size && resource->transientSlice.IsValid()) {
|
||||||
|
outSlice = resource->transientSlice;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_transientUploadArena.Upload(m_currentFrameIndex, bufferObject->MappedData(), size, 16,
|
||||||
|
outSlice)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
resource->transientSlice = outSlice;
|
||||||
|
resource->transientFrameSerial = m_frameSerial;
|
||||||
|
resource->transientChangeSerial = changeSerial;
|
||||||
|
resource->transientSize = size;
|
||||||
|
|
||||||
|
// Streaming path is authoritative now; release resident storage so we do
|
||||||
|
// not keep a second, stale copy alive (downgrade).
|
||||||
|
if (resource->buffer.IsValid()) {
|
||||||
|
DeferRelease(std::move(resource->buffer));
|
||||||
|
resource->storageSize = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkBufferManager::DeferRelease(VkBufferObject&& buffer) {
|
||||||
if (!buffer.IsValid()) {
|
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) {
|
||||||
@@ -196,51 +593,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// never need to recreate a buffer after it has already been bound.
|
// never need to recreate a buffer after it has already been bound.
|
||||||
return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||||
case BufferKind::Uniform:
|
case BufferKind::Uniform:
|
||||||
|
return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
||||||
|
case BufferKind::TextureBuffer:
|
||||||
|
return VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
|
||||||
|
case BufferKind::ShaderStorage:
|
||||||
|
return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
|
||||||
|
case BufferKind::Indirect:
|
||||||
|
return VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vertex,
|
Vertex,
|
||||||
Index,
|
Index,
|
||||||
Uniform,
|
Uniform,
|
||||||
|
TextureBuffer,
|
||||||
|
ShaderStorage,
|
||||||
|
Indirect,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VkBufferManagerInitInfo {
|
struct VkBufferManagerInitInfo {
|
||||||
@@ -30,6 +33,42 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool transientPersistentMapping = false;
|
Bool transientPersistentMapping = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The DirectVulkan storage behind one frontend buffer (pipe_resource analogue).
|
||||||
|
// Owned (refcounted) by the frontend BufferObject; the manager holds only weak
|
||||||
|
// references (for shutdown) plus strong references on deferred-release lists.
|
||||||
|
class VkBufferResource : public MG_State::GLState::BackendBufferResource {
|
||||||
|
public:
|
||||||
|
~VkBufferResource() override = default;
|
||||||
|
|
||||||
|
// Resident storage (may be invalid for streaming-only buffers).
|
||||||
|
VkBufferObject buffer;
|
||||||
|
VkDeviceSize storageSize = 0;
|
||||||
|
VkBufferUsageFlags usageFlags = 0;
|
||||||
|
// Frame serial of the last GPU reference; drives busy tracking.
|
||||||
|
Uint64 lastUseSerial = 0;
|
||||||
|
// Set when an immediate op could not be applied; forces a full re-upload
|
||||||
|
// on the next AcquireResidentSlice.
|
||||||
|
Bool pendingFullUpload = false;
|
||||||
|
// Backs a zero-copy coherent persistent map (PipeResource GPU residency): the
|
||||||
|
// buffer is HOST_VISIBLE+COHERENT, persistently mapped, carries every usage and is
|
||||||
|
// never orphaned or recreated. Draw-time acquire binds it directly, no re-upload.
|
||||||
|
Bool persistentMapped = false;
|
||||||
|
|
||||||
|
// Cached transient (streaming) slice for the current frame.
|
||||||
|
BufferSlice transientSlice{};
|
||||||
|
Uint64 transientFrameSerial = 0;
|
||||||
|
Uint64 transientChangeSerial = 0;
|
||||||
|
VkDeviceSize transientSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supplies a command buffer that is recording and outside any render pass,
|
||||||
|
// for staged buffer-range copies. Implemented by VulkanRenderer.
|
||||||
|
class IBufferCopyCommandProvider {
|
||||||
|
public:
|
||||||
|
virtual ~IBufferCopyCommandProvider() = default;
|
||||||
|
virtual VkCommandBuffer AcquireBufferCopyCommandBuffer() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class VkBufferManager {
|
class VkBufferManager {
|
||||||
public:
|
public:
|
||||||
Bool Initialize(const VkBufferManagerInitInfo& initInfo);
|
Bool Initialize(const VkBufferManagerInitInfo& initInfo);
|
||||||
@@ -38,35 +77,77 @@ 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);
|
||||||
|
// 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);
|
||||||
@@ -140,12 +143,39 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size));
|
Memcpy(static_cast<Uint8*>(mapped) + offset, data, static_cast<SizeT>(size));
|
||||||
|
const VkResult flushResult = vmaFlushAllocation(m_allocator, m_allocation, offset, size);
|
||||||
|
if (flushResult != VK_SUCCESS) {
|
||||||
|
MGLOG_E("VkBufferObject::Upload failed: vmaFlushAllocation returned %d", flushResult);
|
||||||
|
if (!wasMapped) {
|
||||||
|
Unmap();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!wasMapped) {
|
if (!wasMapped) {
|
||||||
Unmap();
|
Unmap();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool VkBufferObject::Invalidate(VkDeviceSize size, VkDeviceSize offset) {
|
||||||
|
MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Invalidate called on invalid buffer");
|
||||||
|
MOBILEGL_ASSERT(IsMapped(), "VkBufferObject::Invalidate requires mapped memory");
|
||||||
|
MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::Invalidate offset out of range");
|
||||||
|
|
||||||
|
const VkDeviceSize resolvedSize = size == VK_WHOLE_SIZE ? m_size - offset : size;
|
||||||
|
MOBILEGL_ASSERT(offset + resolvedSize <= m_size, "VkBufferObject::Invalidate range out of bounds");
|
||||||
|
if (resolvedSize == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkResult result = vmaInvalidateAllocation(m_allocator, m_allocation, offset, resolvedSize);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
MGLOG_E("VkBufferObject::Invalidate failed: vmaInvalidateAllocation returned %d", result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
BufferSlice VkBufferObject::GetSlice(VkDeviceSize offset, VkDeviceSize size) const {
|
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; }
|
||||||
|
|||||||
@@ -12,12 +12,152 @@
|
|||||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
|
||||||
|
return target >= TextureUploadTarget::CubeMapPositiveX &&
|
||||||
|
target <= TextureUploadTarget::CubeMapNegativeZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool PendingClearMatchesTextureIdentity(const PendingClearKey& key, const TextureIdentity& identity) {
|
||||||
|
return key.texture == identity.texture && key.textureLifetimeId == identity.lifetimeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Uint32 ResolveAttachmentBaseArrayLayer(TextureUploadTarget target) {
|
||||||
|
if (!IsCubeMapFaceUploadTarget(target)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<Uint32>(target) - static_cast<Uint32>(TextureUploadTarget::CubeMapPositiveX);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Uint32 ResolveAttachmentBaseArrayLayer(
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||||
|
if (attachment.IsLayered()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const TextureUploadTarget uploadTarget = attachment.GetTextureUploadTarget();
|
||||||
|
if (!IsCubeMapFaceUploadTarget(uploadTarget)) {
|
||||||
|
return static_cast<Uint32>(std::max(attachment.GetTextureLayer(), 0));
|
||||||
|
}
|
||||||
|
return ResolveAttachmentBaseArrayLayer(uploadTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Uint32 ResolveAttachmentLayerCount(
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||||
|
if (attachment.IsLayered()) {
|
||||||
|
return static_cast<Uint32>(std::max(attachment.GetSize().z(), 1));
|
||||||
|
}
|
||||||
|
return 1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const MG_State::GLState::FramebufferAttachmentObject* GetClearableAttachment(
|
||||||
|
const MG_State::GLState::FramebufferObject& drawFbo, FramebufferAttachmentType attachmentType) {
|
||||||
|
if (attachmentType == FramebufferAttachmentType::None) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& attachment = drawFbo.GetAttachment(attachmentType);
|
||||||
|
if (!attachment.IsTexture() || attachment.IsRenderbuffer()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingClearKey VkClearManager::MakePendingClearKey(MG_State::GLState::ITextureObject* texture, Uint32 mipLevel,
|
||||||
|
Uint32 baseArrayLayer, Uint32 layerCount) {
|
||||||
|
return PendingClearKey {
|
||||||
|
.texture = texture,
|
||||||
|
.textureLifetimeId = texture ? texture->GetLifetimeId() : 0,
|
||||||
|
.mipLevel = mipLevel,
|
||||||
|
.baseArrayLayer = baseArrayLayer,
|
||||||
|
.layerCount = layerCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingClearKey VkClearManager::MakePendingClearKey(
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||||
|
MOBILEGL_ASSERT(attachment.IsTexture() && !attachment.IsRenderbuffer(),
|
||||||
|
"MakePendingClearKey requires a texture framebuffer attachment");
|
||||||
|
auto* texture = attachment.GetTexture().get();
|
||||||
|
MOBILEGL_ASSERT(texture != nullptr, "MakePendingClearKey: texture attachment resolved to null");
|
||||||
|
const Uint32 mipLevel = static_cast<Uint32>(std::max(attachment.GetTextureLevel(), 0));
|
||||||
|
const Uint32 baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||||
|
const Uint32 layerCount = ResolveAttachmentLayerCount(attachment);
|
||||||
|
return MakePendingClearKey(texture, mipLevel, baseArrayLayer, layerCount);
|
||||||
|
}
|
||||||
|
|
||||||
Bool VkClearManager::Initialize() {
|
Bool VkClearManager::Initialize() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkClearManager::Shutdown() {
|
void VkClearManager::Shutdown() {
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
m_pendingClears.clear();
|
||||||
|
m_aliveObjects.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureIdentity VkClearManager::MakeTextureIdentity(MG_State::GLState::ITextureObject* texture) {
|
||||||
|
return TextureIdentity {
|
||||||
|
.texture = texture,
|
||||||
|
.lifetimeId = texture ? texture->GetLifetimeId() : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkClearManager::MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src) {
|
||||||
|
dst.mask |= src.mask;
|
||||||
|
if ((src.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||||
|
dst.color = src.color;
|
||||||
|
}
|
||||||
|
if ((src.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||||
|
dst.depth = src.depth;
|
||||||
|
}
|
||||||
|
if ((src.mask & GL_STENCIL_BUFFER_BIT) != 0) {
|
||||||
|
dst.stencil = src.stencil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkClearManager::ErasePendingClearsForTextureLocked(const TextureIdentity& identity) {
|
||||||
|
Vector<PendingClearKey> keysToErase;
|
||||||
|
keysToErase.reserve(m_pendingClears.size());
|
||||||
|
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||||
|
if (PendingClearMatchesTextureIdentity(it->first, identity)) {
|
||||||
|
keysToErase.emplace_back(it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& key : keysToErase) {
|
||||||
|
m_pendingClears.erase(key);
|
||||||
|
}
|
||||||
|
m_aliveObjects.erase(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VkClearManager::LockTextureIdentityLocked(const TextureIdentity& identity,
|
||||||
|
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||||
|
outTexture.reset();
|
||||||
|
if (identity.texture == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto aliveIt = m_aliveObjects.find(identity);
|
||||||
|
if (aliveIt == m_aliveObjects.end()) {
|
||||||
|
ErasePendingClearsForTextureLocked(identity);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outTexture = aliveIt->second.lock();
|
||||||
|
if (!outTexture || outTexture.get() != identity.texture || outTexture->GetLifetimeId() != identity.lifetimeId) {
|
||||||
|
ErasePendingClearsForTextureLocked(identity);
|
||||||
|
outTexture.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VkClearManager::LockTextureLocked(const PendingClearKey& key,
|
||||||
|
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||||
|
return LockTextureIdentityLocked(TextureIdentity{
|
||||||
|
.texture = key.texture,
|
||||||
|
.lifetimeId = key.textureLifetimeId,
|
||||||
|
}, outTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||||
@@ -26,93 +166,237 @@ 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) {
|
||||||
if (drawbuf == FramebufferAttachmentType::None ||
|
const auto* attachment = GetClearableAttachment(drawFbo, drawbuf);
|
||||||
drawFbo.GetAttachment(drawbuf).IsRenderbuffer())
|
if (!attachment) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
QueueClear({
|
QueueClear({
|
||||||
.color = clearPayload.color,
|
.mask = GL_COLOR_BUFFER_BIT,
|
||||||
.attachmentType = drawbuf
|
.color = clearPayload.color
|
||||||
}, drawFbo.GetAttachment(drawbuf).GetTexture());
|
}, *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(),
|
||||||
drawFbo.GetAttachment(drawbuf).GetTexture()->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) {
|
||||||
!drawFbo.GetAttachment(FramebufferAttachmentType::Depth).IsRenderbuffer()) {
|
const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Depth);
|
||||||
QueueClear({
|
if (attachment) {
|
||||||
.depth = clearPayload.depth,
|
QueueClear({
|
||||||
.attachmentType = FramebufferAttachmentType::Depth,
|
.mask = GL_DEPTH_BUFFER_BIT,
|
||||||
}, drawFbo.GetAttachment(FramebufferAttachmentType::Depth).GetTexture());
|
.depth = clearPayload.depth,
|
||||||
MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__,
|
}, *attachment);
|
||||||
drawFbo.GetAttachment(FramebufferAttachmentType::Depth).GetTexture()->GetExternalIndex(), clearPayload.depth);
|
|
||||||
|
MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__,
|
||||||
|
attachment->GetTexture()->GetExternalIndex(), clearPayload.depth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mask & GL_STENCIL_BUFFER_BIT &&
|
if (mask & GL_STENCIL_BUFFER_BIT) {
|
||||||
!drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).IsRenderbuffer()) {
|
const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Stencil);
|
||||||
QueueClear({
|
if (attachment) {
|
||||||
.stencil = clearPayload.stencil,
|
QueueClear({
|
||||||
.attachmentType = FramebufferAttachmentType::Stencil,
|
.mask = GL_STENCIL_BUFFER_BIT,
|
||||||
}, drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).GetTexture());
|
.stencil = clearPayload.stencil,
|
||||||
MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__,
|
}, *attachment);
|
||||||
drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).GetTexture()->GetExternalIndex(), clearPayload.stencil);
|
|
||||||
|
MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__,
|
||||||
|
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) {
|
||||||
WeakPtr<MG_State::GLState::ITextureObject> weakTexturePtr = texture;
|
if (clearPayload.mask == 0 || !texture) {
|
||||||
if (weakTexturePtr.expired())
|
|
||||||
return;
|
return;
|
||||||
auto* pTexture = weakTexturePtr.lock().get();
|
}
|
||||||
m_aliveObjects[pTexture] = weakTexturePtr;
|
|
||||||
m_pendingClears[pTexture] = clearPayload;
|
const PendingClearKey key = MakePendingClearKey(texture.get());
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
|
||||||
|
auto& pending = m_pendingClears[key];
|
||||||
|
MergeClearPayload(pending, clearPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||||
|
if (clearPayload.mask == 0 || !attachment.IsTexture() || attachment.IsRenderbuffer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto texture = attachment.GetTexture();
|
||||||
|
if (!texture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PendingClearKey key = MakePendingClearKey(attachment);
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
|
||||||
|
auto& pending = m_pendingClears[key];
|
||||||
|
MergeClearPayload(pending, clearPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
|
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];
|
const Uint64 lifetimeId = texture->GetLifetimeId();
|
||||||
MGLOG_D("%s: Got pending clear for texture %d (%s), clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__,
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
texture->GetExternalIndex(),
|
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||||
MG_Util::ConvertTextureInternalFormatToString(texture->GetFormat()).c_str(),
|
if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) {
|
||||||
|
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||||
|
return LockTextureLocked(it->first, liveTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VkClearManager::HasPendingClear(const PendingClearKey& key) {
|
||||||
|
if (key.texture == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
if (m_pendingClears.find(key) == m_pendingClears.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||||
|
return LockTextureLocked(key, liveTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VkClearManager::HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||||
|
if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return HasPendingClear(MakePendingClearKey(attachment));
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload) {
|
||||||
|
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||||
|
return GetPendingClear(key, outPayload, liveTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload,
|
||||||
|
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||||
|
if (key.texture == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
if (!LockTextureLocked(key, outTexture)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto it = m_pendingClears.find(key);
|
||||||
|
if (it == m_pendingClears.end()) {
|
||||||
|
outTexture.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outPayload = it->second;
|
||||||
|
MGLOG_D("%s: Got pending clear for texture@%p lifetime=%llu, mip=%u layer=%u count=%u mask=0x%x clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__,
|
||||||
|
static_cast<void*>(key.texture),
|
||||||
|
static_cast<unsigned long long>(key.textureLifetimeId),
|
||||||
|
key.mipLevel, key.baseArrayLayer, key.layerCount,
|
||||||
|
static_cast<Uint32>(outPayload.mask),
|
||||||
outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3],
|
outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3],
|
||||||
outPayload.depth,
|
outPayload.depth,
|
||||||
outPayload.stencil);
|
outPayload.stencil);
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
}
|
||||||
|
|
||||||
|
const TextureIdentity identity = MakeTextureIdentity(texture);
|
||||||
|
MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex());
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
ErasePendingClearsForTextureLocked(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkClearManager::PopPendingClear(const PendingClearKey& key) {
|
||||||
|
if (key.texture == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
auto it = m_pendingClears.find(key);
|
||||||
|
if (it != m_pendingClears.end()) {
|
||||||
|
m_pendingClears.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MGLOG_D("%s: Pop pending clear for texture@%p lifetime=%llu mip=%u layer=%u count=%u", __func__,
|
||||||
|
static_cast<void*>(key.texture), static_cast<unsigned long long>(key.textureLifetimeId),
|
||||||
|
key.mipLevel, key.baseArrayLayer, key.layerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkClearManager::PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||||
|
if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PopPendingClear(MakePendingClearKey(attachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
SizeT VkClearManager::CollectGarbage() {
|
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 (const auto& [raw, weak]: m_aliveObjects) {
|
expiredTextures.reserve(m_aliveObjects.size());
|
||||||
if (weak.expired()) {
|
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
|
||||||
count++;
|
if (it->second.expired()) {
|
||||||
m_pendingClears.erase(raw);
|
expiredTextures.emplace_back(it->first);
|
||||||
m_aliveObjects.erase(raw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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,7 @@
|
|||||||
#include "MG_Util/Math/VectorTypes.h"
|
#include "MG_Util/Math/VectorTypes.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
struct ClearFramebufferPayload {
|
struct ClearFramebufferPayload {
|
||||||
@@ -23,16 +24,70 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct ClearAttachmentPayload {
|
struct ClearAttachmentPayload {
|
||||||
union {
|
GLbitfield mask = 0;
|
||||||
FloatVec4 color;
|
FloatVec4 color = FloatVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
Float depth{};
|
Float depth = 1.0f;
|
||||||
Uint32 stencil;
|
Uint32 stencil = 0;
|
||||||
};
|
};
|
||||||
FramebufferAttachmentType attachmentType = FramebufferAttachmentType::Color0;
|
|
||||||
|
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();
|
||||||
|
|
||||||
@@ -40,13 +95,33 @@ 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;
|
mutable std::mutex m_mutex;
|
||||||
UnorderedMap<MG_State::GLState::ITextureObject*, WeakPtr<MG_State::GLState::ITextureObject>> m_aliveObjects;
|
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,26 +16,49 @@
|
|||||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
enum class TrackedAttachmentTarget : Uint8 {
|
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;
|
||||||
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
|
Uint32 textureMipLevel = 0;
|
||||||
Uint32 swapchainImageIndex = 0;
|
Uint32 swapchainImageIndex = 0;
|
||||||
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DepthStencilAttachmentLoadInfo {
|
||||||
|
VkAttachmentLoadOp depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
|
VkAttachmentLoadOp stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
|
VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
};
|
||||||
|
|
||||||
|
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
|
||||||
|
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil);
|
||||||
|
IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent,
|
||||||
|
VkExtent2D swapchainExtent);
|
||||||
|
|
||||||
struct RenderPassEntry {
|
struct RenderPassEntry {
|
||||||
static inline VkDevice s_device;
|
static inline VkDevice s_device;
|
||||||
static inline Vector<VkTextureManager::TextureResource*> s_textureResourcesScratch;
|
static inline Vector<VkTextureManager::TextureResource*> s_textureResourcesScratch;
|
||||||
@@ -46,8 +69,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<PendingClearAttachmentInfo> pendingClearAttachments;
|
Vector<PendingClearAttachmentInfo> pendingClearAttachments;
|
||||||
Vector<TrackedAttachmentLayoutInfo> trackedAttachmentLayouts;
|
Vector<TrackedAttachmentLayoutInfo> trackedAttachmentLayouts;
|
||||||
Uint32 attachmentCount = 0;
|
Uint32 attachmentCount = 0;
|
||||||
|
Uint32 colorAttachmentCount = 0;
|
||||||
|
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;
|
||||||
@@ -59,8 +88,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
std::swap(pendingClearAttachments, that.pendingClearAttachments);
|
std::swap(pendingClearAttachments, that.pendingClearAttachments);
|
||||||
std::swap(trackedAttachmentLayouts, that.trackedAttachmentLayouts);
|
std::swap(trackedAttachmentLayouts, that.trackedAttachmentLayouts);
|
||||||
std::swap(attachmentCount, that.attachmentCount);
|
std::swap(attachmentCount, that.attachmentCount);
|
||||||
|
std::swap(colorAttachmentCount, that.colorAttachmentCount);
|
||||||
|
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
|
||||||
|
std::swap(sampleCount, that.sampleCount);
|
||||||
std::swap(extent, that.extent);
|
std::swap(extent, that.extent);
|
||||||
std::swap(subpass, that.subpass);
|
std::swap(layers, that.layers);
|
||||||
|
std::swap(lastUsedFrame, that.lastUsedFrame);
|
||||||
}
|
}
|
||||||
RenderPassEntry(
|
RenderPassEntry(
|
||||||
Uint64 hash,
|
Uint64 hash,
|
||||||
@@ -70,7 +103,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const Vector<PendingClearAttachmentInfo>& pendingClearAttachments,
|
const Vector<PendingClearAttachmentInfo>& pendingClearAttachments,
|
||||||
const Vector<TrackedAttachmentLayoutInfo>& trackedAttachmentLayouts,
|
const Vector<TrackedAttachmentLayoutInfo>& trackedAttachmentLayouts,
|
||||||
Uint32 attachmentCount,
|
Uint32 attachmentCount,
|
||||||
IntVec2 extent, int subpass):
|
Uint32 colorAttachmentCount,
|
||||||
|
Bool hasDepthStencilAttachment,
|
||||||
|
VkSampleCountFlagBits sampleCount,
|
||||||
|
IntVec2 extent, Uint32 layers):
|
||||||
hash(hash),
|
hash(hash),
|
||||||
renderPass(renderpass),
|
renderPass(renderpass),
|
||||||
framebuffer(framebuffer),
|
framebuffer(framebuffer),
|
||||||
@@ -78,8 +114,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
pendingClearAttachments(Move(pendingClearAttachments)),
|
pendingClearAttachments(Move(pendingClearAttachments)),
|
||||||
trackedAttachmentLayouts(Move(trackedAttachmentLayouts)),
|
trackedAttachmentLayouts(Move(trackedAttachmentLayouts)),
|
||||||
attachmentCount(attachmentCount),
|
attachmentCount(attachmentCount),
|
||||||
|
colorAttachmentCount(colorAttachmentCount),
|
||||||
|
hasDepthStencilAttachment(hasDepthStencilAttachment),
|
||||||
|
sampleCount(sampleCount),
|
||||||
extent(extent),
|
extent(extent),
|
||||||
subpass(subpass)
|
layers(layers)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
~RenderPassEntry() {
|
~RenderPassEntry() {
|
||||||
@@ -119,8 +158,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
public:
|
public:
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
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();
|
||||||
|
|
||||||
Bool Initialize();
|
Bool Initialize();
|
||||||
@@ -129,23 +168,87 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
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);
|
RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex);
|
||||||
|
void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||||
|
const MG_State::GLState::FramebufferObject& drawFbo);
|
||||||
|
void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload,
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||||
|
void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer);
|
||||||
|
// Frame boundary hook: ages the render-pass cache and evicts long-unused
|
||||||
|
// entries (their command buffers retired many frames ago).
|
||||||
|
void OnPresent();
|
||||||
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
|
static Bool 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;
|
||||||
|
|
||||||
|
// Bumped whenever a renderbuffer VkImage is (re)created; together with the texture
|
||||||
|
// manager's image epoch this invalidates the render-pass fast path on any attachment
|
||||||
|
// image recreation.
|
||||||
|
Uint64 m_renderbufferImageEpoch = 1;
|
||||||
|
|
||||||
|
// Per-draw fast-path memo for GetOrCreateRenderPass (dirty-flag state tracking): when the
|
||||||
|
// framebuffer state is provably unchanged since the last resolution, the active render pass
|
||||||
|
// is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch /
|
||||||
|
// version change, swapchain rotation, any attachment image recreation (the two epochs),
|
||||||
|
// or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed).
|
||||||
|
Bool m_rpFastValid = false;
|
||||||
|
const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr;
|
||||||
|
Uint16 m_rpFastFboVersion = 0;
|
||||||
|
Uint32 m_rpFastSwapchainIndex = 0;
|
||||||
|
Uint64 m_rpFastTexEpoch = 0;
|
||||||
|
Uint64 m_rpFastRbEpoch = 0;
|
||||||
|
Uint64 m_rpFastRenderPassHash = 0;
|
||||||
|
|
||||||
|
struct RenderbufferResource {
|
||||||
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
|
VkImage image = VK_NULL_HANDLE;
|
||||||
|
VmaAllocation allocation = nullptr;
|
||||||
|
VkImageView view = VK_NULL_HANDLE;
|
||||||
|
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
|
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
|
||||||
|
VkExtent2D extent = {0, 0};
|
||||||
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
|
||||||
|
Int samples = 0;
|
||||||
|
|
||||||
|
void Destroy(VkDevice device, VmaAllocator allocator);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PendingRenderbufferClear {
|
||||||
|
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||||
|
ClearAttachmentPayload payload{};
|
||||||
|
};
|
||||||
|
|
||||||
|
UnorderedMap<MG_State::GLState::RenderbufferObject*, RenderbufferResource> m_renderbufferResources;
|
||||||
|
UnorderedMap<MG_State::GLState::RenderbufferObject*, PendingRenderbufferClear> m_pendingRenderbufferClears;
|
||||||
|
|
||||||
|
RenderbufferResource* GetOrCreateRenderbufferResource(
|
||||||
|
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||||
|
Bool GetPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer,
|
||||||
|
ClearAttachmentPayload& outPayload) const;
|
||||||
|
Bool HasPendingRenderbufferClear(
|
||||||
|
const MG_State::GLState::FramebufferAttachmentObject& attachment) const;
|
||||||
|
void CollectRenderbufferGarbage();
|
||||||
|
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline 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
|
||||||
|
|||||||
@@ -10,17 +10,74 @@
|
|||||||
|
|
||||||
#include "MG_State/GLState/Core.h"
|
#include "MG_State/GLState/Core.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
namespace {
|
||||||
|
Bool UsesBorderColor(const MG_State::GLState::SamplerObject& sampler) {
|
||||||
|
return sampler.GetWrapS() == SamplerWrapMode::ClampToBorder ||
|
||||||
|
sampler.GetWrapT() == SamplerWrapMode::ClampToBorder ||
|
||||||
|
sampler.GetWrapR() == SamplerWrapMode::ClampToBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool IsDepthTextureFormat(TextureInternalFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureInternalFormat::DepthComponent:
|
||||||
|
case TextureInternalFormat::DepthComponent16:
|
||||||
|
case TextureInternalFormat::DepthComponent24:
|
||||||
|
case TextureInternalFormat::DepthComponent32:
|
||||||
|
case TextureInternalFormat::DepthComponent32F:
|
||||||
|
case TextureInternalFormat::Depth24Stencil8:
|
||||||
|
case TextureInternalFormat::Depth32FStencil8:
|
||||||
|
case TextureInternalFormat::DepthStencil:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool NearlyEqual(Float lhs, Float rhs) {
|
||||||
|
return std::fabs(lhs - rhs) <= 1e-6f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Float ResolveEffectiveMaxLod(const MG_State::GLState::SamplerObject& sampler) {
|
||||||
|
if (sampler.GetMipmapMode() == SamplerMipmapMode::None) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
return sampler.GetMaxLod();
|
||||||
|
}
|
||||||
|
|
||||||
|
Float ResolveEffectiveMinLod(const MG_State::GLState::SamplerObject& sampler, Float effectiveMaxLod) {
|
||||||
|
return std::min(sampler.GetMinLod(), effectiveMaxLod);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Bool VkSamplerManager::Initialize(const InitInfo& initInfo) {
|
Bool VkSamplerManager::Initialize(const InitInfo& initInfo) {
|
||||||
Shutdown();
|
Shutdown();
|
||||||
|
|
||||||
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) {
|
||||||
@@ -34,10 +91,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_config = nullptr;
|
m_config = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler) const {
|
Uint64 VkSamplerManager::BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||||
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
|
Bool forceNearestFiltering) const {
|
||||||
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
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)));
|
||||||
|
|
||||||
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();
|
||||||
@@ -50,21 +111,30 @@ 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 minLod = sampler.GetMinLod();
|
const auto maxLod = ResolveEffectiveMaxLod(sampler);
|
||||||
|
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
||||||
const auto maxLod = sampler.GetMaxLod();
|
|
||||||
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 = sampler.GetSamplerCompareFunc();
|
const auto compareFunc = ResolveCompareFunc(sampler, texture);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
||||||
|
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
||||||
return XXH64_digest(m_hashState);
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler) {
|
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const Uint64 key = BuildSamplerKey(sampler);
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
|
Bool forceNearestFiltering) {
|
||||||
|
const Uint64 key = BuildSamplerKey(sampler, texture, forceNearestFiltering);
|
||||||
auto it = m_samplers.find(key);
|
auto it = m_samplers.find(key);
|
||||||
if (it != m_samplers.end()) {
|
if (it != m_samplers.end()) {
|
||||||
return it->second.handle;
|
return it->second.handle;
|
||||||
@@ -72,20 +142,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
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 ? 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(sampler.GetSamplerCompareFunc());
|
samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
|
||||||
samplerInfo.minLod = sampler.GetMinLod();
|
samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler);
|
||||||
samplerInfo.maxLod = sampler.GetMaxLod();
|
samplerInfo.minLod = ResolveEffectiveMinLod(sampler, samplerInfo.maxLod);
|
||||||
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
samplerInfo.borderColor = ResolveVkBorderColor(sampler, texture);
|
||||||
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||||
|
|
||||||
VkSampler vkSampler = VK_NULL_HANDLE;
|
VkSampler vkSampler = VK_NULL_HANDLE;
|
||||||
@@ -153,4 +227,49 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return VK_COMPARE_OP_ALWAYS;
|
return VK_COMPARE_OP_ALWAYS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SamplerCompareFunc VkSamplerManager::ResolveCompareFunc(const MG_State::GLState::SamplerObject& sampler,
|
||||||
|
const MG_State::GLState::ITextureObject& texture) {
|
||||||
|
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
||||||
|
if (sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture &&
|
||||||
|
IsDepthTextureFormat(texture.GetFormat()) && compareFunc == SamplerCompareFunc::Always) {
|
||||||
|
return SamplerCompareFunc::LessEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkBorderColor VkSamplerManager::ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||||
|
const MG_State::GLState::ITextureObject& texture) {
|
||||||
|
if (!UsesBorderColor(sampler)) {
|
||||||
|
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& borderColor = texture.GetBorderColor();
|
||||||
|
const Bool isDepthTexture = IsDepthTextureFormat(texture.GetFormat());
|
||||||
|
|
||||||
|
if (isDepthTexture) {
|
||||||
|
if (NearlyEqual(borderColor.x(), 1.0f)) {
|
||||||
|
return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||||
|
}
|
||||||
|
if (NearlyEqual(borderColor.x(), 0.0f)) {
|
||||||
|
return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Bool rgbZero = NearlyEqual(borderColor.x(), 0.0f) && NearlyEqual(borderColor.y(), 0.0f) &&
|
||||||
|
NearlyEqual(borderColor.z(), 0.0f);
|
||||||
|
if (rgbZero && NearlyEqual(borderColor.w(), 0.0f)) {
|
||||||
|
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||||
|
}
|
||||||
|
if (rgbZero && NearlyEqual(borderColor.w(), 1.0f)) {
|
||||||
|
return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
|
||||||
|
}
|
||||||
|
if (NearlyEqual(borderColor.x(), 1.0f) && NearlyEqual(borderColor.y(), 1.0f) &&
|
||||||
|
NearlyEqual(borderColor.z(), 1.0f) && NearlyEqual(borderColor.w(), 1.0f)) {
|
||||||
|
return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_State::GLState {
|
namespace MobileGL::MG_State::GLState {
|
||||||
class SamplerObject;
|
class SamplerObject;
|
||||||
|
class ITextureObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
@@ -23,12 +24,18 @@ 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();
|
||||||
|
|
||||||
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler);
|
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||||
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
|
Bool forceNearestFiltering = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct SamplerCacheEntry {
|
struct SamplerCacheEntry {
|
||||||
@@ -37,14 +44,28 @@ private:
|
|||||||
Uint16 version = 0;
|
Uint16 version = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler) const;
|
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
||||||
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
|
Bool forceNearestFiltering) 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,
|
||||||
|
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;
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,35 +12,158 @@
|
|||||||
#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>
|
||||||
|
|
||||||
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; }
|
||||||
|
|
||||||
|
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;
|
||||||
VmaAllocator allocator = nullptr;
|
VmaAllocator allocator = nullptr;
|
||||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||||
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
VkQueue graphicsQueue = VK_NULL_HANDLE;
|
||||||
|
Uint32 frameCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextureResource {
|
struct TextureResource {
|
||||||
|
struct AttachmentViewKey {
|
||||||
|
Uint32 mipLevel = 0;
|
||||||
|
Uint32 baseArrayLayer = 0;
|
||||||
|
Uint32 layerCount = 1;
|
||||||
|
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
|
||||||
|
Bool operator==(const AttachmentViewKey& other) const {
|
||||||
|
return mipLevel == other.mipLevel &&
|
||||||
|
baseArrayLayer == other.baseArrayLayer &&
|
||||||
|
layerCount == other.layerCount &&
|
||||||
|
viewType == other.viewType;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AttachmentViewKeyHash {
|
||||||
|
SizeT operator()(const AttachmentViewKey& key) const {
|
||||||
|
SizeT hash = std::hash<Uint32>{}(key.mipLevel);
|
||||||
|
hash ^= std::hash<Uint32>{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
hash ^= std::hash<Uint32>{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
|
||||||
|
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageImageViewKey {
|
||||||
|
Uint32 mipLevel = 0;
|
||||||
|
Uint32 baseArrayLayer = 0;
|
||||||
|
Uint32 layerCount = 1;
|
||||||
|
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
|
|
||||||
|
Bool operator==(const StorageImageViewKey& other) const {
|
||||||
|
return mipLevel == other.mipLevel &&
|
||||||
|
baseArrayLayer == other.baseArrayLayer &&
|
||||||
|
layerCount == other.layerCount &&
|
||||||
|
viewType == other.viewType &&
|
||||||
|
format == other.format;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SampledImageViewKey {
|
||||||
|
Uint32 baseMipLevel = 0;
|
||||||
|
Uint32 levelCount = 1;
|
||||||
|
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
|
|
||||||
|
Bool operator==(const SampledImageViewKey& other) const {
|
||||||
|
return baseMipLevel == other.baseMipLevel &&
|
||||||
|
levelCount == other.levelCount &&
|
||||||
|
viewType == other.viewType &&
|
||||||
|
format == other.format;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SampledImageViewKeyHash {
|
||||||
|
SizeT operator()(const SampledImageViewKey& key) const {
|
||||||
|
SizeT hash = std::hash<Uint32>{}(key.baseMipLevel);
|
||||||
|
hash ^= std::hash<Uint32>{}(key.levelCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
|
||||||
|
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.format)) +
|
||||||
|
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageImageViewKeyHash {
|
||||||
|
SizeT operator()(const StorageImageViewKey& key) const {
|
||||||
|
SizeT hash = std::hash<Uint32>{}(key.mipLevel);
|
||||||
|
hash ^= std::hash<Uint32>{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
hash ^= std::hash<Uint32>{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.viewType)) +
|
||||||
|
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
hash ^= std::hash<Uint32>{}(static_cast<Uint32>(key.format)) +
|
||||||
|
0x9e3779b9u + (hash << 6) + (hash >> 2);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
VkImage image = VK_NULL_HANDLE;
|
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;
|
||||||
Vector<VkImageView> perMipViews;
|
Vector<VkImageView> perMipViews;
|
||||||
|
Vector<VkImageView> perMipSampledViews;
|
||||||
|
UnorderedMap<AttachmentViewKey, VkImageView, AttachmentViewKeyHash> attachmentViews;
|
||||||
|
UnorderedMap<SampledImageViewKey, VkImageView, SampledImageViewKeyHash> alternateSampledViews;
|
||||||
|
UnorderedMap<StorageImageViewKey, VkImageView, StorageImageViewKeyHash> storageImageViews;
|
||||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
VkExtent2D extent = {0, 0};
|
VkExtent2D extent = {0, 0};
|
||||||
|
Uint32 depth = 1;
|
||||||
|
Uint32 arrayLayers = 1;
|
||||||
Uint32 mipLevels = 1;
|
Uint32 mipLevels = 1;
|
||||||
Uint32 sampledBaseMipLevel = 0;
|
Uint32 sampledBaseMipLevel = 0;
|
||||||
Uint32 sampledLevelCount = 1;
|
Uint32 sampledLevelCount = 1;
|
||||||
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;
|
||||||
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
VkImageCreateFlags imageCreateFlags = 0;
|
||||||
Uint16 syncedTextureParamsVersion = 0;
|
Uint16 syncedTextureParamsVersion = 0;
|
||||||
|
// Snapshot of ITextureObject::GetContentVersion() at the last successful sync;
|
||||||
|
// lets SyncTexture skip the whole re-check/re-upload when content is unchanged.
|
||||||
|
Uint64 syncedContentVersion = 0;
|
||||||
|
// Snapshot of the defined mip-level count at the last sync. Folded into the early-out key
|
||||||
|
// as defense-in-depth: any path that grows the level set (which resizes the sampled view)
|
||||||
|
// busts the skip even if it failed to bump the content version.
|
||||||
|
Uint32 syncedMipLevelCount = 0;
|
||||||
|
|
||||||
TextureResource() = default;
|
TextureResource() = default;
|
||||||
TextureResource(const TextureResource&) = delete;
|
TextureResource(const TextureResource&) = delete;
|
||||||
@@ -48,41 +171,88 @@ public:
|
|||||||
std::swap(this->image, that.image);
|
std::swap(this->image, that.image);
|
||||||
std::swap(this->allocation, that.allocation);
|
std::swap(this->allocation, that.allocation);
|
||||||
std::swap(this->fullView, that.fullView);
|
std::swap(this->fullView, that.fullView);
|
||||||
|
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->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->arrayLayers, that.arrayLayers);
|
||||||
std::swap(this->mipLevels, that.mipLevels);
|
std::swap(this->mipLevels, that.mipLevels);
|
||||||
std::swap(this->sampledBaseMipLevel, that.sampledBaseMipLevel);
|
std::swap(this->sampledBaseMipLevel, that.sampledBaseMipLevel);
|
||||||
std::swap(this->sampledLevelCount, that.sampledLevelCount);
|
std::swap(this->sampledLevelCount, that.sampledLevelCount);
|
||||||
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->sampleCount, that.sampleCount);
|
||||||
|
std::swap(this->imageCreateFlags, that.imageCreateFlags);
|
||||||
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
|
||||||
|
std::swap(this->syncedContentVersion, that.syncedContentVersion);
|
||||||
|
std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reset() {
|
void Reset() {
|
||||||
if (fullView != VK_NULL_HANDLE) {
|
if (fullView != VK_NULL_HANDLE) {
|
||||||
vkDestroyImageView(s_device, fullView, nullptr);
|
vkDestroyImageView(s_device, fullView, nullptr);
|
||||||
}
|
}
|
||||||
|
if (sampledView != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyImageView(s_device, sampledView, nullptr);
|
||||||
|
}
|
||||||
for (const auto attachmentView : perMipViews) {
|
for (const auto attachmentView : perMipViews) {
|
||||||
if (attachmentView != VK_NULL_HANDLE) {
|
if (attachmentView != VK_NULL_HANDLE) {
|
||||||
vkDestroyImageView(s_device, attachmentView, nullptr);
|
vkDestroyImageView(s_device, attachmentView, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const auto sampledView : perMipSampledViews) {
|
||||||
|
if (sampledView != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyImageView(s_device, sampledView, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& [_, attachmentView] : attachmentViews) {
|
||||||
|
if (attachmentView != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyImageView(s_device, attachmentView, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& [_, sampledView] : alternateSampledViews) {
|
||||||
|
if (sampledView != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyImageView(s_device, sampledView, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& [_, storageImageView] : storageImageViews) {
|
||||||
|
if (storageImageView != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyImageView(s_device, storageImageView, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (image != VK_NULL_HANDLE && allocation != nullptr) {
|
if (image != VK_NULL_HANDLE && allocation != nullptr) {
|
||||||
vmaDestroyImage(s_allocator, image, allocation);
|
vmaDestroyImage(s_allocator, image, allocation);
|
||||||
}
|
}
|
||||||
fullView = VK_NULL_HANDLE;
|
fullView = VK_NULL_HANDLE;
|
||||||
|
sampledView = VK_NULL_HANDLE;
|
||||||
perMipViews.clear();
|
perMipViews.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;
|
||||||
extent = {0, 0};
|
extent = {0, 0};
|
||||||
|
depth = 1;
|
||||||
|
arrayLayers = 1;
|
||||||
mipLevels = 1;
|
mipLevels = 1;
|
||||||
sampledBaseMipLevel = 0;
|
sampledBaseMipLevel = 0;
|
||||||
sampledLevelCount = 1;
|
sampledLevelCount = 1;
|
||||||
format = VK_FORMAT_UNDEFINED;
|
format = VK_FORMAT_UNDEFINED;
|
||||||
aspect = VK_IMAGE_ASPECT_NONE;
|
aspect = VK_IMAGE_ASPECT_NONE;
|
||||||
|
viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
imageCreateFlags = 0;
|
||||||
syncedTextureParamsVersion = 0;
|
syncedTextureParamsVersion = 0;
|
||||||
|
syncedContentVersion = 0;
|
||||||
|
syncedMipLevelCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
~TextureResource() {
|
~TextureResource() {
|
||||||
@@ -95,21 +265,66 @@ public:
|
|||||||
|
|
||||||
Bool Initialize(const InitInfo& initInfo);
|
Bool Initialize(const InitInfo& initInfo);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
void BeginFrame(Uint32 frameIndex);
|
||||||
|
|
||||||
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 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,
|
||||||
|
MG_State::GLState::ITextureObject* texture,
|
||||||
|
Uint32 writtenMipLevel,
|
||||||
|
VkImageLayout newLayout);
|
||||||
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);
|
||||||
|
|
||||||
|
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,
|
||||||
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
|
VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask,
|
||||||
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask,
|
VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask,
|
||||||
Uint32 baseMipLevel = 0, Uint32 levelCount = 1);
|
Uint32 baseMipLevel = 0, Uint32 levelCount = 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;
|
||||||
|
|
||||||
Bool SyncTexture(MG_State::GLState::ITextureObject &texture,
|
Bool SyncTexture(MG_State::GLState::ITextureObject &texture,
|
||||||
TextureResource &outResource);
|
TextureResource &outResource);
|
||||||
@@ -119,7 +334,11 @@ private:
|
|||||||
TextureResource &resource);
|
TextureResource &resource);
|
||||||
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,
|
||||||
Uint32 baseMipLevel, Uint32 levelCount) const;
|
VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount,
|
||||||
|
Uint32 baseArrayLayer,
|
||||||
|
Uint32 layerCount,
|
||||||
|
const VkComponentMapping* components = nullptr,
|
||||||
|
VkImageUsageFlags viewUsage = 0) const;
|
||||||
Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture,
|
Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture,
|
||||||
TextureUploadTarget uploadTarget,
|
TextureUploadTarget uploadTarget,
|
||||||
TextureResource &outResource);
|
TextureResource &outResource);
|
||||||
@@ -132,15 +351,37 @@ private:
|
|||||||
static void ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels,
|
static void ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels,
|
||||||
Uint32& outBaseMipLevel, Uint32& outLevelCount);
|
Uint32& outBaseMipLevel, Uint32& outLevelCount);
|
||||||
static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format);
|
static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format);
|
||||||
|
void DeferResourceRelease(TextureResource&& resource);
|
||||||
|
void DeferViewRelease(VkImageView view);
|
||||||
|
void CollectDeferredReleases(Uint32 frameIndex);
|
||||||
|
void DestroyDeferredReleases();
|
||||||
|
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
||||||
|
void EraseTrackedTexture(const TextureIdentity& identity);
|
||||||
|
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
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;
|
||||||
|
Uint32 m_currentFrameIndex = 0;
|
||||||
|
|
||||||
Uint8 m_gcCounter = 0;
|
Uint8 m_gcCounter = 0;
|
||||||
UnorderedMap<MG_State::GLState::ITextureObject*, WeakPtr<MG_State::GLState::ITextureObject>> m_aliveObjects;
|
// Active only between BeginDrawSyncScope/EndDrawSyncScope; identities of
|
||||||
UnorderedMap<MG_State::GLState::ITextureObject*, TextureResource> m_textureResources;
|
// textures already fully synced in the current draw (small N -> flat scan).
|
||||||
|
Bool m_drawSyncScopeActive = false;
|
||||||
|
// Per-draw sync memo: the identity plus the resolved resource pointer. The pointer is stable
|
||||||
|
// across rehash in the node-based m_textureResources and stays valid for the draw (a texture
|
||||||
|
// synced this draw is alive and is not erased mid-draw), so a repeat sync of the same texture
|
||||||
|
// returns the resource without re-hashing the identity into m_textureResources.
|
||||||
|
struct DrawSyncedTexture {
|
||||||
|
TextureIdentity identity;
|
||||||
|
TextureResource* resource = nullptr;
|
||||||
|
};
|
||||||
|
Vector<DrawSyncedTexture> m_drawSyncedThisDraw;
|
||||||
|
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||||
|
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
||||||
|
Vector<Vector<TextureResource>> m_deferredReleases;
|
||||||
|
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // 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>
|
||||||
@@ -86,6 +87,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 +108,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class VulkanRenderer {
|
class VulkanRenderer : public IBufferCopyCommandProvider, public FrameContext::IRecordingObserver {
|
||||||
public:
|
public:
|
||||||
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
|
||||||
~VulkanRenderer();
|
~VulkanRenderer();
|
||||||
@@ -109,23 +116,141 @@ 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;
|
||||||
|
|
||||||
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||||
|
const DrawCmdParam& drawParams,
|
||||||
const IndexBufferView* pIndexBufferView = nullptr);
|
const IndexBufferView* pIndexBufferView = nullptr);
|
||||||
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 ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
|
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||||
|
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||||
|
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
|
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||||
GLbitfield mask, GLenum filter);
|
GLbitfield mask, GLenum filter);
|
||||||
|
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFbo,
|
||||||
|
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFbo,
|
||||||
|
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||||
|
GLbitfield mask, GLenum filter);
|
||||||
|
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||||
|
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||||
|
void CopyImageSubData(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||||
|
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||||
|
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||||
|
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||||
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
||||||
|
void GenerateMipmap(GLenum target);
|
||||||
|
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||||
|
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
|
||||||
|
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
||||||
|
GLsizei width, GLsizei height, GLenum destinationFormat,
|
||||||
|
GLenum destinationType, SizeT destinationRowStride,
|
||||||
|
Uint8* destinationPixels);
|
||||||
|
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||||
|
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||||
|
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
||||||
|
GLsizei bufSize, GLvoid* pixels);
|
||||||
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
|
void MemoryBarrier(GLbitfield barriers);
|
||||||
|
static VkMemoryBarrier BuildMemoryBarrierForGlBarriers(GLbitfield barriers);
|
||||||
void DrawArrays(const DrawCmd& payload);
|
void 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,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
void Present();
|
void Present();
|
||||||
|
|
||||||
const PhysicalDevice& GetPhysicalDevice() const;
|
const PhysicalDevice& GetPhysicalDevice() const;
|
||||||
|
VkInstance GetInstance() const;
|
||||||
Bool IsDrawIndirectCountExtensionEnabled() const;
|
Bool IsDrawIndirectCountExtensionEnabled() const;
|
||||||
|
|
||||||
|
// GL fence support, expressed in queue-submission indices backed by
|
||||||
|
// real VkFences. A GL fence captures GetSyncPointSubmitIndex() at
|
||||||
|
// creation: the index of the submission that will carry the commands
|
||||||
|
// recorded so far (m_submitCounter + 1 while work is pending, or
|
||||||
|
// m_submitCounter when nothing has been recorded since the last
|
||||||
|
// submit). It is signaled once that submission's fence is observed
|
||||||
|
// signaled - unlike the frame-serial heuristic, this makes fences
|
||||||
|
// signal as soon as the GPU actually finishes, which MC 1.21.5's
|
||||||
|
// fence-paced ring buffers rely on to recycle their space.
|
||||||
|
Uint64 GetSyncPointSubmitIndex() const;
|
||||||
|
// Non-blocking: polls outstanding submission fences and reports
|
||||||
|
// whether every submission up to `submitIndex` has completed.
|
||||||
|
Bool IsSubmitIndexComplete(Uint64 submitIndex);
|
||||||
|
// Submits the commands recorded so far without waiting (GL flush).
|
||||||
|
// Recording restarts lazily on a fresh command buffer; the submitted
|
||||||
|
// one is retired until the frame slot's fence is next waited. Returns
|
||||||
|
// true when a submission was made.
|
||||||
|
Bool FlushPendingCommands();
|
||||||
|
// Flush gated on usefulness: only flushes when `submitIndex` is still
|
||||||
|
// unsubmitted, so poll loops on already-submitted fences do not split
|
||||||
|
// the frame's render pass (a full tile load/store on TBDR GPUs).
|
||||||
|
Bool FlushForSyncPoint(Uint64 submitIndex);
|
||||||
|
// Blocking wait for a submission index with a nanosecond timeout.
|
||||||
|
// When the index is still unsubmitted and flushIfPending is set, the
|
||||||
|
// pending commands are flushed first so the wait can make progress.
|
||||||
|
Bool WaitForSubmitIndex(Uint64 submitIndex, Uint64 timeoutNs, Bool flushIfPending);
|
||||||
|
|
||||||
|
// Frame-serial completion, still used by the timer-query paths (their
|
||||||
|
// records are bucketed per frame slot).
|
||||||
|
Bool IsFrameSerialComplete(Uint64 serial) const;
|
||||||
|
// Blocking wait for a submitted serial. Returns false when the serial
|
||||||
|
// cannot complete without further submissions (it belongs to the
|
||||||
|
// current, not-yet-presented frame) or when the wait failed.
|
||||||
|
Bool WaitForFrameSerial(Uint64 serial, Uint64 timeoutNs);
|
||||||
|
|
||||||
|
// GPU timer queries, backing the GL_TIME_ELAPSED / GL_TIMESTAMP
|
||||||
|
// frontend. Timestamp support (queue timestampValidBits > 0 and a
|
||||||
|
// non-zero timestampPeriod) is cached at device creation.
|
||||||
|
Bool IsTimerQuerySupported() const;
|
||||||
|
// The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is
|
||||||
|
// honored rather than accepted-and-ignored.
|
||||||
|
Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; }
|
||||||
|
// Ensures the frame command buffer is recording (same lazy pattern as
|
||||||
|
// SetupDraw) and writes a bottom-of-pipe timestamp into the current
|
||||||
|
// frame's pool. Null when unsupported or the pool is exhausted.
|
||||||
|
SharedPtr<VkTimerQueryManager::TimestampRecord> WriteTimerQueryTimestamp();
|
||||||
|
// Non-blocking: true once the record's raw ticks are on the CPU
|
||||||
|
// (harvests the slot once its frame serial has completed).
|
||||||
|
Bool IsTimerQueryResultReady(VkTimerQueryManager::TimestampRecord& record);
|
||||||
|
// Blocking wait, mirroring ClientWaitSync's caveat: a record written
|
||||||
|
// this frame cannot complete until Present submits the commands, so
|
||||||
|
// this returns false (result reads as 0) instead of deadlocking.
|
||||||
|
Bool WaitForTimerQueryResult(VkTimerQueryManager::TimestampRecord& record);
|
||||||
|
Uint64 GetTimerQueryElapsedNs(const VkTimerQueryManager::TimestampRecord& begin,
|
||||||
|
const VkTimerQueryManager::TimestampRecord& end) const;
|
||||||
|
Uint64 GetTimerQueryTimestampNs(const VkTimerQueryManager::TimestampRecord& record) const;
|
||||||
|
|
||||||
|
void RequestSwapchainResize(Uint32 width, Uint32 height);
|
||||||
void RecreateSwapchain();
|
void RecreateSwapchain();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -146,8 +271,84 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 samplerBinding = 0;
|
Uint32 samplerBinding = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DepthMipmapResources {
|
||||||
|
SharedPtr<MG_State::GLState::ProgramObject> program;
|
||||||
|
Int srcRectLocation = -1;
|
||||||
|
Int dstRectLocation = -1;
|
||||||
|
Int surfaceTransformLocation = -1;
|
||||||
|
Int srcTexelSizeLocation = -1;
|
||||||
|
Uint32 samplerBinding = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeferredDepthMipmapCleanup {
|
||||||
|
Vector<VkImageView> imageViews;
|
||||||
|
Vector<VkFramebuffer> framebuffers;
|
||||||
|
Vector<VkRenderPass> renderPasses;
|
||||||
|
Vector<VkPipeline> pipelines;
|
||||||
|
};
|
||||||
|
|
||||||
|
void QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload);
|
||||||
|
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer,
|
||||||
|
const ClearAttachmentPayload& clearPayload);
|
||||||
|
void RecordScissoredClearBuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||||
|
GLenum buffer, GLint drawbuffer,
|
||||||
|
const ClearAttachmentPayload& clearPayload,
|
||||||
|
const VkClearRect& clearRect);
|
||||||
|
|
||||||
|
// ---- Submission fence tracking (GL sync objects) ----
|
||||||
|
// One record per vkQueueSubmit still in flight, in ascending submit
|
||||||
|
// order. Present/readback submissions reference the frame slot's
|
||||||
|
// fence (not pool-owned); mid-frame flushes use pooled fences that are
|
||||||
|
// recycled once their submission is observed complete.
|
||||||
|
// Not thread-safe: like the rest of the renderer, the tracker relies
|
||||||
|
// on GL calls being serialized (launchers migrate the context across
|
||||||
|
// threads, but calls never run concurrently), so sync-object polls
|
||||||
|
// may mutate it without locking.
|
||||||
|
struct SubmitRecord {
|
||||||
|
Uint64 submitIndex = 0;
|
||||||
|
// Buffer-manager frame serial the submission was made under; its
|
||||||
|
// completion raises the completed-serial floor (timer queries and
|
||||||
|
// buffer busy-tracking live in frame-serial space).
|
||||||
|
Uint64 frameSerial = 0;
|
||||||
|
VkFence fence = VK_NULL_HANDLE;
|
||||||
|
Bool pooledFence = false;
|
||||||
|
};
|
||||||
|
// Registers a submission that vkQueueSubmit just made with `fence`.
|
||||||
|
// Invariant: every graphics-queue submission that outlives its call
|
||||||
|
// site must be registered so GL fences observe it. Exempt are the
|
||||||
|
// texture-upload/preserve submits in VkTextureManager, which
|
||||||
|
// vkWaitForFences inline before returning.
|
||||||
|
void RegisterSubmit(VkFence fence, Bool pooledFence);
|
||||||
|
// Builds the submit packet for the frame's pending command buffer
|
||||||
|
// (consuming the acquire semaphore on the slot's first submission),
|
||||||
|
// submits it with `fence`, and registers the submission. On failure
|
||||||
|
// the frame state is left untouched. Shared by the mid-frame flush
|
||||||
|
// and the readback path so the semaphore-consumption invariant lives
|
||||||
|
// in one place.
|
||||||
|
Bool SubmitPendingCommandBuffer(FrameContext::FrameData& frame, VkFence fence, Bool pooledFence);
|
||||||
|
// Polls in-flight submission fences (prefix order) and advances the
|
||||||
|
// completed counter past every fence observed signaled.
|
||||||
|
void RefreshCompletedSubmits();
|
||||||
|
// All submissions up to `submitIndex` are known complete (their fence
|
||||||
|
// was waited or the device was idled); drops their records and
|
||||||
|
// recycles pooled fences.
|
||||||
|
void OnSubmitsCompletedUpTo(Uint64 submitIndex);
|
||||||
|
VkFence AcquirePooledSubmitFence();
|
||||||
|
void DestroySubmitFencePool();
|
||||||
|
Bool HasPendingRecordedWork() const;
|
||||||
|
|
||||||
|
Vector<SubmitRecord> m_inFlightSubmits;
|
||||||
|
Vector<VkFence> m_freeSubmitFences;
|
||||||
|
Uint64 m_submitCounter = 0;
|
||||||
|
Uint64 m_completedSubmitCounter = 0;
|
||||||
|
|
||||||
NativeWindowType m_window = 0;
|
NativeWindowType m_window = 0;
|
||||||
|
void* m_platformDisplay = nullptr;
|
||||||
|
void* m_platformLibrary = nullptr;
|
||||||
|
void* m_platformCloseDisplay = nullptr;
|
||||||
VulkanRendererConfig m_config;
|
VulkanRendererConfig m_config;
|
||||||
|
Bool m_swapchainResizeRequested = false;
|
||||||
|
|
||||||
// Vulkan objects
|
// Vulkan objects
|
||||||
Bool m_validationLayersEnabled = false;
|
Bool m_validationLayersEnabled = false;
|
||||||
@@ -164,6 +365,27 @@ 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,
|
||||||
@@ -173,12 +395,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkBufferManager m_bufferManager;
|
VkBufferManager m_bufferManager;
|
||||||
Vector<const MG_State::GLState::BufferObject*> m_transientVertexIndexBuffersThisFrame;
|
|
||||||
|
|
||||||
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.
|
||||||
|
Bool m_lastPipelineValid = false;
|
||||||
|
GLenum m_lastPipelineMode = 0;
|
||||||
|
Uint64 m_lastPipelineProgramHash = 0;
|
||||||
|
Uint64 m_lastPipelineVertexInputHash = 0;
|
||||||
|
Uint64 m_lastPipelineRenderPassHash = 0;
|
||||||
|
Uint m_lastPipelineRenderStateVersion = 0;
|
||||||
|
ProgramFactory::CompileOptionFlags m_lastPipelineTransformFlags = {};
|
||||||
|
VkPipeline m_lastPipelineResult = VK_NULL_HANDLE;
|
||||||
|
UnorderedMap<ProgramFactory::HashType, VkPipeline> m_computePipelines;
|
||||||
UniquePtr<ProgramFactory> m_programFactory;
|
UniquePtr<ProgramFactory> m_programFactory;
|
||||||
UniquePtr<UniformManager> m_uniformManager;
|
UniquePtr<UniformManager> m_uniformManager;
|
||||||
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
|
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
|
||||||
@@ -186,7 +420,76 @@ 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;
|
||||||
|
Vector<DeferredDepthMipmapCleanup> m_deferredDepthMipmapCleanup;
|
||||||
|
|
||||||
|
// Skip the per-draw CollectSampledTextures walk (~5% of the render thread) when the sampled
|
||||||
|
// texture SET is provably unchanged from the previous draw: same program (lifetime id +
|
||||||
|
// backend-state version, which covers sampler-uniform reassignment / relink) and transform
|
||||||
|
// flags, and no texture bind/unbind/delete since (GetTextureBindGeneration). On a hit,
|
||||||
|
// m_sampledTexturesScratch still holds the previous draw's list and steps 2-4 (feedback /
|
||||||
|
// layout probe / transition) re-run on it, so layout correctness is unaffected - only the GL
|
||||||
|
// walk is skipped. The program lifetime id (never reused, unlike the GL name) and the
|
||||||
|
// monotonic bind generation make the key ABA-proof; the per-command-buffer reset is a cheap
|
||||||
|
// belt-and-suspenders.
|
||||||
|
Bool m_lastSampledSetValid = false;
|
||||||
|
Uint64 m_lastSampledSetProgramLifetimeId = 0;
|
||||||
|
Uint32 m_lastSampledSetProgramVersion = 0;
|
||||||
|
ProgramFactory::CompileOptionFlags m_lastSampledSetTransformFlags = {};
|
||||||
|
Uint64 m_lastSampledSetBindGeneration = 0;
|
||||||
|
|
||||||
|
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
||||||
|
// draw call and must not allocate.
|
||||||
|
Vector<MG_State::GLState::ITextureObject*> m_sampledTexturesScratch;
|
||||||
|
Vector<MG_State::GLState::ITextureObject*> m_storageImageTexturesScratch;
|
||||||
|
Vector<VkBuffer> m_vertexBuffersScratch;
|
||||||
|
Vector<VkDeviceSize> m_vertexOffsetsScratch;
|
||||||
|
Vector<VkVertexInputAttributeDescription> m_patchedAttributesScratch;
|
||||||
|
Vector<Float> m_vertexConversionScratch;
|
||||||
|
Vector<Uint8> m_vertexRepackScratch;
|
||||||
|
|
||||||
|
struct ConvertedVertexStreamKey {
|
||||||
|
const MG_State::GLState::BufferObject* buffer = nullptr;
|
||||||
|
Uint64 changeSerial = 0;
|
||||||
|
SizeT baseOffset = 0;
|
||||||
|
Uint32 sourceStride = 0;
|
||||||
|
DataType type = DataType::Float32;
|
||||||
|
Int size = 0;
|
||||||
|
Bool normalized = false;
|
||||||
|
Bool isInteger = false;
|
||||||
|
VertexInputStateFactory::VertexStreamConversion conversion =
|
||||||
|
VertexInputStateFactory::VertexStreamConversion::None;
|
||||||
|
|
||||||
|
Bool operator==(const ConvertedVertexStreamKey& other) const {
|
||||||
|
return buffer == other.buffer && changeSerial == other.changeSerial &&
|
||||||
|
baseOffset == other.baseOffset && sourceStride == other.sourceStride &&
|
||||||
|
type == other.type && size == other.size && normalized == other.normalized &&
|
||||||
|
isInteger == other.isInteger && conversion == other.conversion;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConvertedVertexStreamKeyHash {
|
||||||
|
SizeT operator()(const ConvertedVertexStreamKey& key) const {
|
||||||
|
SizeT hash = std::hash<const void*>{}(key.buffer);
|
||||||
|
auto combine = [&hash](SizeT value) {
|
||||||
|
hash ^= value + static_cast<SizeT>(0x9e3779b97f4a7c15ull) + (hash << 6) + (hash >> 2);
|
||||||
|
};
|
||||||
|
combine(std::hash<Uint64>{}(key.changeSerial));
|
||||||
|
combine(std::hash<SizeT>{}(key.baseOffset));
|
||||||
|
combine(std::hash<Uint32>{}(key.sourceStride));
|
||||||
|
combine(std::hash<Uint32>{}(static_cast<Uint32>(key.type)));
|
||||||
|
combine(std::hash<Int>{}(key.size));
|
||||||
|
combine(std::hash<Bool>{}(key.normalized));
|
||||||
|
combine(std::hash<Bool>{}(key.isInteger));
|
||||||
|
combine(std::hash<Uint32>{}(static_cast<Uint32>(key.conversion)));
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
UnorderedMap<ConvertedVertexStreamKey, BufferSlice, ConvertedVertexStreamKeyHash>
|
||||||
|
m_convertedVertexStreams;
|
||||||
|
|
||||||
void CreateInstance();
|
void CreateInstance();
|
||||||
VkResult SetupDebugMessenger();
|
VkResult SetupDebugMessenger();
|
||||||
@@ -203,15 +506,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkPipeline GetOrCreatePipeline(
|
VkPipeline GetOrCreatePipeline(
|
||||||
GLenum mode,
|
GLenum mode,
|
||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
|
ProgramFactory::CompileOptionFlags transformFlags,
|
||||||
const MG_State::GLState::VertexArrayObject& vao,
|
const MG_State::GLState::VertexArrayObject& vao,
|
||||||
const RenderPassEntry& renderPassEntry);
|
const RenderPassEntry& renderPassEntry);
|
||||||
|
VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj);
|
||||||
|
void DestroyComputePipelines();
|
||||||
|
Bool PrepareStorageImageTextures(
|
||||||
|
VkCommandBuffer commandBuffer,
|
||||||
|
const MG_State::GLState::ProgramObject& program,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj);
|
||||||
|
|
||||||
Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao);
|
Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao,
|
||||||
|
const ProgramFactory::VkProgramObject& programObj,
|
||||||
|
const DrawCmdParam& drawParams, Bool indexedDraw);
|
||||||
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);
|
||||||
Bool InitializeBlitResources();
|
Bool InitializeBlitResources();
|
||||||
|
Bool InitializeDepthMipmapResources();
|
||||||
void ShutdownBlitResources();
|
void ShutdownBlitResources();
|
||||||
|
void ShutdownDepthMipmapResources();
|
||||||
|
void CollectDeferredDepthMipmapCleanup(Uint32 frameIndex);
|
||||||
|
void DestroyDeferredDepthMipmapCleanup();
|
||||||
Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame,
|
Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame,
|
||||||
MG_State::GLState::FramebufferObject& readFbo,
|
MG_State::GLState::FramebufferObject& readFbo,
|
||||||
MG_State::GLState::FramebufferObject& drawFbo,
|
MG_State::GLState::FramebufferObject& drawFbo,
|
||||||
@@ -221,6 +538,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||||
MG_State::GLState::ITextureObject& texture);
|
MG_State::GLState::ITextureObject& texture);
|
||||||
VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry);
|
VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry);
|
||||||
|
Bool GenerateDepthMipmapWithShader(FrameContext::FrameData& frame,
|
||||||
|
MG_State::GLState::ITextureObject& texture,
|
||||||
|
VkTextureManager::TextureResource& resource,
|
||||||
|
Uint32 baseMipLevel,
|
||||||
|
Uint32 generateMipLevelCount,
|
||||||
|
const IntVec3& storageBaseTexelSize,
|
||||||
|
VkImageLayout originalLayout,
|
||||||
|
VkImageLayout finalLayout);
|
||||||
|
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
||||||
|
|
||||||
void ShutdownSwapchain();
|
void ShutdownSwapchain();
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,59 @@
|
|||||||
|
|
||||||
#include "VulkanRendererConfig.h"
|
#include "VulkanRendererConfig.h"
|
||||||
|
|
||||||
|
#define ENUM_STR_CASE(c) case c: return #c;
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
inline const char* VkResultToString(VkResult result) {
|
||||||
|
switch (result) {
|
||||||
|
ENUM_STR_CASE(VK_SUCCESS)
|
||||||
|
ENUM_STR_CASE(VK_NOT_READY)
|
||||||
|
ENUM_STR_CASE(VK_TIMEOUT)
|
||||||
|
ENUM_STR_CASE(VK_EVENT_SET)
|
||||||
|
ENUM_STR_CASE(VK_EVENT_RESET)
|
||||||
|
ENUM_STR_CASE(VK_INCOMPLETE)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_OUT_OF_HOST_MEMORY)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_OUT_OF_DEVICE_MEMORY)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_INITIALIZATION_FAILED)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_DEVICE_LOST)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_MEMORY_MAP_FAILED)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_LAYER_NOT_PRESENT)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_EXTENSION_NOT_PRESENT)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_FEATURE_NOT_PRESENT)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_INCOMPATIBLE_DRIVER)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_TOO_MANY_OBJECTS)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_FORMAT_NOT_SUPPORTED)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_FRAGMENTED_POOL)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_UNKNOWN)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_OUT_OF_POOL_MEMORY)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_INVALID_EXTERNAL_HANDLE)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_FRAGMENTATION)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS)
|
||||||
|
ENUM_STR_CASE(VK_PIPELINE_COMPILE_REQUIRED)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_SURFACE_LOST_KHR)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR)
|
||||||
|
ENUM_STR_CASE(VK_SUBOPTIMAL_KHR)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_OUT_OF_DATE_KHR)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_VALIDATION_FAILED_EXT)
|
||||||
|
ENUM_STR_CASE(VK_ERROR_INVALID_SHADER_NV)
|
||||||
|
default:
|
||||||
|
return "VK_RESULT_UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|
||||||
#define VK_VERIFY(expr, ...) \
|
#define VK_VERIFY(expr, ...) \
|
||||||
do { \
|
do { \
|
||||||
VkResult _vk_verify_result = (expr); \
|
VkResult _vk_verify_result = (expr); \
|
||||||
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _vk_verify_result, __FILE__, __LINE__); \
|
if (_vk_verify_result != VK_SUCCESS) { \
|
||||||
|
MGLOG_F("Vulkan error %s (%d) at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, \
|
||||||
|
MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), \
|
||||||
|
_vk_verify_result, __FILE__, __LINE__); \
|
||||||
|
} \
|
||||||
|
MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %s (%d) at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), _vk_verify_result, __FILE__, __LINE__); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define ENUM_STR_CASE(c) case c: return #c;
|
|
||||||
|
|
||||||
#define XXHASH_VERIFY(expr, ...) \
|
#define XXHASH_VERIFY(expr, ...) \
|
||||||
do { \
|
do { \
|
||||||
XXH_errorcode _xxh_verify_result = (expr); \
|
XXH_errorcode _xxh_verify_result = (expr); \
|
||||||
|
|||||||
@@ -11,10 +11,22 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
struct VulkanRendererConfig {
|
struct VulkanRendererConfig {
|
||||||
Uint32 MaxFramesInFlight = 2;
|
// Fallback CPU pipeline depth used when the MOBILEGL_MAGMA_FRAMESINFLIGHT env var is
|
||||||
|
// unset/invalid. A deeper pipeline lets the CPU run further ahead of the GPU, hiding
|
||||||
|
// per-frame GPU-completion latency. Whatever value is chosen (env or this fallback) is
|
||||||
|
// only a request: VulkanRenderer::Initialize clamps it down to the surface's maxImageCount
|
||||||
|
// (and never below 2), since not every driver allows that many swapchain images.
|
||||||
|
Uint32 MaxFramesInFlight = 3;
|
||||||
String AppName = "MobileGL-VulkanRenderer";
|
String AppName = "MobileGL-VulkanRenderer";
|
||||||
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
|
||||||
Bool EnableValidationLayers = true;
|
Bool EnableValidationLayers = true;
|
||||||
|
#else
|
||||||
|
Bool EnableValidationLayers = false;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ target_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)
|
||||||
@@ -17,3 +17,4 @@ target_link_libraries(
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_test(NAME ProgramBench COMMAND ProgramBench --benchmark_counters_tabular=true)
|
add_test(NAME ProgramBench COMMAND ProgramBench --benchmark_counters_tabular=true)
|
||||||
|
set_tests_properties(ProgramBench PROPERTIES LABELS benchmark)
|
||||||
@@ -0,0 +1,700 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#include "CGLImpl.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#include "../EGLImpl/EGLImpl.h"
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Impl::CGLImpl {
|
||||||
|
namespace {
|
||||||
|
struct PixelFormatObject {
|
||||||
|
Uint32 RetainCount = 1;
|
||||||
|
Bool DoubleBuffer = true;
|
||||||
|
GLint ColorSize = 24;
|
||||||
|
GLint AlphaSize = 8;
|
||||||
|
GLint DepthSize = 24;
|
||||||
|
GLint StencilSize = 8;
|
||||||
|
GLint SampleBuffers = 0;
|
||||||
|
GLint Samples = 0;
|
||||||
|
GLint Profile = kCGLOGLPVersion_3_2_Core;
|
||||||
|
GLint RendererId = 0x4d474c;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ContextObject {
|
||||||
|
Uint32 RetainCount = 1;
|
||||||
|
CGLPixelFormatObj PixelFormat = nullptr;
|
||||||
|
CGLContextObj Share = nullptr;
|
||||||
|
EGLDisplay Display = EGL_NO_DISPLAY;
|
||||||
|
EGLConfig Config = nullptr;
|
||||||
|
EGLContext Context = EGL_NO_CONTEXT;
|
||||||
|
EGLSurface Surface = EGL_NO_SURFACE;
|
||||||
|
void* NSObject = nullptr;
|
||||||
|
void* View = nullptr;
|
||||||
|
void* MetalLayer = nullptr;
|
||||||
|
GLint SwapInterval = 1;
|
||||||
|
GLint VirtualScreen = 0;
|
||||||
|
GLint SurfaceBackingSize[2] = {0, 0};
|
||||||
|
Bool HasDrawable = false;
|
||||||
|
Bool Locked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::recursive_mutex& RegistryMutex() {
|
||||||
|
static auto* mutex = new std::recursive_mutex();
|
||||||
|
return *mutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint64& NextPixelFormatHandle() {
|
||||||
|
static auto* handle = new Uint64(1);
|
||||||
|
return *handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint64& NextContextHandle() {
|
||||||
|
static auto* handle = new Uint64(1);
|
||||||
|
return *handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnorderedMap<CGLPixelFormatObj, PixelFormatObject>& PixelFormats() {
|
||||||
|
static auto* formats = new UnorderedMap<CGLPixelFormatObj, PixelFormatObject>();
|
||||||
|
return *formats;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnorderedMap<CGLContextObj, ContextObject>& Contexts() {
|
||||||
|
static auto* contexts = new UnorderedMap<CGLContextObj, ContextObject>();
|
||||||
|
return *contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnorderedMap<std::thread::id, CGLContextObj>& CurrentContexts() {
|
||||||
|
static auto* contexts = new UnorderedMap<std::thread::id, CGLContextObj>();
|
||||||
|
return *contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLPixelFormatObj EncodePixelFormat(Uint64 handle) {
|
||||||
|
return reinterpret_cast<CGLPixelFormatObj>(static_cast<SizeT>(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLContextObj EncodeContext(Uint64 handle) {
|
||||||
|
return reinterpret_cast<CGLContextObj>(static_cast<SizeT>(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread::id CurrentThreadKey() {
|
||||||
|
return std::this_thread::get_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool AttributeHasValue(CGLPixelFormatAttribute attrib) {
|
||||||
|
switch (attrib) {
|
||||||
|
case kCGLPFAColorSize:
|
||||||
|
case kCGLPFAAlphaSize:
|
||||||
|
case kCGLPFADepthSize:
|
||||||
|
case kCGLPFAStencilSize:
|
||||||
|
case kCGLPFASampleBuffers:
|
||||||
|
case kCGLPFASamples:
|
||||||
|
case kCGLPFARendererID:
|
||||||
|
case kCGLPFADisplayMask:
|
||||||
|
case kCGLPFAOpenGLProfile:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyPixelFormatAttribute(PixelFormatObject& pixelFormat,
|
||||||
|
CGLPixelFormatAttribute attrib,
|
||||||
|
GLint value) {
|
||||||
|
switch (attrib) {
|
||||||
|
case kCGLPFADoubleBuffer:
|
||||||
|
pixelFormat.DoubleBuffer = true;
|
||||||
|
break;
|
||||||
|
case kCGLPFAColorSize:
|
||||||
|
pixelFormat.ColorSize = value;
|
||||||
|
break;
|
||||||
|
case kCGLPFAAlphaSize:
|
||||||
|
pixelFormat.AlphaSize = value;
|
||||||
|
break;
|
||||||
|
case kCGLPFADepthSize:
|
||||||
|
pixelFormat.DepthSize = value;
|
||||||
|
break;
|
||||||
|
case kCGLPFAStencilSize:
|
||||||
|
pixelFormat.StencilSize = value;
|
||||||
|
break;
|
||||||
|
case kCGLPFASampleBuffers:
|
||||||
|
pixelFormat.SampleBuffers = value;
|
||||||
|
break;
|
||||||
|
case kCGLPFASamples:
|
||||||
|
pixelFormat.Samples = value;
|
||||||
|
break;
|
||||||
|
case kCGLPFAOpenGLProfile:
|
||||||
|
pixelFormat.Profile = value;
|
||||||
|
break;
|
||||||
|
case kCGLPFARendererID:
|
||||||
|
pixelFormat.RendererId = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool InitEGLContext(ContextObject& object, CGLPixelFormatObj pix, CGLContextObj share) {
|
||||||
|
auto* pixelFormat = [&]() -> PixelFormatObject* {
|
||||||
|
auto& pixelFormats = PixelFormats();
|
||||||
|
auto it = pixelFormats.find(pix);
|
||||||
|
return it == pixelFormats.end() ? nullptr : &it->second;
|
||||||
|
}();
|
||||||
|
if (!pixelFormat) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLDisplay display = EGLImpl::GetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
if (display == EGL_NO_DISPLAY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!EGLImpl::Initialize(display, nullptr, nullptr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EGLImpl::BindAPI(EGL_OPENGL_API);
|
||||||
|
|
||||||
|
const EGLint attribs[] = {
|
||||||
|
EGL_RED_SIZE, 8,
|
||||||
|
EGL_GREEN_SIZE, 8,
|
||||||
|
EGL_BLUE_SIZE, 8,
|
||||||
|
EGL_ALPHA_SIZE, std::max(pixelFormat->AlphaSize, 0),
|
||||||
|
EGL_DEPTH_SIZE, std::max(pixelFormat->DepthSize, 0),
|
||||||
|
EGL_STENCIL_SIZE, std::max(pixelFormat->StencilSize, 0),
|
||||||
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
|
||||||
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||||
|
EGL_NONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
EGLConfig config = nullptr;
|
||||||
|
EGLint count = 0;
|
||||||
|
if (!EGLImpl::ChooseConfig(display, attribs, &config, 1, &count) || count <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLContext shareContext = EGL_NO_CONTEXT;
|
||||||
|
if (share != nullptr) {
|
||||||
|
auto& contexts = Contexts();
|
||||||
|
auto shareIt = contexts.find(share);
|
||||||
|
if (shareIt == contexts.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
shareContext = shareIt->second.Context;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EGLint contextAttribs[] = {
|
||||||
|
EGL_CONTEXT_MAJOR_VERSION, 3,
|
||||||
|
EGL_CONTEXT_MINOR_VERSION, 3,
|
||||||
|
EGL_NONE,
|
||||||
|
};
|
||||||
|
EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs);
|
||||||
|
if (eglContext == EGL_NO_CONTEXT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
object.Display = display;
|
||||||
|
object.Config = config;
|
||||||
|
object.Context = eglContext;
|
||||||
|
object.PixelFormat = pix;
|
||||||
|
object.Share = share;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextObject* TryGetContext(CGLContextObj ctx) {
|
||||||
|
auto& contexts = Contexts();
|
||||||
|
auto it = contexts.find(ctx);
|
||||||
|
return it == contexts.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContextObject* TryGetContext(CGLContextObj ctx, const std::lock_guard<std::recursive_mutex>&) {
|
||||||
|
auto& contexts = Contexts();
|
||||||
|
auto it = contexts.find(ctx);
|
||||||
|
return it == contexts.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelFormatObject* TryGetPixelFormat(CGLPixelFormatObj pix) {
|
||||||
|
auto& pixelFormats = PixelFormats();
|
||||||
|
auto it = pixelFormats.find(pix);
|
||||||
|
return it == pixelFormats.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError MakeCurrentLocked(CGLContextObj ctx, ContextObject& object) {
|
||||||
|
CurrentContexts()[CurrentThreadKey()] = ctx;
|
||||||
|
if (!object.HasDrawable || object.Surface == EGL_NO_SURFACE) {
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
if (!EGLImpl::MakeCurrent(object.Display, object.Surface, object.Surface, object.Context)) {
|
||||||
|
return kCGLBadState;
|
||||||
|
}
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError RecreateSurfaceLocked(CGLContextObj ctx, ContextObject& object) {
|
||||||
|
if (!object.MetalLayer) {
|
||||||
|
return kCGLBadDrawable;
|
||||||
|
}
|
||||||
|
if (object.Surface != EGL_NO_SURFACE) {
|
||||||
|
EGLImpl::DestroySurface(object.Display, object.Surface);
|
||||||
|
object.Surface = EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
const EGLAttrib attribs[] = {
|
||||||
|
EGL_WIDTH, std::max<GLint>(object.SurfaceBackingSize[0], 1),
|
||||||
|
EGL_HEIGHT, std::max<GLint>(object.SurfaceBackingSize[1], 1),
|
||||||
|
EGL_NONE,
|
||||||
|
};
|
||||||
|
EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(object.Display, object.Config,
|
||||||
|
object.MetalLayer, attribs);
|
||||||
|
if (surface == EGL_NO_SURFACE) {
|
||||||
|
object.HasDrawable = false;
|
||||||
|
return kCGLBadDrawable;
|
||||||
|
}
|
||||||
|
object.Surface = surface;
|
||||||
|
object.HasDrawable = true;
|
||||||
|
return GetCurrentContext() == ctx ? MakeCurrentLocked(ctx, object) : kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError ResizeSurfaceLocked(ContextObject& object) {
|
||||||
|
if (object.Surface == EGL_NO_SURFACE) {
|
||||||
|
return kCGLBadDrawable;
|
||||||
|
}
|
||||||
|
return EGLImpl::ResizePlatformWindowSurface(
|
||||||
|
object.Display, object.Surface,
|
||||||
|
std::max<GLint>(object.SurfaceBackingSize[0], 1),
|
||||||
|
std::max<GLint>(object.SurfaceBackingSize[1], 1))
|
||||||
|
? kCGLNoError
|
||||||
|
: kCGLBadDrawable;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CGLError ChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
if (!pix || !npix) {
|
||||||
|
return kCGLBadAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelFormatObject object;
|
||||||
|
if (attribs) {
|
||||||
|
for (SizeT i = 0; attribs[i] != static_cast<CGLPixelFormatAttribute>(0); ++i) {
|
||||||
|
const auto attrib = attribs[i];
|
||||||
|
GLint value = 1;
|
||||||
|
if (AttributeHasValue(attrib)) {
|
||||||
|
value = static_cast<GLint>(attribs[++i]);
|
||||||
|
}
|
||||||
|
ApplyPixelFormatAttribute(object, attrib, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto handle = EncodePixelFormat(NextPixelFormatHandle()++);
|
||||||
|
PixelFormats()[handle] = object;
|
||||||
|
*pix = handle;
|
||||||
|
*npix = 1;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError DestroyPixelFormat(CGLPixelFormatObj pix) {
|
||||||
|
ReleasePixelFormat(pix);
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError DescribePixelFormat(CGLPixelFormatObj pix, GLint pixNum, CGLPixelFormatAttribute attrib, GLint* value) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
if (!value) {
|
||||||
|
return kCGLBadAddress;
|
||||||
|
}
|
||||||
|
if (pixNum != 0 && pixNum != 1) {
|
||||||
|
return kCGLBadValue;
|
||||||
|
}
|
||||||
|
auto* pixelFormat = TryGetPixelFormat(pix);
|
||||||
|
if (!pixelFormat) {
|
||||||
|
return kCGLBadPixelFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (attrib) {
|
||||||
|
case kCGLPFADoubleBuffer:
|
||||||
|
*value = pixelFormat->DoubleBuffer ? 1 : 0;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFAAccelerated:
|
||||||
|
case kCGLPFAAcceleratedCompute:
|
||||||
|
case kCGLPFASupportsAutomaticGraphicsSwitching:
|
||||||
|
*value = 1;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFAColorSize:
|
||||||
|
*value = pixelFormat->ColorSize;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFAAlphaSize:
|
||||||
|
*value = pixelFormat->AlphaSize;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFADepthSize:
|
||||||
|
*value = pixelFormat->DepthSize;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFAStencilSize:
|
||||||
|
*value = pixelFormat->StencilSize;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFASampleBuffers:
|
||||||
|
*value = pixelFormat->SampleBuffers;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFASamples:
|
||||||
|
*value = pixelFormat->Samples;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFARendererID:
|
||||||
|
*value = pixelFormat->RendererId;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFAOpenGLProfile:
|
||||||
|
*value = pixelFormat->Profile;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLPFAVirtualScreenCount:
|
||||||
|
*value = 1;
|
||||||
|
return kCGLNoError;
|
||||||
|
default:
|
||||||
|
*value = 0;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleasePixelFormat(CGLPixelFormatObj pix) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* pixelFormat = TryGetPixelFormat(pix);
|
||||||
|
if (!pixelFormat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pixelFormat->RetainCount > 1) {
|
||||||
|
--pixelFormat->RetainCount;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PixelFormats().erase(pix);
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLPixelFormatObj RetainPixelFormat(CGLPixelFormatObj pix) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* pixelFormat = TryGetPixelFormat(pix);
|
||||||
|
if (pixelFormat) {
|
||||||
|
++pixelFormat->RetainCount;
|
||||||
|
}
|
||||||
|
return pix;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint GetPixelFormatRetainCount(CGLPixelFormatObj pix) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* pixelFormat = TryGetPixelFormat(pix);
|
||||||
|
return pixelFormat ? pixelFormat->RetainCount : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError CreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
if (!ctx) {
|
||||||
|
return kCGLBadAddress;
|
||||||
|
}
|
||||||
|
if (!TryGetPixelFormat(pix)) {
|
||||||
|
return kCGLBadPixelFormat;
|
||||||
|
}
|
||||||
|
if (share && !TryGetContext(share)) {
|
||||||
|
return kCGLBadMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextObject object;
|
||||||
|
if (!InitEGLContext(object, pix, share)) {
|
||||||
|
return kCGLBadAlloc;
|
||||||
|
}
|
||||||
|
RetainPixelFormat(pix);
|
||||||
|
const auto handle = EncodeContext(NextContextHandle()++);
|
||||||
|
Contexts()[handle] = object;
|
||||||
|
*ctx = handle;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError DestroyContext(CGLContextObj ctx) {
|
||||||
|
ReleaseContext(ctx);
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLContextObj RetainContext(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (object) {
|
||||||
|
++object->RetainCount;
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseContext(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (object->RetainCount > 1) {
|
||||||
|
--object->RetainCount;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (object->Surface != EGL_NO_SURFACE) {
|
||||||
|
EGLImpl::DestroySurface(object->Display, object->Surface);
|
||||||
|
}
|
||||||
|
if (object->Context != EGL_NO_CONTEXT) {
|
||||||
|
EGLImpl::DestroyContext(object->Display, object->Context);
|
||||||
|
}
|
||||||
|
ReleasePixelFormat(object->PixelFormat);
|
||||||
|
auto& currentContexts = CurrentContexts();
|
||||||
|
for (auto it = currentContexts.begin(); it != currentContexts.end();) {
|
||||||
|
if (it->second == ctx) {
|
||||||
|
it = currentContexts.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Contexts().erase(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint GetContextRetainCount(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
return object ? object->RetainCount : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLPixelFormatObj GetPixelFormat(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
return object ? object->PixelFormat : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError SetCurrentContext(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
if (!ctx) {
|
||||||
|
CurrentContexts().erase(CurrentThreadKey());
|
||||||
|
EGLImpl::MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
return MakeCurrentLocked(ctx, *object);
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLContextObj GetCurrentContext() {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto& currentContexts = CurrentContexts();
|
||||||
|
auto it = currentContexts.find(CurrentThreadKey());
|
||||||
|
return it == currentContexts.end() ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
if (!params && pname != kCGLCPReclaimResources) {
|
||||||
|
return kCGLBadAddress;
|
||||||
|
}
|
||||||
|
switch (pname) {
|
||||||
|
case kCGLCPSwapInterval:
|
||||||
|
object->SwapInterval = params[0];
|
||||||
|
EGLImpl::SwapInterval(object->Display, object->SwapInterval);
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLCPSurfaceBackingSize:
|
||||||
|
{
|
||||||
|
const GLint width = std::max<GLint>(params[0], 1);
|
||||||
|
const GLint height = std::max<GLint>(params[1], 1);
|
||||||
|
if (object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height) {
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
object->SurfaceBackingSize[0] = width;
|
||||||
|
object->SurfaceBackingSize[1] = height;
|
||||||
|
if (object->MetalLayer && object->Surface != EGL_NO_SURFACE) {
|
||||||
|
return ResizeSurfaceLocked(*object);
|
||||||
|
}
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
case kCGLCPSurfaceOpacity:
|
||||||
|
case kCGLCPSurfaceOrder:
|
||||||
|
case kCGLCPMPSwapsInFlight:
|
||||||
|
case kCGLCPReclaimResources:
|
||||||
|
return kCGLNoError;
|
||||||
|
default:
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
if (!params) {
|
||||||
|
return kCGLBadAddress;
|
||||||
|
}
|
||||||
|
switch (pname) {
|
||||||
|
case kCGLCPSwapInterval:
|
||||||
|
params[0] = object->SwapInterval;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLCPSurfaceBackingSize:
|
||||||
|
params[0] = object->SurfaceBackingSize[0];
|
||||||
|
params[1] = object->SurfaceBackingSize[1];
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLCPCurrentRendererID:
|
||||||
|
params[0] = 0x4d474c;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLCPGPUVertexProcessing:
|
||||||
|
case kCGLCPGPUFragmentProcessing:
|
||||||
|
case kCGLCPHasDrawable:
|
||||||
|
params[0] = object->HasDrawable ? 1 : 0;
|
||||||
|
return kCGLNoError;
|
||||||
|
case kCGLCPMPSwapsInFlight:
|
||||||
|
params[0] = 1;
|
||||||
|
return kCGLNoError;
|
||||||
|
default:
|
||||||
|
params[0] = 0;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError UpdateContext(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
return TryGetContext(ctx) ? kCGLNoError : kCGLBadContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError ClearDrawable(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
if (object->Surface != EGL_NO_SURFACE) {
|
||||||
|
EGLImpl::DestroySurface(object->Display, object->Surface);
|
||||||
|
}
|
||||||
|
object->Surface = EGL_NO_SURFACE;
|
||||||
|
object->View = nullptr;
|
||||||
|
object->MetalLayer = nullptr;
|
||||||
|
object->HasDrawable = false;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError FlushDrawable(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
if (!object->HasDrawable || object->Surface == EGL_NO_SURFACE) {
|
||||||
|
return kCGLBadDrawable;
|
||||||
|
}
|
||||||
|
const auto currentError = MakeCurrentLocked(ctx, *object);
|
||||||
|
if (currentError != kCGLNoError) {
|
||||||
|
return currentError;
|
||||||
|
}
|
||||||
|
return EGLImpl::SwapBuffers(object->Display, object->Surface) ? kCGLNoError : kCGLBadDrawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError LockContext(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
object->Locked = true;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError UnlockContext(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
object->Locked = false;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetVersion(GLint* majorvers, GLint* minorvers) {
|
||||||
|
if (majorvers) {
|
||||||
|
*majorvers = 1;
|
||||||
|
}
|
||||||
|
if (minorvers) {
|
||||||
|
*minorvers = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ErrorString(CGLError error) {
|
||||||
|
switch (error) {
|
||||||
|
case kCGLNoError:
|
||||||
|
return "no error";
|
||||||
|
case kCGLBadAttribute:
|
||||||
|
return "invalid pixel format attribute";
|
||||||
|
case kCGLBadPixelFormat:
|
||||||
|
return "invalid pixel format";
|
||||||
|
case kCGLBadContext:
|
||||||
|
return "invalid context";
|
||||||
|
case kCGLBadDrawable:
|
||||||
|
return "invalid drawable";
|
||||||
|
case kCGLBadState:
|
||||||
|
return "invalid context state";
|
||||||
|
case kCGLBadValue:
|
||||||
|
return "invalid numerical value";
|
||||||
|
case kCGLBadMatch:
|
||||||
|
return "invalid share context";
|
||||||
|
case kCGLBadAddress:
|
||||||
|
return "invalid pointer";
|
||||||
|
case kCGLBadAlloc:
|
||||||
|
return "invalid memory allocation";
|
||||||
|
default:
|
||||||
|
return "unknown CGL error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer, GLint width, GLint height) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (!object) {
|
||||||
|
return kCGLBadContext;
|
||||||
|
}
|
||||||
|
if (!metalLayer) {
|
||||||
|
return kCGLBadDrawable;
|
||||||
|
}
|
||||||
|
width = std::max<GLint>(width, 1);
|
||||||
|
height = std::max<GLint>(height, 1);
|
||||||
|
const Bool sameSize = object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height;
|
||||||
|
object->SurfaceBackingSize[0] = width;
|
||||||
|
object->SurfaceBackingSize[1] = height;
|
||||||
|
if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer && sameSize) {
|
||||||
|
object->View = nsView;
|
||||||
|
object->HasDrawable = true;
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer) {
|
||||||
|
object->View = nsView;
|
||||||
|
object->HasDrawable = true;
|
||||||
|
return ResizeSurfaceLocked(*object);
|
||||||
|
}
|
||||||
|
if (object->Surface != EGL_NO_SURFACE) {
|
||||||
|
EGLImpl::DestroySurface(object->Display, object->Surface);
|
||||||
|
object->Surface = EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
object->View = nsView;
|
||||||
|
object->MetalLayer = metalLayer;
|
||||||
|
const auto recreateError = RecreateSurfaceLocked(ctx, *object);
|
||||||
|
if (recreateError != kCGLNoError) {
|
||||||
|
return recreateError;
|
||||||
|
}
|
||||||
|
return kCGLNoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GetContextNSObject(CGLContextObj ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
return object ? object->NSObject : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetContextNSObject(CGLContextObj ctx, void* nsObject) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
|
||||||
|
auto* object = TryGetContext(ctx);
|
||||||
|
if (object) {
|
||||||
|
object->NSObject = nsObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace MobileGL::MG_Impl::CGLImpl
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/CGLImpl/CGLImpl.h
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#ifndef GL_SILENCE_DEPRECATION
|
||||||
|
#define GL_SILENCE_DEPRECATION
|
||||||
|
#endif
|
||||||
|
#include <OpenGL/OpenGL.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Impl::CGLImpl {
|
||||||
|
CGLError ChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix);
|
||||||
|
CGLError DestroyPixelFormat(CGLPixelFormatObj pix);
|
||||||
|
CGLError DescribePixelFormat(CGLPixelFormatObj pix, GLint pixNum, CGLPixelFormatAttribute attrib, GLint* value);
|
||||||
|
void ReleasePixelFormat(CGLPixelFormatObj pix);
|
||||||
|
CGLPixelFormatObj RetainPixelFormat(CGLPixelFormatObj pix);
|
||||||
|
GLuint GetPixelFormatRetainCount(CGLPixelFormatObj pix);
|
||||||
|
|
||||||
|
CGLError CreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx);
|
||||||
|
CGLError DestroyContext(CGLContextObj ctx);
|
||||||
|
CGLContextObj RetainContext(CGLContextObj ctx);
|
||||||
|
void ReleaseContext(CGLContextObj ctx);
|
||||||
|
GLuint GetContextRetainCount(CGLContextObj ctx);
|
||||||
|
CGLPixelFormatObj GetPixelFormat(CGLContextObj ctx);
|
||||||
|
|
||||||
|
CGLError SetCurrentContext(CGLContextObj ctx);
|
||||||
|
CGLContextObj GetCurrentContext();
|
||||||
|
CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params);
|
||||||
|
CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params);
|
||||||
|
CGLError UpdateContext(CGLContextObj ctx);
|
||||||
|
CGLError ClearDrawable(CGLContextObj ctx);
|
||||||
|
CGLError FlushDrawable(CGLContextObj ctx);
|
||||||
|
CGLError LockContext(CGLContextObj ctx);
|
||||||
|
CGLError UnlockContext(CGLContextObj ctx);
|
||||||
|
void GetVersion(GLint* majorvers, GLint* minorvers);
|
||||||
|
const char* ErrorString(CGLError error);
|
||||||
|
|
||||||
|
CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer, GLint width, GLint height);
|
||||||
|
void* GetContextNSObject(CGLContextObj ctx);
|
||||||
|
void SetContextNSObject(CGLContextObj ctx, void* nsObject);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#include "../CGLImpl.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLChoosePixelFormat(const CGLPixelFormatAttribute* attribs,
|
||||||
|
CGLPixelFormatObj* pix,
|
||||||
|
GLint* npix) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::ChoosePixelFormat(attribs, pix, npix);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLDestroyPixelFormat(CGLPixelFormatObj pix) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::DestroyPixelFormat(pix);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLDescribePixelFormat(CGLPixelFormatObj pix,
|
||||||
|
GLint pix_num,
|
||||||
|
CGLPixelFormatAttribute attrib,
|
||||||
|
GLint* value) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::DescribePixelFormat(pix, pix_num, attrib, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API void CGLReleasePixelFormat(CGLPixelFormatObj pix) {
|
||||||
|
MobileGL::MG_Impl::CGLImpl::ReleasePixelFormat(pix);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLPixelFormatObj CGLRetainPixelFormat(CGLPixelFormatObj pix) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::RetainPixelFormat(pix);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pix) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::GetPixelFormatRetainCount(pix);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLCreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::CreateContext(pix, share, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLDestroyContext(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::DestroyContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLContextObj CGLRetainContext(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::RetainContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API void CGLReleaseContext(CGLContextObj ctx) {
|
||||||
|
MobileGL::MG_Impl::CGLImpl::ReleaseContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API GLuint CGLGetContextRetainCount(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::GetContextRetainCount(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLPixelFormatObj CGLGetPixelFormat(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::GetPixelFormat(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLSetCurrentContext(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::SetCurrentContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLContextObj CGLGetCurrentContext(void) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::GetCurrentContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLSetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::SetParameter(ctx, pname, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLGetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::GetParameter(ctx, pname, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLUpdateContext(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::UpdateContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLClearDrawable(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::ClearDrawable(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLFlushDrawable(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::FlushDrawable(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLLockContext(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::LockContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API CGLError CGLUnlockContext(CGLContextObj ctx) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::UnlockContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API void CGLGetVersion(GLint* majorvers, GLint* minorvers) {
|
||||||
|
MobileGL::MG_Impl::CGLImpl::GetVersion(majorvers, minorvers);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOBILEGL_CGL_API const char* CGLErrorString(CGLError error) {
|
||||||
|
return MobileGL::MG_Impl::CGLImpl::ErrorString(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
|
||||||
|
#include "MG_Impl/GetProcAddress.h"
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct DyldInterposeEntry {
|
||||||
|
const void* Replacement;
|
||||||
|
const void* Replacee;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool IsGLProcName(const char* name) {
|
||||||
|
if (name == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(name, "CGL", 3) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(name, "gl", 2) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid stealing glfw*/glib*/glX*/global application symbols.
|
||||||
|
return name[2] >= 'A' && name[2] <= 'Z' && name[2] != 'X';
|
||||||
|
}
|
||||||
|
|
||||||
|
void* MobileGLDlsym(void* handle, const char* symbol) {
|
||||||
|
if (IsGLProcName(symbol)) {
|
||||||
|
if (void* proc = MobileGL::MG_Impl::GetProcAddress(symbol)) {
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlsym(handle, symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((used)) static const DyldInterposeEntry kMobileGLDyldInterpose[]
|
||||||
|
__attribute__((section("__DATA,__interpose"))) = {
|
||||||
|
{reinterpret_cast<const void*>(MobileGLDlsym), reinterpret_cast<const void*>(dlsym)},
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "../GetProcAddress.h"
|
#include "../GetProcAddress.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 {
|
||||||
@@ -31,14 +33,53 @@ 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(__linux__)
|
||||||
|
return MG_Backend::WindowBackend::X11;
|
||||||
#else
|
#else
|
||||||
return MG_Backend::WindowBackend::Unknown;
|
return MG_Backend::WindowBackend::Unknown;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EGLint GetAttribValue(const EGLint* attribList, EGLint attrib, EGLint defaultValue) {
|
||||||
|
if (!attribList) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) {
|
||||||
|
if (attribList[i] == attrib) {
|
||||||
|
return attribList[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLint GetAttribValueAttrib(const EGLAttrib* attribList, EGLint attrib, EGLint defaultValue) {
|
||||||
|
if (!attribList) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) {
|
||||||
|
if (attribList[i] == attrib) {
|
||||||
|
return static_cast<EGLint>(attribList[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename NativeType>
|
template <typename NativeType>
|
||||||
Bool IsNullNativeHandle(NativeType nativeHandle) {
|
Bool IsNullNativeHandle(NativeType nativeHandle) {
|
||||||
if constexpr (std::is_pointer_v<NativeType>) {
|
if constexpr (std::is_pointer_v<NativeType>) {
|
||||||
@@ -77,25 +118,35 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return EGL_NO_SURFACE;
|
return EGL_NO_SURFACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* backendObject = GetBackendObject(state);
|
|
||||||
if (!backendObject) {
|
|
||||||
MGLOG_E("activeBackendObject not initialized!");
|
|
||||||
return EGL_NO_SURFACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MG_Backend::WindowHandle windowHandle = {
|
const MG_Backend::WindowHandle windowHandle = {
|
||||||
.Backend = DetectWindowBackend(),
|
.Backend = DetectWindowBackend(),
|
||||||
.Handle = ToVoidHandle(window),
|
.Handle = ToVoidHandle(window),
|
||||||
|
.Width = static_cast<Uint32>(std::max<EGLint>(GetAttribValue(attrib_list, EGL_WIDTH, 0), 0)),
|
||||||
|
.Height = static_cast<Uint32>(std::max<EGLint>(GetAttribValue(attrib_list, EGL_HEIGHT, 0), 0)),
|
||||||
};
|
};
|
||||||
if (!backendObject->CreateEGLWindowSurface(windowHandle)) {
|
|
||||||
|
EGLSurface surface = state->CreateWindowSurface(dpy, config, window, attrib_list);
|
||||||
|
if (surface == EGL_NO_SURFACE) {
|
||||||
|
return EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* backendObject = GetBackendObject(state);
|
||||||
|
if (!backendObject) {
|
||||||
|
MGLOG_E("activeBackendObject not initialized!");
|
||||||
|
state->DestroySurface(dpy, surface);
|
||||||
|
return EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
if (!backendObject->CreateEGLWindowSurface(surface, windowHandle)) {
|
||||||
|
state->DestroySurface(dpy, surface);
|
||||||
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
state->SetError(EGL_BAD_NATIVE_WINDOW);
|
||||||
return EGL_NO_SURFACE;
|
return EGL_NO_SURFACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state->CreateWindowSurface(dpy, config, window, attrib_list);
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||||
auto* state = GetState();
|
auto* state = GetState();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
@@ -111,6 +162,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;
|
||||||
}
|
}
|
||||||
@@ -172,6 +224,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
EGLBoolean MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||||
auto* state = GetState();
|
auto* state = GetState();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
@@ -181,17 +234,30 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
const auto oldDraw = state->GetCurrentSurface(EGL_DRAW);
|
const auto oldDraw = state->GetCurrentSurface(EGL_DRAW);
|
||||||
const auto oldRead = state->GetCurrentSurface(EGL_READ);
|
const auto oldRead = state->GetCurrentSurface(EGL_READ);
|
||||||
const auto oldContext = state->GetCurrentContext();
|
const auto oldContext = state->GetCurrentContext();
|
||||||
|
const String threadId = CurrentThreadIdString();
|
||||||
|
|
||||||
|
MGLOG_D("eglMakeCurrent begin thread=%s dpy=%p draw=%p read=%p ctx=%p oldDpy=%p oldDraw=%p oldRead=%p oldCtx=%p",
|
||||||
|
threadId.c_str(), dpy, draw, read, ctx, oldDisplay, oldDraw, oldRead, oldContext);
|
||||||
|
|
||||||
if (!state->MakeCurrent(dpy, draw, read, ctx)) {
|
if (!state->MakeCurrent(dpy, draw, read, ctx)) {
|
||||||
|
const EGLint error = state->ConsumeError();
|
||||||
|
MGLOG_D("eglMakeCurrent rejected by EGLState thread=%s error=0x%04x", threadId.c_str(), error);
|
||||||
|
state->SetError(error);
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bool releaseCurrentRequest =
|
const Bool releaseCurrentRequest =
|
||||||
dpy == EGL_NO_DISPLAY && draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT;
|
||||||
if (releaseCurrentRequest) {
|
if (releaseCurrentRequest) {
|
||||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||||
(void)backendObject->MakeEGLCurrent(dpy, draw, read, ctx);
|
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
|
||||||
|
MGLOG_E("eglMakeCurrent release failed in backend thread=%s", threadId.c_str());
|
||||||
|
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
||||||
|
state->SetError(EGL_BAD_ACCESS);
|
||||||
|
return EGL_FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
MGLOG_D("eglMakeCurrent release succeeded thread=%s", threadId.c_str());
|
||||||
return EGL_TRUE;
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,10 +268,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
|
if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) {
|
||||||
|
MGLOG_E("eglMakeCurrent backend attach failed thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(),
|
||||||
|
dpy, draw, read, ctx);
|
||||||
state->SetError(EGL_BAD_ACCESS);
|
state->SetError(EGL_BAD_ACCESS);
|
||||||
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext);
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
|
MGLOG_D("eglMakeCurrent attach succeeded thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(), dpy, draw,
|
||||||
|
read, ctx);
|
||||||
return EGL_TRUE;
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,11 +288,18 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean DestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
EGLBoolean DestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
||||||
|
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||||
auto* state = GetState();
|
auto* state = GetState();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
return state->DestroySurface(dpy, surface) ? EGL_TRUE : EGL_FALSE;
|
if (!state->DestroySurface(dpy, surface)) {
|
||||||
|
return EGL_FALSE;
|
||||||
|
}
|
||||||
|
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||||
|
backendObject->ReleaseEGLSurface(surface);
|
||||||
|
}
|
||||||
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean Terminate(EGLDisplay dpy) {
|
EGLBoolean Terminate(EGLDisplay dpy) {
|
||||||
@@ -230,7 +307,13 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
return state->TerminateDisplay(dpy) ? EGL_TRUE : EGL_FALSE;
|
if (!state->TerminateDisplay(dpy)) {
|
||||||
|
return EGL_FALSE;
|
||||||
|
}
|
||||||
|
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||||
|
backendObject->ReleaseEGLResources();
|
||||||
|
}
|
||||||
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean ReleaseThread() {
|
EGLBoolean ReleaseThread() {
|
||||||
@@ -238,6 +321,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;
|
||||||
}
|
}
|
||||||
@@ -310,7 +396,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
case EGL_CLIENT_APIS:
|
case EGL_CLIENT_APIS:
|
||||||
return "OpenGL OpenGL_ES";
|
return "OpenGL OpenGL_ES";
|
||||||
case EGL_EXTENSIONS:
|
case EGL_EXTENSIONS:
|
||||||
return "";
|
if (display == EGL_NO_DISPLAY) {
|
||||||
|
return "EGL_EXT_client_extensions "
|
||||||
|
"EGL_EXT_platform_base "
|
||||||
|
"EGL_KHR_platform_base "
|
||||||
|
"EGL_MESA_platform_surfaceless";
|
||||||
|
}
|
||||||
|
return "EGL_KHR_create_context "
|
||||||
|
"EGL_MESA_platform_surfaceless";
|
||||||
default:
|
default:
|
||||||
state->SetError(EGL_BAD_PARAMETER);
|
state->SetError(EGL_BAD_PARAMETER);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -322,7 +415,17 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
return state->SwapInterval(dpy, interval) ? EGL_TRUE : EGL_FALSE;
|
if (!state->SwapInterval(dpy, interval)) {
|
||||||
|
return EGL_FALSE;
|
||||||
|
}
|
||||||
|
// Forward the request to the backend's native presentation path; without this
|
||||||
|
// the app's vsync setting only ever reaches MobileGL's shadow state and the
|
||||||
|
// native surface stays at the driver default (interval 1 = always vsynced).
|
||||||
|
auto* backendObject = GetBackendObject(state);
|
||||||
|
if (backendObject) {
|
||||||
|
backendObject->SetEGLSwapInterval(static_cast<Int>(interval));
|
||||||
|
}
|
||||||
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
||||||
@@ -330,7 +433,24 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_NO_SURFACE;
|
return EGL_NO_SURFACE;
|
||||||
}
|
}
|
||||||
return state->CreatePbufferSurface(dpy, config, attrib_list);
|
const EGLint width = GetAttribValue(attrib_list, EGL_WIDTH, 1);
|
||||||
|
const EGLint height = GetAttribValue(attrib_list, EGL_HEIGHT, 1);
|
||||||
|
EGLSurface surface = state->CreatePbufferSurface(dpy, config, attrib_list);
|
||||||
|
if (surface == EGL_NO_SURFACE) {
|
||||||
|
return EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* backendObject = GetBackendObject(state);
|
||||||
|
if (!backendObject) {
|
||||||
|
return EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
if (!backendObject->CreateEGLPbufferSurface(surface, width, height)) {
|
||||||
|
state->DestroySurface(dpy, surface);
|
||||||
|
state->SetError(EGL_BAD_ALLOC);
|
||||||
|
return EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLBoolean BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
EGLBoolean BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||||
@@ -547,22 +667,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,
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list);
|
EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list);
|
||||||
EGLSurface CreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
EGLSurface CreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
||||||
const EGLAttrib* attrib_list);
|
const EGLAttrib* attrib_list);
|
||||||
|
EGLBoolean ResizePlatformWindowSurface(EGLDisplay dpy, EGLSurface surface, EGLint width, EGLint height);
|
||||||
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
EGLSurface CreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
||||||
const EGLAttrib* attrib_list);
|
const EGLAttrib* attrib_list);
|
||||||
EGLBoolean WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags);
|
EGLBoolean WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags);
|
||||||
|
|||||||
@@ -235,6 +235,14 @@ MOBILEGL_EGL_API EGLDisplay eglGetPlatformDisplay(EGLenum platform, void* native
|
|||||||
return MobileGL::MG_Impl::EGLImpl::GetPlatformDisplay(platform, native_display, attrib_list);
|
return MobileGL::MG_Impl::EGLImpl::GetPlatformDisplay(platform, native_display, attrib_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MOBILEGL_EGL_API EGLDisplay eglGetPlatformDisplayEXT(EGLenum platform, void* native_display,
|
||||||
|
const EGLint* attrib_list) {
|
||||||
|
MGLOG_D("eglGetPlatformDisplayEXT(platform=%u, native_display=%p, attrib_list=%p)", platform, native_display,
|
||||||
|
attrib_list);
|
||||||
|
return MobileGL::MG_Impl::EGLImpl::GetPlatformDisplay(
|
||||||
|
platform, native_display, reinterpret_cast<const EGLAttrib*>(attrib_list));
|
||||||
|
}
|
||||||
|
|
||||||
MOBILEGL_EGL_API EGLSurface eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
MOBILEGL_EGL_API EGLSurface eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
||||||
const EGLAttrib* attrib_list) {
|
const EGLAttrib* attrib_list) {
|
||||||
MGLOG_D("eglCreatePlatformWindowSurface(dpy=%p, config=%p, native_window=%p, attrib_list=%p)", dpy, config,
|
MGLOG_D("eglCreatePlatformWindowSurface(dpy=%p, config=%p, native_window=%p, attrib_list=%p)", dpy, config,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,19 +12,42 @@
|
|||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||||
|
void GetBufferParameteri64v(GLenum target, GLenum pname, GLint64* params);
|
||||||
|
void GetBufferPointerv(GLenum target, GLenum pname, void** params);
|
||||||
GLboolean IsBuffer(GLuint buffer);
|
GLboolean IsBuffer(GLuint buffer);
|
||||||
void DeleteBuffers(GLsizei n, const GLuint* buffers);
|
void DeleteBuffers(GLsizei n, const GLuint* buffers);
|
||||||
void FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length);
|
void FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length);
|
||||||
GLboolean UnmapBuffer(GLenum target);
|
GLboolean UnmapBuffer(GLenum target);
|
||||||
void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||||
void* MapBuffer(GLenum target, GLenum access);
|
void* MapBuffer(GLenum target, GLenum access);
|
||||||
|
void BufferStorage(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags);
|
||||||
|
void CreateBuffers(GLsizei n, GLuint* buffers);
|
||||||
|
void NamedBufferStorage(GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags);
|
||||||
|
void NamedBufferData(GLuint buffer, GLsizeiptr size, const void* data, GLenum usage);
|
||||||
|
void NamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data);
|
||||||
|
void CopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset,
|
||||||
|
GLsizeiptr size);
|
||||||
|
void ClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data);
|
||||||
|
void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format,
|
||||||
|
GLenum type, const void* data);
|
||||||
|
void* MapNamedBuffer(GLuint buffer, GLenum access);
|
||||||
|
void* MapNamedBufferRange(GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||||
|
GLboolean UnmapNamedBuffer(GLuint buffer);
|
||||||
|
void FlushMappedNamedBufferRange(GLuint buffer, GLintptr offset, GLsizeiptr length);
|
||||||
|
void GetNamedBufferParameteriv(GLuint buffer, GLenum pname, GLint* params);
|
||||||
|
void GetNamedBufferParameteri64v(GLuint buffer, GLenum pname, GLint64* params);
|
||||||
|
void GetNamedBufferPointerv(GLuint buffer, GLenum pname, void** params);
|
||||||
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset,
|
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 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,26 @@ 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 (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;
|
||||||
|
|||||||
@@ -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,75 @@
|
|||||||
#include "GL_Drawing.h"
|
#include "GL_Drawing.h"
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_State/EGLState/Core.h>
|
||||||
#include <MG_Backend/BackendObjects.h>
|
#include <MG_Backend/BackendObjects.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
||||||
|
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||||
|
if (!currentProgram) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "There is no current program object."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentProgram->GetLinkStatus()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"The current program object is not linked."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool ValidateCurrentProgramForCompute(const char* functionName) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(functionName)) return false;
|
||||||
|
|
||||||
|
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||||
|
if (currentProgram->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"The current program object has no compute shader stage."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) {
|
||||||
|
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
||||||
|
if (!activeBackendObject) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "No active backend object."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeBackendObject->GetBackendType() == BackendType::DirectVulkan && mode == GL_LINE_LOOP) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", functionName,
|
||||||
|
"Primitive mode GL_LINE_LOOP is not supported by the DirectVulkan backend."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
|
if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"Default vertex array object cannot be used for drawing in core profile."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Clear_Backend(GLbitfield mask) {
|
void Clear_Backend(GLbitfield mask) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
@@ -50,6 +116,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MG_Backend::gBackendFunctionsTable.GL.DrawArrays(mode, first, count);
|
MG_Backend::gBackendFunctionsTable.GL.DrawArrays(mode, first, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiDrawArrays_Backend(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArrays(mode, first, count, drawcount);
|
||||||
|
}
|
||||||
|
|
||||||
void DrawElementsBaseVertex_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsBaseVertex_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLint basevertex) {
|
GLint basevertex) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
@@ -73,6 +146,24 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirect(mode, indirect, drawcount, stride);
|
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirect(mode, indirect, drawcount, stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiDrawElementsIndirectCount_Backend(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount(mode, type, indirect, drawcount,
|
||||||
|
maxdrawcount, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiDrawArraysIndirectCount_Backend(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount(mode, indirect, drawcount, maxdrawcount,
|
||||||
|
stride);
|
||||||
|
}
|
||||||
|
|
||||||
void DrawRangeElementsBaseVertex_Backend(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void DrawRangeElementsBaseVertex_Backend(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex) {
|
const void* indices, GLint basevertex) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
@@ -156,75 +247,193 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||||
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
|
||||||
|
auto dispatchCompute = MG_Backend::gBackendFunctionsTable.GL.DispatchCompute;
|
||||||
|
if (!dispatchCompute) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Backend does not support compute dispatch."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||||
|
dispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DispatchComputeIndirect(GLintptr indirect) {
|
||||||
|
auto dispatchComputeIndirect = MG_Backend::gBackendFunctionsTable.GL.DispatchComputeIndirect;
|
||||||
|
if (!dispatchComputeIndirect) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Backend does not support indirect compute dispatch."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||||
|
dispatchComputeIndirect(indirect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryBarrier(GLbitfield barriers) {
|
||||||
|
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
|
||||||
|
if (!memoryBarrier) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Backend does not support memory barriers."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memoryBarrier(barriers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryBarrierByRegion(GLbitfield barriers) {
|
||||||
|
auto memoryBarrierByRegion = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrierByRegion;
|
||||||
|
if (!memoryBarrierByRegion) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Backend does not support regional memory barriers."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memoryBarrierByRegion(barriers);
|
||||||
|
}
|
||||||
|
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride);
|
MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawArraysIndirect_Backend(mode, indirect, drawcount, stride);
|
MultiDrawArraysIndirect_Backend(mode, indirect, drawcount, stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride) {
|
||||||
|
auto multiDrawElementsIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount;
|
||||||
|
if (!multiDrawElementsIndirectCount) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Backend does not support indirect-parameter indexed draws."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MultiDrawElementsIndirectCount_Backend(mode, type, indirect, drawcount, maxdrawcount, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride) {
|
||||||
|
auto multiDrawArraysIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount;
|
||||||
|
if (!multiDrawArraysIndirectCount) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Backend does not support indirect-parameter array draws."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MultiDrawArraysIndirectCount_Backend(mode, indirect, drawcount, maxdrawcount, stride);
|
||||||
|
}
|
||||||
|
|
||||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex) {
|
const void* indices, GLint basevertex) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawRangeElementsBaseVertex_Backend(mode, start, end, count, type, indices, basevertex);
|
DrawRangeElementsBaseVertex_Backend(mode, start, end, count, type, indices, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {
|
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawRangeElements_Backend(mode, start, end, count, type, indices);
|
DrawRangeElements_Backend(mode, start, end, count, type, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstancedBaseVertexBaseInstance_Backend(mode, count, type, indices, instancecount, basevertex,
|
DrawElementsInstancedBaseVertexBaseInstance_Backend(mode, count, type, indices, instancecount, basevertex,
|
||||||
baseinstance);
|
baseinstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLint basevertex) {
|
GLsizei instancecount, GLint basevertex) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstancedBaseVertex_Backend(mode, count, type, indices, instancecount, basevertex);
|
DrawElementsInstancedBaseVertex_Backend(mode, count, type, indices, instancecount, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLuint baseinstance) {
|
GLsizei instancecount, GLuint baseinstance) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstancedBaseInstance_Backend(mode, count, type, indices, instancecount, baseinstance);
|
DrawElementsInstancedBaseInstance_Backend(mode, count, type, indices, instancecount, baseinstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) {
|
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstanced_Backend(mode, count, type, indices, instancecount);
|
DrawElementsInstanced_Backend(mode, count, type, indices, instancecount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsIndirect_Backend(mode, type, indirect);
|
DrawElementsIndirect_Backend(mode, type, indirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||||
GLuint baseinstance) {
|
GLuint baseinstance) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArraysInstancedBaseInstance_Backend(mode, first, count, instancecount, baseinstance);
|
DrawArraysInstancedBaseInstance_Backend(mode, first, count, instancecount, baseinstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
|
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArraysInstanced_Backend(mode, first, count, instancecount);
|
DrawArraysInstanced_Backend(mode, first, count, instancecount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArraysIndirect_Backend(mode, indirect);
|
DrawArraysIndirect_Backend(mode, indirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex);
|
DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArrays_Backend(mode, first, count);
|
DrawArrays_Backend(mode, first, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
|
if (drawcount < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "drawcount must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MultiDrawArrays_Backend(mode, first, count, drawcount);
|
||||||
|
}
|
||||||
|
|
||||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||||
GLsizei drawcount) {
|
GLsizei drawcount) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawElements_Backend(mode, count, type, indices, drawcount);
|
MultiDrawElements_Backend(mode, count, type, indices, drawcount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||||
GLsizei drawcount, const GLint* basevertex) {
|
GLsizei drawcount, const GLint* basevertex) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawElementsBaseVertex_Backend(mode, count, type, indices, drawcount, basevertex);
|
MultiDrawElementsBaseVertex_Backend(mode, count, type, indices, drawcount, basevertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +442,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||||
|
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||||
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElements_Backend(mode, count, type, indices);
|
DrawElements_Backend(mode, count, type, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,16 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
|
void MemoryBarrier(GLbitfield barriers);
|
||||||
|
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||||
|
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
|
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||||
|
GLsizei maxdrawcount, GLsizei stride);
|
||||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex);
|
const void* indices, GLint basevertex);
|
||||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
||||||
@@ -30,6 +38,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,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -24,19 +24,43 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLboolean IsRenderbuffer(GLuint renderbuffer);
|
GLboolean IsRenderbuffer(GLuint renderbuffer);
|
||||||
void GetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
void GetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params);
|
||||||
void GenRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
void GenRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||||
|
void CreateRenderbuffers(GLsizei n, GLuint* renderbuffers);
|
||||||
|
void NamedRenderbufferStorage(GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height);
|
||||||
|
void NamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat,
|
||||||
|
GLsizei width, GLsizei height);
|
||||||
|
void GetNamedRenderbufferParameteriv(GLuint renderbuffer, GLenum pname, GLint* params);
|
||||||
void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
||||||
|
void NamedFramebufferRenderbuffer(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget,
|
||||||
|
GLuint renderbuffer);
|
||||||
void DeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers);
|
void DeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers);
|
||||||
void BindRenderbuffer(GLenum target, GLuint renderbuffer);
|
void BindRenderbuffer(GLenum target, GLuint renderbuffer);
|
||||||
void SampleMaski(GLuint maskNumber, GLbitfield mask);
|
void SampleMaski(GLuint maskNumber, GLbitfield mask);
|
||||||
GLboolean IsFramebuffer(GLuint framebuffer);
|
GLboolean IsFramebuffer(GLuint framebuffer);
|
||||||
void GetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint* params);
|
void GetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint* params);
|
||||||
void GenFramebuffers(GLsizei n, GLuint* framebuffers);
|
void GenFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||||
|
void CreateFramebuffers(GLsizei n, GLuint* framebuffers);
|
||||||
void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
||||||
void FramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
void FramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
||||||
GLint zoffset);
|
GLint zoffset);
|
||||||
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||||
void FramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
void 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 NamedFramebufferTexture1D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||||
|
void NamedFramebufferTexture2D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||||
|
void NamedFramebufferTexture3D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
||||||
|
GLint zoffset);
|
||||||
|
void NamedFramebufferTextureLayer(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer);
|
||||||
|
void NamedFramebufferDrawBuffer(GLuint framebuffer, GLenum buf);
|
||||||
|
void NamedFramebufferDrawBuffers(GLuint framebuffer, GLsizei n, const GLenum* bufs);
|
||||||
|
void NamedFramebufferReadBuffer(GLuint framebuffer, GLenum src);
|
||||||
|
void ClearNamedFramebufferfv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||||
|
void ClearNamedFramebufferfi(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||||
|
GLenum CheckNamedFramebufferStatus(GLuint framebuffer, GLenum target);
|
||||||
|
void GetNamedFramebufferAttachmentParameteriv(GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params);
|
||||||
|
void BlitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1,
|
||||||
|
GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask,
|
||||||
|
GLenum filter);
|
||||||
void DrawBuffer(GLenum buf);
|
void DrawBuffer(GLenum buf);
|
||||||
void DrawBuffers(GLsizei n, const GLenum* bufs);
|
void DrawBuffers(GLsizei n, const GLenum* bufs);
|
||||||
void ReadBuffer(GLenum src);
|
void ReadBuffer(GLenum src);
|
||||||
|
|||||||
@@ -76,7 +76,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",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
const GLubyte* GetString(GLenum name);
|
const GLubyte* GetString(GLenum name);
|
||||||
const GLubyte* GetStringi(GLenum name, GLuint index);
|
const GLubyte* GetStringi(GLenum name, GLuint index);
|
||||||
|
void GetBooleanv(GLenum pname, GLboolean* params);
|
||||||
|
void GetFloatv(GLenum pname, GLfloat* params);
|
||||||
|
void GetDoublev(GLenum pname, GLdouble* params);
|
||||||
void GetIntegerv(GLenum pname, GLint* params);
|
void GetIntegerv(GLenum pname, GLint* params);
|
||||||
|
void GetInteger64v(GLenum pname, GLint64* params);
|
||||||
|
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||||
|
void GetInteger64i_v(GLenum target, GLuint index, GLint64* data);
|
||||||
GLenum GetError();
|
GLenum GetError();
|
||||||
} // 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);
|
||||||
@@ -45,6 +52,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void Uniform2i(GLint location, GLint v0, GLint v1);
|
void Uniform2i(GLint location, GLint v0, GLint v1);
|
||||||
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 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);
|
||||||
@@ -53,15 +64,78 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void Uniform2iv(GLint location, GLsizei count, const GLint* value);
|
void Uniform2iv(GLint location, GLsizei count, const GLint* value);
|
||||||
void Uniform3iv(GLint location, GLsizei count, const GLint* value);
|
void 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 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);
|
||||||
|
void ProgramUniform1f(GLuint program, GLint location, GLfloat v0);
|
||||||
|
void ProgramUniform2f(GLuint program, GLint location, GLfloat v0, GLfloat v1);
|
||||||
|
void ProgramUniform3f(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
|
||||||
|
void ProgramUniform4f(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
||||||
|
void ProgramUniform1i(GLuint program, GLint location, GLint v0);
|
||||||
|
void ProgramUniform2i(GLuint program, GLint location, GLint v0, GLint v1);
|
||||||
|
void ProgramUniform3i(GLuint program, GLint location, GLint v0, GLint v1, GLint v2);
|
||||||
|
void ProgramUniform4i(GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
|
||||||
|
void ProgramUniform1ui(GLuint program, GLint location, GLuint v0);
|
||||||
|
void ProgramUniform2ui(GLuint program, GLint location, GLuint v0, GLuint v1);
|
||||||
|
void ProgramUniform3ui(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2);
|
||||||
|
void ProgramUniform4ui(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
|
||||||
|
void ProgramUniform1fv(GLuint program, GLint location, GLsizei count, const GLfloat* value);
|
||||||
|
void ProgramUniform2fv(GLuint program, GLint location, GLsizei count, const GLfloat* value);
|
||||||
|
void ProgramUniform3fv(GLuint program, GLint location, GLsizei count, const GLfloat* value);
|
||||||
|
void ProgramUniform4fv(GLuint program, GLint location, GLsizei count, const GLfloat* value);
|
||||||
|
void ProgramUniform1iv(GLuint program, GLint location, GLsizei count, const GLint* value);
|
||||||
|
void ProgramUniform2iv(GLuint program, GLint location, GLsizei count, const GLint* value);
|
||||||
|
void ProgramUniform3iv(GLuint program, GLint location, GLsizei count, const GLint* value);
|
||||||
|
void ProgramUniform4iv(GLuint program, GLint location, GLsizei count, const GLint* value);
|
||||||
|
void ProgramUniform1uiv(GLuint program, GLint location, GLsizei count, const GLuint* value);
|
||||||
|
void ProgramUniform2uiv(GLuint program, GLint location, GLsizei count, const GLuint* value);
|
||||||
|
void ProgramUniform3uiv(GLuint program, GLint location, GLsizei count, const GLuint* value);
|
||||||
|
void ProgramUniform4uiv(GLuint program, GLint location, GLsizei count, const GLuint* value);
|
||||||
|
void ProgramUniformMatrix2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void UniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void UniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix2x3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix3x2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix2x4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix4x2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix3x4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
|
void ProgramUniformMatrix4x3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||||
|
const GLfloat* value);
|
||||||
GLuint GetUniformBlockIndex(GLuint program, const GLchar* uniformBlockName);
|
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);
|
||||||
|
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize,
|
||||||
|
GLsizei* length, GLchar* name);
|
||||||
|
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
|
||||||
|
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||||
|
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
|
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||||
void ValidateProgram(GLuint program);
|
void ValidateProgram(GLuint program);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -0,0 +1,362 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#include "GL_Query.h"
|
||||||
|
#include <Config.h>
|
||||||
|
#include <MG_Backend/BackendObjects.h>
|
||||||
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_State/GLState/ErrorState/ErrorInfo.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
namespace {
|
||||||
|
// Frontend query object (GL_ARB_timer_query): wraps an optional backend
|
||||||
|
// timer-query handle. A null backend handle (backend has no timer-query
|
||||||
|
// support, timer queries are disabled by config, or the backend could
|
||||||
|
// not create a query at call time) keeps a graceful fallback: the query
|
||||||
|
// result is immediately available and reads as zero.
|
||||||
|
struct QueryObject {
|
||||||
|
GLuint id = 0;
|
||||||
|
GLenum target = 0; // 0 = gen'd but never used with BeginQuery/QueryCounter
|
||||||
|
MG_Backend::BackendQueryHandle backendHandle = nullptr;
|
||||||
|
Bool active = false;
|
||||||
|
Bool ended = false;
|
||||||
|
Bool resultCached = false;
|
||||||
|
Uint64 cachedResult = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Query calls may arrive from any thread (launchers migrate the context
|
||||||
|
// across JVM threads), so the live-object registry is mutex-guarded,
|
||||||
|
// like the sync-object registry in GL_Sync.cpp. Entries left at process
|
||||||
|
// shutdown are simply dropped; their backend handles die with the
|
||||||
|
// backend.
|
||||||
|
std::mutex g_queryObjectsMutex;
|
||||||
|
UnorderedMap<GLuint, QueryObject*> g_liveQueryObjects;
|
||||||
|
// Monotonically increasing id allocator; ids are valid query objects
|
||||||
|
// immediately after GenQueries.
|
||||||
|
GLuint g_nextQueryId = 1;
|
||||||
|
// Id of the query currently active on GL_TIME_ELAPSED (0 = none).
|
||||||
|
GLuint g_activeTimeElapsedQueryId = 0;
|
||||||
|
|
||||||
|
Bool TimerQueryDisabled() {
|
||||||
|
return MG_Config::Features.DisableTimerQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecordQueryError(ErrorCode code, const char* function, const char* message) {
|
||||||
|
MG_State::pGLContext->RecordError(code,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", function, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callers must hold g_queryObjectsMutex.
|
||||||
|
QueryObject* FindQueryObjectLocked(GLuint id) {
|
||||||
|
const auto it = g_liveQueryObjects.find(id);
|
||||||
|
return it != g_liveQueryObjects.end() ? it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callers must hold g_queryObjectsMutex. Releases the backend handle
|
||||||
|
// (if any) and clears any cached result, so the object can be reused.
|
||||||
|
void ResetQueryObjectLocked(QueryObject* queryObject) {
|
||||||
|
if (queryObject->backendHandle) {
|
||||||
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
|
deleteBackendQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->backendHandle = nullptr;
|
||||||
|
}
|
||||||
|
queryObject->active = false;
|
||||||
|
queryObject->ended = false;
|
||||||
|
queryObject->resultCached = false;
|
||||||
|
queryObject->cachedResult = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callers must hold g_queryObjectsMutex.
|
||||||
|
void EndTimeElapsedQueryLocked(QueryObject* queryObject) {
|
||||||
|
const auto endTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.EndTimeElapsedQuery;
|
||||||
|
if (endTimeElapsedQuery && queryObject->backendHandle) {
|
||||||
|
endTimeElapsedQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->active = false;
|
||||||
|
queryObject->ended = true;
|
||||||
|
g_activeTimeElapsedQueryId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared GetQueryObject* implementation. Returns false when an error
|
||||||
|
// was recorded and no value should be written back.
|
||||||
|
Bool GetQueryObjectValue(GLuint id, GLenum pname, const char* function, Uint64& outValue) {
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
auto* queryObject = FindQueryObjectLocked(id);
|
||||||
|
if (!queryObject) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, function, "Query object does not exist.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (queryObject->active) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, function, "Query object is still active.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (pname) {
|
||||||
|
case GL_QUERY_RESULT_AVAILABLE: {
|
||||||
|
if (queryObject->resultCached || !queryObject->backendHandle) {
|
||||||
|
outValue = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto isQueryResultAvailable = MG_Backend::gBackendFunctionsTable.GL.IsQueryResultAvailable;
|
||||||
|
outValue = (!isQueryResultAvailable || isQueryResultAvailable(queryObject->backendHandle)) ? 1 : 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case GL_QUERY_RESULT: {
|
||||||
|
if (queryObject->resultCached) {
|
||||||
|
outValue = queryObject->cachedResult;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Uint64 result = 0;
|
||||||
|
if (queryObject->backendHandle) {
|
||||||
|
const auto getQueryResult64 = MG_Backend::gBackendFunctionsTable.GL.GetQueryResult64;
|
||||||
|
if (getQueryResult64 &&
|
||||||
|
!getQueryResult64(queryObject->backendHandle, /*wait=*/true, &result)) {
|
||||||
|
// The backend could not produce the result YET (e.g. a
|
||||||
|
// Vulkan wait refusing to block on a not-yet-submitted
|
||||||
|
// frame serial). Per the documented no-stall tradeoff
|
||||||
|
// this call reads 0, but the value is NOT cached and
|
||||||
|
// the backend handle is kept, so a later AVAILABLE
|
||||||
|
// poll / RESULT read still produces the real value.
|
||||||
|
outValue = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Final value produced (or no GetQueryResult64 hook: the
|
||||||
|
// query degrades to a zero result); the backend handle is
|
||||||
|
// consumed and the value cached for later reads.
|
||||||
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
|
deleteBackendQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->backendHandle = nullptr;
|
||||||
|
}
|
||||||
|
queryObject->cachedResult = result;
|
||||||
|
queryObject->resultCached = true;
|
||||||
|
outValue = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, function, "Unsupported query object parameter.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void GenQueries(GLsizei n, GLuint* ids) {
|
||||||
|
if (n < 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ids) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
|
const GLuint id = g_nextQueryId++;
|
||||||
|
auto* queryObject = new QueryObject;
|
||||||
|
queryObject->id = id;
|
||||||
|
g_liveQueryObjects[id] = queryObject;
|
||||||
|
ids[i] = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteQueries(GLsizei n, const GLuint* ids) {
|
||||||
|
if (n < 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "n cannot be negative.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ids) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
|
const auto it = g_liveQueryObjects.find(ids[i]);
|
||||||
|
if (it == g_liveQueryObjects.end()) {
|
||||||
|
continue; // unknown ids are silently ignored
|
||||||
|
}
|
||||||
|
QueryObject* queryObject = it->second;
|
||||||
|
if (queryObject->active) {
|
||||||
|
EndTimeElapsedQueryLocked(queryObject); // implicitly end before deletion
|
||||||
|
}
|
||||||
|
if (queryObject->backendHandle) {
|
||||||
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
|
deleteBackendQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
queryObject->backendHandle = nullptr;
|
||||||
|
}
|
||||||
|
g_liveQueryObjects.erase(it);
|
||||||
|
delete queryObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLboolean IsQuery(GLuint id) {
|
||||||
|
if (id == 0) {
|
||||||
|
return GL_FALSE;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
// Gen'd ids count as query objects here: the registry creates live
|
||||||
|
// objects at GenQueries time.
|
||||||
|
return FindQueryObjectLocked(id) != nullptr ? GL_TRUE : GL_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginQuery(GLenum target, GLuint id) {
|
||||||
|
if (target != GL_TIME_ELAPSED) {
|
||||||
|
// Only GL_TIME_ELAPSED timer queries are implemented (occlusion and
|
||||||
|
// primitive queries remain stubs); GL_TIMESTAMP is not a valid
|
||||||
|
// BeginQuery target either.
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (id == 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query id 0 cannot be used.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
auto* queryObject = FindQueryObjectLocked(id);
|
||||||
|
if (!queryObject) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (g_activeTimeElapsedQueryId != 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
|
"A query is already active on GL_TIME_ELAPSED.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queryObject->active) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object is already active.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queryObject->target != 0 && queryObject->target != target) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
|
"Query object was already used with a different target.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||||
|
queryObject->target = target;
|
||||||
|
queryObject->active = true;
|
||||||
|
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
||||||
|
queryObject->backendHandle =
|
||||||
|
(!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr;
|
||||||
|
g_activeTimeElapsedQueryId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndQuery(GLenum target) {
|
||||||
|
if (target != GL_TIME_ELAPSED) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
if (g_activeTimeElapsedQueryId == 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on GL_TIME_ELAPSED.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* queryObject = FindQueryObjectLocked(g_activeTimeElapsedQueryId);
|
||||||
|
if (!queryObject) {
|
||||||
|
g_activeTimeElapsedQueryId = 0; // should not happen; keep state consistent
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EndTimeElapsedQueryLocked(queryObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryCounter(GLuint id, GLenum target) {
|
||||||
|
if (target != GL_TIMESTAMP) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "QueryCounter target must be GL_TIMESTAMP.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (id == 0) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query id 0 cannot be used.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
auto* queryObject = FindQueryObjectLocked(id);
|
||||||
|
if (!queryObject) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queryObject->active) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object is currently active.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queryObject->target != 0 && queryObject->target != target) {
|
||||||
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
|
"Query object was already used with a different target.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||||
|
queryObject->target = target;
|
||||||
|
const auto queryCounterTimestamp = MG_Backend::gBackendFunctionsTable.GL.QueryCounterTimestamp;
|
||||||
|
queryObject->backendHandle =
|
||||||
|
(!TimerQueryDisabled() && queryCounterTimestamp) ? queryCounterTimestamp() : nullptr;
|
||||||
|
queryObject->ended = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryiv(GLenum target, GLenum pname, GLint* params) {
|
||||||
|
if (!params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (pname) {
|
||||||
|
case GL_CURRENT_QUERY: {
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
// Only GL_TIME_ELAPSED queries can be active; GL_TIMESTAMP queries
|
||||||
|
// never are, and other targets remain unimplemented.
|
||||||
|
*params = target == GL_TIME_ELAPSED ? static_cast<GLint>(g_activeTimeElapsedQueryId) : 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case GL_QUERY_COUNTER_BITS: {
|
||||||
|
// 64 bits are advertised only while the live backend can actually
|
||||||
|
// time: IsTimerQuerySupported is the dynamic truth (extension /
|
||||||
|
// entry points / timestamp valid bits at call time, not at table
|
||||||
|
// init), and the MOBILEGL_DISABLE_TIMERQUERY kill switch always
|
||||||
|
// wins. Non-timer targets remain unimplemented and report 0.
|
||||||
|
const Bool timerTarget = target == GL_TIME_ELAPSED || target == GL_TIMESTAMP;
|
||||||
|
const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported;
|
||||||
|
const Bool supported =
|
||||||
|
timerTarget && !TimerQueryDisabled() && isTimerQuerySupported && isTimerQuerySupported();
|
||||||
|
*params = supported ? 64 : 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Unsupported query parameter.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params) {
|
||||||
|
Uint64 value = 0;
|
||||||
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
constexpr Uint64 kMaxInt = static_cast<Uint64>(INT_MAX);
|
||||||
|
*params = value > kMaxInt ? INT_MAX : static_cast<GLint>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params) {
|
||||||
|
Uint64 value = 0;
|
||||||
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*params = static_cast<GLuint>(value & 0xFFFFFFFFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params) {
|
||||||
|
Uint64 value = 0;
|
||||||
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*params = static_cast<GLint64>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) {
|
||||||
|
Uint64 value = 0;
|
||||||
|
if (!GetQueryObjectValue(id, pname, __FUNCTION__, value) || !params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*params = static_cast<GLuint64>(value);
|
||||||
|
}
|
||||||
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Impl/GLImpl/Query/GL_Query.h
|
||||||
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
|
void GenQueries(GLsizei n, GLuint* ids);
|
||||||
|
void DeleteQueries(GLsizei n, const GLuint* ids);
|
||||||
|
GLboolean IsQuery(GLuint id);
|
||||||
|
void BeginQuery(GLenum target, GLuint id);
|
||||||
|
void EndQuery(GLenum target);
|
||||||
|
void GetQueryiv(GLenum target, GLenum pname, GLint* params);
|
||||||
|
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params);
|
||||||
|
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params);
|
||||||
|
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params);
|
||||||
|
void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params);
|
||||||
|
void QueryCounter(GLuint id, GLenum target);
|
||||||
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
@@ -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,82 @@
|
|||||||
#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,
|
||||||
|
::MobileGL::BlendEquation& outEquation) {
|
||||||
|
outEquation = MG_Util::ConvertGLEnumToBlendEquation(mode);
|
||||||
|
if (outEquation != ::MobileGL::BlendEquation::Unknown) return true;
|
||||||
|
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"Blend equation enum " + MG_Util::ConvertGLEnumToString(mode) +
|
||||||
|
" is not supported."));
|
||||||
|
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,
|
||||||
@@ -26,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) {
|
||||||
@@ -61,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) {
|
||||||
@@ -101,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(
|
||||||
@@ -122,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) {
|
||||||
@@ -137,14 +398,70 @@ 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) {
|
||||||
// TODO: implement
|
FrontFaceMode frontFaceMode = MG_Util::ConvertGLEnumToFrontFaceMode(mode);
|
||||||
|
if (frontFaceMode == FrontFaceMode::Unknown) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "FrontFace_State",
|
||||||
|
"Front face mode enum " +
|
||||||
|
MG_Util::ConvertFrontFaceModeToString(frontFaceMode) + "(" +
|
||||||
|
MG_Util::ConvertGLEnumToString(mode) + ") is not supported."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_State::pGLContext->SetFrontFaceMode(frontFaceMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProvokingVertex_State(GLenum mode) {
|
||||||
|
ProvokingVertexMode provokingVertexMode = MG_Util::ConvertGLEnumToProvokingVertexMode(mode);
|
||||||
|
if (provokingVertexMode == ProvokingVertexMode::Unknown) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ProvokingVertex_State",
|
||||||
|
"Provoking vertex mode enum " +
|
||||||
|
MG_Util::ConvertProvokingVertexModeToString(provokingVertexMode) +
|
||||||
|
"(" + MG_Util::ConvertGLEnumToString(mode) + ") is not supported."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_State::pGLContext->SetProvokingVertexMode(provokingVertexMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
@@ -159,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(
|
||||||
@@ -173,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) {
|
||||||
@@ -210,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) {
|
||||||
@@ -249,11 +624,24 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlendEquation_State(GLenum mode) {
|
void BlendEquation_State(GLenum mode) {
|
||||||
// TODO: implement
|
::MobileGL::BlendEquation blendEquation = ::MobileGL::BlendEquation::Unknown;
|
||||||
|
if (!TryConvertBlendEquation(mode, "BlendEquation_State", blendEquation)) return;
|
||||||
|
MG_State::pGLContext->SetBlendEquation(blendEquation, blendEquation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendEquationSeparate_State(GLenum modeRGB, GLenum modeAlpha) {
|
||||||
|
::MobileGL::BlendEquation colorEquation = ::MobileGL::BlendEquation::Unknown;
|
||||||
|
if (!TryConvertBlendEquation(modeRGB, "BlendEquationSeparate_State", colorEquation)) return;
|
||||||
|
|
||||||
|
::MobileGL::BlendEquation alphaEquation = ::MobileGL::BlendEquation::Unknown;
|
||||||
|
if (!TryConvertBlendEquation(modeAlpha, "BlendEquationSeparate_State", alphaEquation)) return;
|
||||||
|
|
||||||
|
MG_State::pGLContext->SetBlendEquation(colorEquation, alphaEquation);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -261,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) {
|
||||||
@@ -283,10 +673,50 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BlendFactor dstRGBM = MG_Util::ConvertGLEnumToBlendFactor(dstRGB);
|
BlendFactor dstRGBM = MG_Util::ConvertGLEnumToBlendFactor(dstRGB);
|
||||||
BlendFactor srcAlphaM = MG_Util::ConvertGLEnumToBlendFactor(srcAlpha);
|
BlendFactor srcAlphaM = MG_Util::ConvertGLEnumToBlendFactor(srcAlpha);
|
||||||
BlendFactor dstAlphaM = MG_Util::ConvertGLEnumToBlendFactor(dstAlpha);
|
BlendFactor dstAlphaM = MG_Util::ConvertGLEnumToBlendFactor(dstAlpha);
|
||||||
|
if (srcRGBM == BlendFactor::Unknown || dstRGBM == BlendFactor::Unknown ||
|
||||||
|
srcAlphaM == BlendFactor::Unknown || dstAlphaM == BlendFactor::Unknown) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "BlendFuncSeparatei_State",
|
||||||
|
"One of the indexed blend factor enums is not supported."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
MG_State::pGLContext->SetBlendFuncIndexed(buf, srcRGBM, dstRGBM, srcAlphaM, dstAlphaM);
|
MG_State::pGLContext->SetBlendFuncIndexed(buf, srcRGBM, dstRGBM, srcAlphaM, dstAlphaM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlendFunci_State(GLuint buf, GLenum src, GLenum dst) {
|
||||||
|
BlendFuncSeparatei_State(buf, src, dst, src, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendEquationSeparatei_State(GLuint buf, GLenum modeRGB, GLenum modeAlpha) {
|
||||||
|
if (buf >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", "BlendEquationSeparatei_State",
|
||||||
|
"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;
|
||||||
|
}
|
||||||
|
|
||||||
|
::MobileGL::BlendEquation colorEquation = ::MobileGL::BlendEquation::Unknown;
|
||||||
|
if (!TryConvertBlendEquation(modeRGB, "BlendEquationSeparatei_State", colorEquation)) return;
|
||||||
|
|
||||||
|
::MobileGL::BlendEquation alphaEquation = ::MobileGL::BlendEquation::Unknown;
|
||||||
|
if (!TryConvertBlendEquation(modeAlpha, "BlendEquationSeparatei_State", alphaEquation)) return;
|
||||||
|
|
||||||
|
MG_State::pGLContext->SetBlendEquationIndexed(buf, colorEquation, alphaEquation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendEquationi_State(GLuint buf, GLenum mode) {
|
||||||
|
BlendEquationSeparatei_State(buf, mode, mode);
|
||||||
|
}
|
||||||
|
|
||||||
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(
|
||||||
@@ -301,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(
|
||||||
@@ -315,10 +749,26 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||||
|
void BlendEquationi(GLuint buf, GLenum mode) {
|
||||||
|
BlendEquationi_State(buf, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendEquationSeparatei(GLuint buf, GLenum modeRGB, GLenum modeAlpha) {
|
||||||
|
BlendEquationSeparatei_State(buf, modeRGB, modeAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendFunci(GLuint buf, GLenum src, GLenum dst) {
|
||||||
|
BlendFunci_State(buf, src, 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) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -387,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);
|
||||||
}
|
}
|
||||||
@@ -415,6 +891,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
FrontFace_State(mode);
|
FrontFace_State(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProvokingVertex(GLenum mode) {
|
||||||
|
ProvokingVertex_State(mode);
|
||||||
|
}
|
||||||
|
|
||||||
void Enable(GLenum cap) {
|
void Enable(GLenum cap) {
|
||||||
Enable_State(cap);
|
Enable_State(cap);
|
||||||
}
|
}
|
||||||
@@ -443,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);
|
||||||
}
|
}
|
||||||
@@ -455,6 +943,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BlendEquation_State(mode);
|
BlendEquation_State(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha) {
|
||||||
|
BlendEquationSeparate_State(modeRGB, modeAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
void BlendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
void BlendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
|
||||||
BlendColor_State(red, green, blue, alpha);
|
BlendColor_State(red, green, blue, alpha);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
|
void BlendEquationi(GLuint buf, GLenum mode);
|
||||||
|
void BlendEquationSeparatei(GLuint buf, GLenum modeRGB, GLenum modeAlpha);
|
||||||
|
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);
|
||||||
@@ -29,13 +33,17 @@ 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);
|
||||||
GLboolean IsEnabled(GLenum cap);
|
GLboolean IsEnabled(GLenum cap);
|
||||||
void Hint(GLenum target, GLenum mode);
|
void Hint(GLenum target, GLenum mode);
|
||||||
void FrontFace(GLenum mode);
|
void FrontFace(GLenum mode);
|
||||||
|
void ProvokingVertex(GLenum mode);
|
||||||
void Enable(GLenum cap);
|
void Enable(GLenum cap);
|
||||||
void Disable(GLenum cap);
|
void Disable(GLenum cap);
|
||||||
void DepthRange(GLclampd near_val, GLclampd far_val);
|
void DepthRange(GLclampd near_val, GLclampd far_val);
|
||||||
@@ -43,9 +51,12 @@ 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 BlendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
|
void BlendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
|
||||||
void ClearStencil(GLint s);
|
void ClearStencil(GLint s);
|
||||||
void ClearDepth(GLclampd depth);
|
void ClearDepth(GLclampd depth);
|
||||||
|
|||||||
@@ -13,7 +13,45 @@
|
|||||||
#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;
|
||||||
|
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 +61,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,6 +90,9 @@ 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;
|
||||||
@@ -64,7 +106,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,6 +147,15 @@ 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;
|
||||||
@@ -167,6 +220,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BindSampler_State(GLuint unit, GLuint sampler) {
|
void BindSampler_State(GLuint unit, GLuint sampler) {
|
||||||
|
MGLOG_D("BindSampler_State: unit = %u, sampler = %u", unit, sampler);
|
||||||
if (unit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) {
|
if (unit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue,
|
ErrorCode::InvalidValue,
|
||||||
@@ -175,10 +229,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);
|
||||||
@@ -212,7 +276,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) {
|
||||||
@@ -240,7 +304,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,37 +7,130 @@
|
|||||||
// 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"
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
GLsync FenceSync_Backend(GLenum condition, GLbitfield flags) {
|
namespace {
|
||||||
return 0;
|
// 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 {
|
||||||
|
MG_Backend::BackendSyncHandle backendHandle = nullptr;
|
||||||
|
GLenum condition = GL_SYNC_GPU_COMMANDS_COMPLETE;
|
||||||
|
GLbitfield flags = 0;
|
||||||
|
};
|
||||||
|
|
||||||
GLenum ClientWaitSync_Backend(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
// Sync calls may arrive from any thread (launchers migrate the context
|
||||||
return 0;
|
// 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 DeleteSync_Backend(GLsync sync) {}
|
SyncObject* FindSyncObject(GLsync sync) {
|
||||||
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
GLsync FenceSync_State(GLenum condition, GLbitfield flags) {
|
const auto it = g_liveSyncObjects.find(sync);
|
||||||
return 0;
|
return it != g_liveSyncObjects.end() ? it->second : nullptr;
|
||||||
}
|
}
|
||||||
|
} // namespace
|
||||||
GLenum ClientWaitSync_State(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeleteSync_State(GLsync sync) {}
|
|
||||||
|
|
||||||
GLsync FenceSync(GLenum condition, GLbitfield flags) {
|
GLsync FenceSync(GLenum condition, GLbitfield flags) {
|
||||||
return 0;
|
auto* syncObject = new SyncObject;
|
||||||
|
syncObject->condition = condition;
|
||||||
|
syncObject->flags = flags;
|
||||||
|
if (const auto backendFenceSync = MG_Backend::gBackendFunctionsTable.GL.FenceSync) {
|
||||||
|
syncObject->backendHandle = backendFenceSync();
|
||||||
|
}
|
||||||
|
const GLsync handle = reinterpret_cast<GLsync>(syncObject);
|
||||||
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
|
g_liveSyncObjects[handle] = syncObject;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLboolean IsSync(GLsync sync) {
|
||||||
|
return FindSyncObject(sync) != nullptr ? GL_TRUE : GL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||||
return 0;
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
|
if (!syncObject) {
|
||||||
|
return GL_WAIT_FAILED;
|
||||||
|
}
|
||||||
|
const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync;
|
||||||
|
if (!backendClientWaitSync || !syncObject->backendHandle) {
|
||||||
|
return GL_ALREADY_SIGNALED; // legacy always-signaled fallback
|
||||||
|
}
|
||||||
|
return backendClientWaitSync(syncObject->backendHandle, flags, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeleteSync(GLsync sync) {}
|
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||||
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
|
if (!syncObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync;
|
||||||
|
if (backendWaitSync && syncObject->backendHandle) {
|
||||||
|
backendWaitSync(syncObject->backendHandle, flags, timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteSync(GLsync sync) {
|
||||||
|
if (sync == nullptr) {
|
||||||
|
return; // glDeleteSync(0) is silently ignored
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
|
if (!syncObject) {
|
||||||
|
if (length) {
|
||||||
|
*length = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLint value = 0;
|
||||||
|
switch (pname) {
|
||||||
|
case GL_OBJECT_TYPE:
|
||||||
|
value = GL_SYNC_FENCE;
|
||||||
|
break;
|
||||||
|
case GL_SYNC_STATUS: {
|
||||||
|
const auto backendGetSyncStatus = MG_Backend::gBackendFunctionsTable.GL.GetSyncStatus;
|
||||||
|
const Bool signaled = !backendGetSyncStatus || !syncObject->backendHandle ||
|
||||||
|
backendGetSyncStatus(syncObject->backendHandle);
|
||||||
|
value = signaled ? GL_SIGNALED : GL_UNSIGNALED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GL_SYNC_CONDITION:
|
||||||
|
value = static_cast<GLint>(syncObject->condition);
|
||||||
|
break;
|
||||||
|
case GL_SYNC_FLAGS:
|
||||||
|
value = static_cast<GLint>(syncObject->flags);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length) {
|
||||||
|
*length = bufSize > 0 && values ? 1 : 0;
|
||||||
|
}
|
||||||
|
if (bufSize > 0 && values) {
|
||||||
|
values[0] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
GLsync FenceSync(GLenum condition, GLbitfield flags);
|
GLsync FenceSync(GLenum condition, GLbitfield flags);
|
||||||
|
GLboolean IsSync(GLsync sync);
|
||||||
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);
|
GLenum ClientWaitSync(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);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,53 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
|
void ClearTexImage(GLuint texture, GLint level, GLenum format, GLenum type, const void* data);
|
||||||
|
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);
|
||||||
|
void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access,
|
||||||
|
GLenum format);
|
||||||
void GenerateMipmap(GLenum target);
|
void GenerateMipmap(GLenum target);
|
||||||
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 CreateTextures(GLenum target, GLsizei n, GLuint* textures);
|
||||||
|
void TextureStorage1D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width);
|
||||||
|
void TextureStorage2D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
|
||||||
|
void TextureStorage3D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height,
|
||||||
|
GLsizei depth);
|
||||||
|
void TextureStorage2DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width,
|
||||||
|
GLsizei height, GLboolean fixedsamplelocations);
|
||||||
|
void TextureStorage3DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width,
|
||||||
|
GLsizei height, GLsizei depth, GLboolean fixedsamplelocations);
|
||||||
|
void TextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type,
|
||||||
|
const void* pixels);
|
||||||
|
void TextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
|
||||||
|
GLenum format, GLenum type, const void* pixels);
|
||||||
|
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);
|
||||||
|
void TextureParameterf(GLuint texture, GLenum pname, GLfloat param);
|
||||||
|
void TextureParameterfv(GLuint texture, GLenum pname, const GLfloat* params);
|
||||||
|
void TextureParameteri(GLuint texture, GLenum pname, GLint param);
|
||||||
|
void TextureParameterIiv(GLuint texture, GLenum pname, const GLint* params);
|
||||||
|
void TextureParameterIuiv(GLuint texture, GLenum pname, const GLuint* params);
|
||||||
|
void TextureParameteriv(GLuint texture, GLenum pname, const GLint* params);
|
||||||
|
void GenerateTextureMipmap(GLuint texture);
|
||||||
|
void BindTextureUnit(GLuint unit, GLuint texture);
|
||||||
|
void GetTextureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels);
|
||||||
|
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);
|
||||||
|
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params);
|
||||||
|
void GetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params);
|
||||||
|
void GetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params);
|
||||||
|
void GetTextureParameteriv(GLuint texture, GLenum pname, GLint* params);
|
||||||
|
void GetTextureLevelParameterfv(GLuint texture, GLint level, GLenum pname, GLfloat* params);
|
||||||
|
void GetTextureLevelParameteriv(GLuint texture, GLint level, GLenum pname, GLint* params);
|
||||||
|
void TexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width);
|
||||||
|
void TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
|
||||||
|
void TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height,
|
||||||
|
GLsizei depth);
|
||||||
|
void TexStorage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
||||||
|
GLsizei height, GLboolean fixedsamplelocations);
|
||||||
|
void TexStorage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
||||||
|
GLsizei height, GLsizei depth, GLboolean fixedsamplelocations);
|
||||||
void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
||||||
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels);
|
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels);
|
||||||
void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
|
void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
|
||||||
@@ -44,6 +89,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void GetTexParameterfv(GLenum target, GLenum pname, GLfloat* params);
|
void GetTexParameterfv(GLenum target, GLenum pname, GLfloat* params);
|
||||||
void GetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params);
|
void GetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params);
|
||||||
void GetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat* params);
|
void GetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat* params);
|
||||||
|
void GetMultisamplefv(GLenum pname, GLuint index, GLfloat* val);
|
||||||
|
void GetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params);
|
||||||
void GetCompressedTexImage(GLenum target, GLint level, void* img);
|
void GetCompressedTexImage(GLenum target, GLint level, void* img);
|
||||||
void GenTextures(GLsizei n, GLuint* textures);
|
void GenTextures(GLsizei n, GLuint* textures);
|
||||||
void DeleteTextures(GLsizei n, const GLuint* textures);
|
void DeleteTextures(GLsizei n, const GLuint* textures);
|
||||||
@@ -51,11 +98,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLsizei width, GLsizei height);
|
GLsizei width, GLsizei height);
|
||||||
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 CopyTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
||||||
|
GLsizei width, GLsizei height);
|
||||||
void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
|
void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
|
||||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||||
GLsizei height, GLint border);
|
GLsizei height, GLint border);
|
||||||
void CopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
void CopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||||
GLint border);
|
GLint border);
|
||||||
|
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);
|
||||||
void CompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
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);
|
GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data);
|
||||||
void CompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
|
void CompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
|
||||||
|
|||||||
@@ -7,7 +7,11 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "ProxyTexture.h"
|
#include "ProxyTexture.h"
|
||||||
|
#include <MG_State/GLState/TextureState/TextureObject1D.h>
|
||||||
#include <MG_State/GLState/TextureState/TextureObject2D.h>
|
#include <MG_State/GLState/TextureState/TextureObject2D.h>
|
||||||
|
#include <MG_State/GLState/TextureState/TextureObject2DCube.h>
|
||||||
|
#include <MG_State/GLState/TextureState/TextureObject3D.h>
|
||||||
|
#include <MG_State/GLState/TextureState/TextureObjectStubs.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
||||||
UniquePtr<ProxyTextureManager> pProxyTextureManager;
|
UniquePtr<ProxyTextureManager> pProxyTextureManager;
|
||||||
@@ -37,7 +41,39 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
m_proxyTexturesMap.erase(it);
|
m_proxyTexturesMap.erase(it);
|
||||||
}
|
}
|
||||||
auto& obj = m_proxyTexturesMap[target];
|
auto& obj = m_proxyTexturesMap[target];
|
||||||
obj = MakeShared<MG_State::GLState::TextureObject2D>(0);
|
switch (target) {
|
||||||
|
case TextureUploadTarget::ProxyTexture1D:
|
||||||
|
case TextureUploadTarget::ProxyTexture1DArray:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObject1D>(0);
|
||||||
|
break;
|
||||||
|
case TextureUploadTarget::ProxyTexture2D:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObject2D>(0);
|
||||||
|
break;
|
||||||
|
case TextureUploadTarget::ProxyTexture3D:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObject3D>(0);
|
||||||
|
break;
|
||||||
|
case TextureUploadTarget::ProxyCubeMap:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObject2DCube>(0);
|
||||||
|
break;
|
||||||
|
case TextureUploadTarget::ProxyTextureRectangle:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObjectRectangle>(0);
|
||||||
|
break;
|
||||||
|
case TextureUploadTarget::ProxyTexture2DArray:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObject2DArray>(0);
|
||||||
|
break;
|
||||||
|
case TextureUploadTarget::ProxyCubeMapArray:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObjectCubeMapArray>(0);
|
||||||
|
break;
|
||||||
|
case TextureUploadTarget::ProxyTexture2DMultisample:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObject2DMultisample>(0);
|
||||||
|
break;
|
||||||
|
case TextureUploadTarget::ProxyTexture2DMultisampleArray:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObject2DMultisampleArray>(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
obj = MakeShared<MG_State::GLState::TextureObject2D>(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -83,8 +84,20 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: GL_INVALID_VALUE may be generated if level is greater than log2(max), where max is the returned
|
Int maxTextureSize = MG_Backend::DynamicBackendParameters{}.MaxTextureSize;
|
||||||
// value of GL_MAX_TEXTURE_SIZE.
|
if (MG_Backend::pActiveBackendObject) {
|
||||||
|
maxTextureSize = MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxTextureSize;
|
||||||
|
}
|
||||||
|
Int maxLevel = 0;
|
||||||
|
for (Int size = std::max(maxTextureSize, 1); size > 1; size >>= 1) {
|
||||||
|
++maxLevel;
|
||||||
|
}
|
||||||
|
if (level > maxLevel) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureLevelNumber",
|
||||||
|
"Texture level exceeds GL_MAX_TEXTURE_SIZE"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -129,7 +142,7 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateTextureSizeRange(SizeT width, SizeT height, SizeT depth) {
|
Bool ValidateTextureSizeRange(Int width, Int height, Int depth) {
|
||||||
if (width < 0 || height < 0 || depth < 0) {
|
if (width < 0 || height < 0 || depth < 0) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureSizeRange",
|
ErrorCode::InvalidValue, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureSizeRange",
|
||||||
@@ -162,61 +175,153 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
|
Bool IsIntegerColorInputFormat(TextureInputFormat format) {
|
||||||
TextureInternalFormat internalFormat,
|
return format == TextureInputFormat::RInteger || format == TextureInputFormat::RGInteger ||
|
||||||
TexturePixelDataType type) {
|
format == TextureInputFormat::RGBInteger || format == TextureInputFormat::BGRInteger ||
|
||||||
if (type == TexturePixelDataType::UnsignedByte332 || type == TexturePixelDataType::UnsignedByte233Rev ||
|
format == TextureInputFormat::RGBAInteger || format == TextureInputFormat::BGRAInteger ||
|
||||||
type == TexturePixelDataType::UnsignedShort565 || type == TexturePixelDataType::UnsignedShort565Rev ||
|
format == TextureInputFormat::GreenInteger || format == TextureInputFormat::BlueInteger ||
|
||||||
type == TexturePixelDataType::UnsignedInt101111Rev) {
|
format == TextureInputFormat::AlphaInteger;
|
||||||
if (format != TextureInputFormat::RGB) {
|
}
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat) {
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureInternalFormatCompatibleWithInput",
|
switch (internalFormat) {
|
||||||
"Invalid format for the given type"));
|
case TextureInternalFormat::R8I:
|
||||||
|
case TextureInternalFormat::R8UI:
|
||||||
|
case TextureInternalFormat::R16I:
|
||||||
|
case TextureInternalFormat::R16UI:
|
||||||
|
case TextureInternalFormat::R32I:
|
||||||
|
case TextureInternalFormat::R32UI:
|
||||||
|
case TextureInternalFormat::RG8I:
|
||||||
|
case TextureInternalFormat::RG8UI:
|
||||||
|
case TextureInternalFormat::RG16I:
|
||||||
|
case TextureInternalFormat::RG16UI:
|
||||||
|
case TextureInternalFormat::RG32I:
|
||||||
|
case TextureInternalFormat::RG32UI:
|
||||||
|
case TextureInternalFormat::RGB8I:
|
||||||
|
case TextureInternalFormat::RGB8UI:
|
||||||
|
case TextureInternalFormat::RGB16I:
|
||||||
|
case TextureInternalFormat::RGB16UI:
|
||||||
|
case TextureInternalFormat::RGB32I:
|
||||||
|
case TextureInternalFormat::RGB32UI:
|
||||||
|
case TextureInternalFormat::RGBA8I:
|
||||||
|
case TextureInternalFormat::RGBA8UI:
|
||||||
|
case TextureInternalFormat::RGBA16I:
|
||||||
|
case TextureInternalFormat::RGBA16UI:
|
||||||
|
case TextureInternalFormat::RGBA32I:
|
||||||
|
case TextureInternalFormat::RGBA32UI:
|
||||||
|
case TextureInternalFormat::RGB10A2UI:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool IsDepthLikeInternalFormat(TextureInternalFormat internalFormat) {
|
||||||
|
switch (internalFormat) {
|
||||||
|
case TextureInternalFormat::DepthComponent:
|
||||||
|
case TextureInternalFormat::DepthComponent16:
|
||||||
|
case TextureInternalFormat::DepthComponent24:
|
||||||
|
case TextureInternalFormat::DepthComponent32: // not core, kept for Minecraft 1.21.5+
|
||||||
|
case TextureInternalFormat::DepthComponent32F:
|
||||||
|
case TextureInternalFormat::Depth24Stencil8:
|
||||||
|
case TextureInternalFormat::Depth32FStencil8:
|
||||||
|
case TextureInternalFormat::DepthStencil:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool IsDepthLikeInputFormat(TextureInputFormat format) {
|
||||||
|
return format == TextureInputFormat::DepthComponent || format == TextureInputFormat::DepthStencil ||
|
||||||
|
format == TextureInputFormat::StencilIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client-memory format<->type pairing rules shared by pixel uploads (TexImage*) and readbacks
|
||||||
|
// (ReadPixels, GetTexImage). Mirrors the desktop-GL validity matrix used by GL CTS packed_pixels
|
||||||
|
// (glcPackedPixelsTests isFormatValid): packed types constrain the formats they may pair with, and
|
||||||
|
// integer formats reject floating-point types; violations raise GL_INVALID_OPERATION.
|
||||||
|
Bool ValidateClientFormatTypePairing(TextureInputFormat format, TexturePixelDataType type) {
|
||||||
|
const auto recordInvalidOperation = [](const char* message) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateClientFormatTypePairing", message));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type == TexturePixelDataType::UnsignedByte332 || type == TexturePixelDataType::UnsignedByte233Rev ||
|
||||||
|
type == TexturePixelDataType::UnsignedShort565 || type == TexturePixelDataType::UnsignedShort565Rev) {
|
||||||
|
if (format != TextureInputFormat::RGB && format != TextureInputFormat::RGBInteger) {
|
||||||
|
return recordInvalidOperation("Packed RGB type requires RGB or RGB_INTEGER format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == TexturePixelDataType::UnsignedInt101111Rev || type == TexturePixelDataType::UnsignedInt5999Rev) {
|
||||||
|
if (format != TextureInputFormat::RGB) {
|
||||||
|
return recordInvalidOperation("Packed float RGB type requires RGB format");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == TexturePixelDataType::UnsignedShort4444 || type == TexturePixelDataType::UnsignedShort4444Rev ||
|
if (type == TexturePixelDataType::UnsignedShort4444 || type == TexturePixelDataType::UnsignedShort4444Rev ||
|
||||||
type == TexturePixelDataType::UnsignedShort5551 || type == TexturePixelDataType::UnsignedShort1555Rev ||
|
type == TexturePixelDataType::UnsignedShort5551 || type == TexturePixelDataType::UnsignedShort1555Rev ||
|
||||||
type == TexturePixelDataType::UnsignedInt8888 || type == TexturePixelDataType::UnsignedInt8888Rev ||
|
type == TexturePixelDataType::UnsignedInt8888 || type == TexturePixelDataType::UnsignedInt8888Rev ||
|
||||||
type == TexturePixelDataType::UnsignedInt1010102 || type == TexturePixelDataType::UnsignedInt2101010Rev ||
|
type == TexturePixelDataType::UnsignedInt1010102 || type == TexturePixelDataType::UnsignedInt2101010Rev) {
|
||||||
type == TexturePixelDataType::UnsignedInt5999Rev) {
|
if (format != TextureInputFormat::RGBA && format != TextureInputFormat::BGRA &&
|
||||||
if (format != TextureInputFormat::RGBA && format != TextureInputFormat::BGRA) {
|
format != TextureInputFormat::RGBAInteger && format != TextureInputFormat::BGRAInteger) {
|
||||||
MG_State::pGLContext->RecordError(
|
return recordInvalidOperation("Packed RGBA type requires RGBA/BGRA (integer) format");
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureInternalFormatCompatibleWithInput",
|
|
||||||
"Invalid format for the given type"));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (internalFormat == TextureInternalFormat::DepthComponent ||
|
if (type == TexturePixelDataType::UnsignedInt248 || type == TexturePixelDataType::Float32UnsignedInt248Rev) {
|
||||||
internalFormat == TextureInternalFormat::DepthComponent16 ||
|
if (format != TextureInputFormat::DepthStencil) {
|
||||||
internalFormat == TextureInternalFormat::DepthComponent24 ||
|
return recordInvalidOperation("Packed depth-stencil type requires DEPTH_STENCIL format");
|
||||||
internalFormat == TextureInternalFormat::DepthComponent32F) {
|
|
||||||
if (format != TextureInputFormat::DepthComponent) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
|
||||||
ErrorCode::InvalidOperation,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureInternalFormatCompatibleWithInput",
|
|
||||||
"Invalid format for depth component internal format"));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (format == TextureInputFormat::DepthComponent &&
|
if (format == TextureInputFormat::DepthStencil && type != TexturePixelDataType::UnsignedInt248 &&
|
||||||
(internalFormat != TextureInternalFormat::DepthComponent &&
|
type != TexturePixelDataType::Float32UnsignedInt248Rev) {
|
||||||
internalFormat != TextureInternalFormat::DepthComponent16 &&
|
return recordInvalidOperation("DEPTH_STENCIL format requires a packed depth-stencil type");
|
||||||
internalFormat != TextureInternalFormat::DepthComponent24 &&
|
}
|
||||||
internalFormat != TextureInternalFormat::DepthComponent32F &&
|
|
||||||
internalFormat != TextureInternalFormat::DepthComponent32 // workaround for Minecraft 1.21.5+
|
if (IsIntegerColorInputFormat(format) &&
|
||||||
)) {
|
(type == TexturePixelDataType::Float || type == TexturePixelDataType::HalfFloat)) {
|
||||||
|
return recordInvalidOperation("Integer format cannot be used with a floating-point type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors the desktop-GL validity matrix used by GL CTS packed_pixels (glcPackedPixelsTests
|
||||||
|
// isFormatValid, INPUT_TEXIMAGE): packed-type/format pairing, depth-vs-color mismatch, and
|
||||||
|
// integer-ness matching all raise GL_INVALID_OPERATION instead of reaching the upload path.
|
||||||
|
Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
|
||||||
|
TextureInternalFormat internalFormat,
|
||||||
|
TexturePixelDataType type) {
|
||||||
|
const auto recordInvalidOperation = [](const char* message) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureInternalFormatCompatibleWithInput",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureInternalFormatCompatibleWithInput",
|
||||||
"Invalid internal format for depth component format"));
|
message));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ValidateClientFormatTypePairing(format, type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TexImage in core 3.3 has no stencil-only upload path (that arrived with GL 4.4).
|
||||||
|
if (format == TextureInputFormat::StencilIndex) {
|
||||||
|
return recordInvalidOperation("STENCIL_INDEX is not a valid texture upload format");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsDepthLikeInputFormat(format) != IsDepthLikeInternalFormat(internalFormat)) {
|
||||||
|
return recordInvalidOperation("Depth/stencil-ness of format and internal format must match");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsIntegerColorInputFormat(format) != IsIntegerColorInternalFormat(internalFormat)) {
|
||||||
|
return recordInvalidOperation("Integer-ness of format and internal format must match");
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +335,18 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (target == TextureUploadTarget::Texture2DMultisample ||
|
||||||
|
target == TextureUploadTarget::ProxyTexture2DMultisample ||
|
||||||
|
target == TextureUploadTarget::Texture2DMultisampleArray ||
|
||||||
|
target == TextureUploadTarget::ProxyTexture2DMultisampleArray) {
|
||||||
|
if (level != 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateTextureLevelWithUploadTarget",
|
||||||
|
"Level must be zero for multisample textures"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +360,19 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ValidateTextureNotDefault(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||||
|
const char* caller) {
|
||||||
|
if (textureObject && textureObject->GetExternalIndex() == 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||||
|
"This operation is not allowed on the default texture (zero is "
|
||||||
|
"bound to the target)."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Bool ValidateTextureTargetUniformity(SharedPtr<MG_State::GLState::ITextureObject> textureObject,
|
Bool ValidateTextureTargetUniformity(SharedPtr<MG_State::GLState::ITextureObject> textureObject,
|
||||||
TextureTarget target) {
|
TextureTarget target) {
|
||||||
if (!textureObject) return true; // should be created later
|
if (!textureObject) return true; // should be created later
|
||||||
|
|||||||
@@ -20,14 +20,22 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
Bool ValidateTexturePixelDataType(TexturePixelDataType texturePixelDataType);
|
Bool ValidateTexturePixelDataType(TexturePixelDataType texturePixelDataType);
|
||||||
Bool ValidateTextureLevelNumber(Int level);
|
Bool ValidateTextureLevelNumber(Int level);
|
||||||
Bool ValidateTextureSizeWithTextureUploadTarget(TextureUploadTarget target, GLsizei width, GLsizei height);
|
Bool ValidateTextureSizeWithTextureUploadTarget(TextureUploadTarget target, GLsizei width, GLsizei height);
|
||||||
Bool ValidateTextureSizeRange(SizeT width, SizeT height, SizeT depth);
|
Bool ValidateTextureSizeRange(Int width, Int height, Int depth);
|
||||||
Bool ValidateTextureInternalFormat(TextureInternalFormat format);
|
Bool ValidateTextureInternalFormat(TextureInternalFormat format);
|
||||||
Bool ValidateTextureBorderNumber(Int border);
|
Bool ValidateTextureBorderNumber(Int border);
|
||||||
|
Bool IsIntegerColorInputFormat(TextureInputFormat format);
|
||||||
|
Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat);
|
||||||
|
Bool ValidateClientFormatTypePairing(TextureInputFormat format, TexturePixelDataType type);
|
||||||
Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
|
Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
|
||||||
TextureInternalFormat internalFormat,
|
TextureInternalFormat internalFormat,
|
||||||
TexturePixelDataType type);
|
TexturePixelDataType type);
|
||||||
Bool ValidateTextureLevelWithUploadTarget(TextureUploadTarget target, Int level);
|
Bool ValidateTextureLevelWithUploadTarget(TextureUploadTarget target, Int level);
|
||||||
Bool ValidateTextureObject(SharedPtr<MG_State::GLState::ITextureObject> textureObject);
|
Bool ValidateTextureObject(SharedPtr<MG_State::GLState::ITextureObject> textureObject);
|
||||||
|
// Rejects the per-target default texture objects (name 0) with GL_INVALID_OPERATION for entry
|
||||||
|
// points that require a GenTextures-created texture, e.g. TexStorage* ("An INVALID_OPERATION
|
||||||
|
// error is generated if zero is bound to target", ARB_texture_storage).
|
||||||
|
Bool ValidateTextureNotDefault(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||||
|
const char* caller);
|
||||||
Bool ValidateTextureTargetUniformity(SharedPtr<MG_State::GLState::ITextureObject> textureObject,
|
Bool ValidateTextureTargetUniformity(SharedPtr<MG_State::GLState::ITextureObject> textureObject,
|
||||||
TextureTarget target);
|
TextureTarget target);
|
||||||
Bool ValidateTextureSubImageOffsets(SharedPtr<MG_State::GLState::ITextureObject> textureObject, Int xoffset,
|
Bool ValidateTextureSubImageOffsets(SharedPtr<MG_State::GLState::ITextureObject> textureObject, Int xoffset,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,101 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
|
void VertexAttrib1f(GLuint index, GLfloat x);
|
||||||
|
void VertexAttrib1fv(GLuint index, const GLfloat* v);
|
||||||
|
void VertexAttrib2f(GLuint index, GLfloat x, GLfloat y);
|
||||||
|
void VertexAttrib2fv(GLuint index, const GLfloat* v);
|
||||||
|
void VertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z);
|
||||||
|
void VertexAttrib3fv(GLuint index, const GLfloat* v);
|
||||||
|
void VertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
|
||||||
|
void VertexAttrib4fv(GLuint index, const GLfloat* v);
|
||||||
|
void VertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w);
|
||||||
|
void VertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w);
|
||||||
|
void VertexAttribI4iv(GLuint index, const GLint* v);
|
||||||
|
void VertexAttribI4uiv(GLuint index, const GLuint* v);
|
||||||
|
// Packed current-value setters (GL_INT_/GL_UNSIGNED_INT_2_10_10_10_REV). Decode one packed word
|
||||||
|
// into the first 1/2/3/4 components of the generic attribute's float current value.
|
||||||
|
void VertexAttribP1ui(GLuint index, GLenum type, GLboolean normalized, GLuint value);
|
||||||
|
void VertexAttribP2ui(GLuint index, GLenum type, GLboolean normalized, GLuint value);
|
||||||
|
void VertexAttribP3ui(GLuint index, GLenum type, GLboolean normalized, GLuint value);
|
||||||
|
void VertexAttribP4ui(GLuint index, GLenum type, GLboolean normalized, GLuint value);
|
||||||
|
void VertexAttribP1uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value);
|
||||||
|
void VertexAttribP2uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value);
|
||||||
|
void VertexAttribP3uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value);
|
||||||
|
void VertexAttribP4uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value);
|
||||||
|
void VertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w);
|
||||||
|
void VertexAttrib4Nubv(GLuint index, const GLubyte* v);
|
||||||
|
void VertexAttrib4ubv(GLuint index, const GLubyte* v);
|
||||||
|
// Float-domain current-value setters that funnel into VertexAttrib4f. The d/s/bv/iv/uiv/usv
|
||||||
|
// forms are value-preserving (only the 4N* forms normalize per GL 3.3 Core Eq 2.1/2.2).
|
||||||
|
void VertexAttrib1d(GLuint index, GLdouble x);
|
||||||
|
void VertexAttrib2d(GLuint index, GLdouble x, GLdouble y);
|
||||||
|
void VertexAttrib3d(GLuint index, GLdouble x, GLdouble y, GLdouble z);
|
||||||
|
void VertexAttrib4d(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w);
|
||||||
|
void VertexAttrib1dv(GLuint index, const GLdouble* v);
|
||||||
|
void VertexAttrib2dv(GLuint index, const GLdouble* v);
|
||||||
|
void VertexAttrib3dv(GLuint index, const GLdouble* v);
|
||||||
|
void VertexAttrib4dv(GLuint index, const GLdouble* v);
|
||||||
|
void VertexAttrib1s(GLuint index, GLshort x);
|
||||||
|
void VertexAttrib2s(GLuint index, GLshort x, GLshort y);
|
||||||
|
void VertexAttrib3s(GLuint index, GLshort x, GLshort y, GLshort z);
|
||||||
|
void VertexAttrib4s(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w);
|
||||||
|
void VertexAttrib1sv(GLuint index, const GLshort* v);
|
||||||
|
void VertexAttrib2sv(GLuint index, const GLshort* v);
|
||||||
|
void VertexAttrib3sv(GLuint index, const GLshort* v);
|
||||||
|
void VertexAttrib4sv(GLuint index, const GLshort* v);
|
||||||
|
void VertexAttrib4bv(GLuint index, const GLbyte* v);
|
||||||
|
void VertexAttrib4iv(GLuint index, const GLint* v);
|
||||||
|
void VertexAttrib4uiv(GLuint index, const GLuint* v);
|
||||||
|
void VertexAttrib4usv(GLuint index, const GLushort* v);
|
||||||
|
void VertexAttrib4Nbv(GLuint index, const GLbyte* v);
|
||||||
|
void VertexAttrib4Nsv(GLuint index, const GLshort* v);
|
||||||
|
void VertexAttrib4Niv(GLuint index, const GLint* v);
|
||||||
|
void VertexAttrib4Nusv(GLuint index, const GLushort* v);
|
||||||
|
void VertexAttrib4Nuiv(GLuint index, const GLuint* v);
|
||||||
|
// Pure-integer current-value setters that funnel into VertexAttribI4i / VertexAttribI4ui and
|
||||||
|
// write the integer view, never the float one.
|
||||||
|
void VertexAttribI1i(GLuint index, GLint x);
|
||||||
|
void VertexAttribI2i(GLuint index, GLint x, GLint y);
|
||||||
|
void VertexAttribI3i(GLuint index, GLint x, GLint y, GLint z);
|
||||||
|
void VertexAttribI1ui(GLuint index, GLuint x);
|
||||||
|
void VertexAttribI2ui(GLuint index, GLuint x, GLuint y);
|
||||||
|
void VertexAttribI3ui(GLuint index, GLuint x, GLuint y, GLuint z);
|
||||||
|
void VertexAttribI1iv(GLuint index, const GLint* v);
|
||||||
|
void VertexAttribI2iv(GLuint index, const GLint* v);
|
||||||
|
void VertexAttribI3iv(GLuint index, const GLint* v);
|
||||||
|
void VertexAttribI1uiv(GLuint index, const GLuint* v);
|
||||||
|
void VertexAttribI2uiv(GLuint index, const GLuint* v);
|
||||||
|
void VertexAttribI3uiv(GLuint index, const GLuint* v);
|
||||||
|
void VertexAttribI4bv(GLuint index, const GLbyte* v);
|
||||||
|
void VertexAttribI4sv(GLuint index, const GLshort* v);
|
||||||
|
void VertexAttribI4ubv(GLuint index, const GLubyte* v);
|
||||||
|
void VertexAttribI4usv(GLuint index, const GLushort* v);
|
||||||
|
void GetVertexAttribfv(GLuint index, GLenum pname, GLfloat* params);
|
||||||
|
void GetVertexAttribdv(GLuint index, GLenum pname, GLdouble* params);
|
||||||
|
void GetVertexAttribiv(GLuint index, GLenum pname, GLint* params);
|
||||||
|
void GetVertexAttribPointerv(GLuint index, GLenum pname, void** pointer);
|
||||||
|
void GetVertexAttribIiv(GLuint index, GLenum pname, GLint* params);
|
||||||
|
void GetVertexAttribIuiv(GLuint index, GLenum pname, GLuint* params);
|
||||||
|
void CreateVertexArrays(GLsizei n, GLuint* arrays);
|
||||||
|
void DisableVertexArrayAttrib(GLuint vaobj, GLuint index);
|
||||||
|
void EnableVertexArrayAttrib(GLuint vaobj, GLuint index);
|
||||||
|
void VertexArrayElementBuffer(GLuint vaobj, GLuint buffer);
|
||||||
|
void VertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride);
|
||||||
|
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
||||||
|
GLuint relativeoffset);
|
||||||
|
void VertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||||
|
void VertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex);
|
||||||
|
void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor);
|
||||||
|
void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
|
||||||
|
const GLintptr* offsets, const GLsizei* strides);
|
||||||
|
void BindVertexBuffer(GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride);
|
||||||
|
void BindVertexBuffers(GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets,
|
||||||
|
const GLsizei* strides);
|
||||||
|
void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset);
|
||||||
|
void VertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||||
|
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex);
|
||||||
|
void VertexBindingDivisor(GLuint bindingindex, GLuint divisor);
|
||||||
void VertexAttribDivisor(GLuint index, GLuint divisor);
|
void VertexAttribDivisor(GLuint index, GLuint divisor);
|
||||||
GLboolean IsVertexArray(GLuint array);
|
GLboolean IsVertexArray(GLuint array);
|
||||||
void DisableVertexAttribArray(GLuint index);
|
void DisableVertexAttribArray(GLuint index);
|
||||||
|
|||||||
@@ -7,12 +7,22 @@
|
|||||||
// 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/MGToGL/DataTypeConverter.h>
|
#include <MG_Util/Converters/MGToGL/DataTypeConverter.h>
|
||||||
#include <MG_Util/Converters/MGToStr/DataTypeConverter.h>
|
#include <MG_Util/Converters/MGToStr/DataTypeConverter.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
||||||
|
Uint GetMaxVertexAttribs() {
|
||||||
|
constexpr Uint capacity = static_cast<Uint>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS);
|
||||||
|
if (!MG_Backend::pActiveBackendObject) return capacity;
|
||||||
|
|
||||||
|
const Int backendLimit = MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxVertexAttribs;
|
||||||
|
if (backendLimit <= 0) return capacity;
|
||||||
|
return std::min(static_cast<Uint>(backendLimit), capacity);
|
||||||
|
}
|
||||||
|
|
||||||
Bool ValidateVertexArrayName(Uint index) {
|
Bool ValidateVertexArrayName(Uint index) {
|
||||||
Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
|
Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
@@ -37,13 +47,13 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateVertexAttributeIndex(Uint index) {
|
Bool ValidateVertexAttributeIndex(Uint index) {
|
||||||
if (index >= MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS) {
|
const Uint maxVertexAttribs = GetMaxVertexAttribs();
|
||||||
|
if (index >= maxVertexAttribs) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue,
|
ErrorCode::InvalidValue,
|
||||||
MakeUnique<GenericErrorInfo>(
|
MakeUnique<GenericErrorInfo>(
|
||||||
"MG_Impl/GLImpl", "ValidateVertexAttributeIndex",
|
"MG_Impl/GLImpl", "ValidateVertexAttributeIndex",
|
||||||
std::format("Attribute index {} exceeds maximum of {}.", index,
|
std::format("Attribute index {} exceeds maximum of {}.", index, maxVertexAttribs - 1)));
|
||||||
MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS - 1)));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -79,4 +89,85 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
|
||||||
|
Bool integerPath) {
|
||||||
|
constexpr const char* fn = "ValidateVertexAttribFormat";
|
||||||
|
if (type == DataType::Unknown) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", fn,
|
||||||
|
std::format("Invalid type for attribute {}.", index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool isPacked = (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev);
|
||||||
|
// The packed 2_10_10_10 types are float-normalizing only; glVertexAttribIPointer never accepts them.
|
||||||
|
if (integerPath && isPacked) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("glVertexAttribIPointer does not accept packed 2_10_10_10 types (attribute {}).",
|
||||||
|
index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeRaw == static_cast<GLint>(GL_BGRA)) {
|
||||||
|
// GL_BGRA is a float-path-only size: it needs GL_UNSIGNED_BYTE or a 2_10_10_10 type and
|
||||||
|
// normalized == GL_TRUE. On the integer path it is simply an out-of-range size.
|
||||||
|
if (integerPath) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("GL_BGRA size is not valid for glVertexAttribIPointer (attribute {}).", index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(type == DataType::Uint8 || isPacked)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("GL_BGRA size requires GL_UNSIGNED_BYTE or a 2_10_10_10 type (attribute {}).",
|
||||||
|
index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!normalized) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("GL_BGRA size requires normalized == GL_TRUE (attribute {}).", index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Size-range (INVALID_VALUE) takes precedence over the packed-size-must-be-4 rule.
|
||||||
|
if (sizeRaw < 1 || sizeRaw > 4) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("Invalid size {} for attribute {}. Must be 1-4 or GL_BGRA.", sizeRaw, index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isPacked && sizeRaw != 4) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("A 2_10_10_10 type requires size 4 or GL_BGRA (attribute {}).", index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stride < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", fn,
|
||||||
|
std::format("Negative stride {} for attribute {}.", stride, index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user