mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-02-19 01:09:58 +00:00
Compare commits
1152 Commits
B4-rc3
...
Release/B6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
188f25c6bf | ||
|
|
8619f787b9 | ||
|
|
60ac03ebaf | ||
|
|
e1ebde3cd2 | ||
|
|
f519904c38 | ||
|
|
fa6f1abaac | ||
|
|
875242fe15 | ||
|
|
20b5957999 | ||
|
|
f0cb9d1167 | ||
|
|
6cd88a70ad | ||
|
|
697bbcd6d4 | ||
|
|
ecca5720a9 | ||
|
|
50e856f823 | ||
|
|
6359bb9acd | ||
|
|
938011fce6 | ||
|
|
d09a10299e | ||
|
|
8e706636d3 | ||
|
|
352cd2fafe | ||
|
|
081a0a419d | ||
|
|
7e42e6cdce | ||
|
|
d857b2a36e | ||
|
|
ba64a2d400 | ||
|
|
9d8bc46812 | ||
|
|
5b6095ad05 | ||
|
|
2ba23e8646 | ||
|
|
5fd9bc6a84 | ||
|
|
7446fe8c99 | ||
|
|
136410daac | ||
|
|
646a725c0e | ||
|
|
b6f994b511 | ||
|
|
9b0af43a42 | ||
|
|
0f1eb0fa7f | ||
|
|
bf8f4c17f0 | ||
|
|
d9ffb98c9b | ||
|
|
0f261abb07 | ||
|
|
1dacf7b2de | ||
|
|
6a9075b412 | ||
|
|
0c63a901be | ||
|
|
1fd00ba26c | ||
|
|
f14d135266 | ||
|
|
ce342029d8 | ||
|
|
6d19e85fa4 | ||
|
|
2780f6c22f | ||
|
|
33bcf19164 | ||
|
|
86793a657e | ||
|
|
0ee45d8a70 | ||
|
|
3b16fb1baa | ||
|
|
57c2e47cbf | ||
|
|
a5e6065d39 | ||
|
|
b10d912ab6 | ||
|
|
9a26519e9b | ||
|
|
5a02e600c3 | ||
|
|
6cbbf4a2a0 | ||
|
|
f42d1e0888 | ||
|
|
def838b883 | ||
|
|
7e032f67be | ||
|
|
7275f249c9 | ||
|
|
de5030f564 | ||
|
|
f489006531 | ||
|
|
571a5da50f | ||
|
|
5385853f3f | ||
|
|
0149549251 | ||
|
|
f5e68711d0 | ||
|
|
28cba2e2b3 | ||
|
|
d376dc4b5a | ||
|
|
af51ea6d0b | ||
|
|
eb1774f955 | ||
|
|
ae38db4915 | ||
|
|
9dee9ed7bb | ||
|
|
0dabfdc432 | ||
|
|
97c5b8c3a7 | ||
|
|
e98913f182 | ||
|
|
4f3682fece | ||
|
|
9a9f9d433e | ||
|
|
d24459b27f | ||
|
|
da04a6dd54 | ||
|
|
ed0cae84c8 | ||
|
|
681106e5c3 | ||
|
|
8a45a74fb1 | ||
|
|
8b68a96ee1 | ||
|
|
cb29de80f9 | ||
|
|
d20e319ccf | ||
|
|
286e7622b8 | ||
|
|
20d459d113 | ||
|
|
20b2130596 | ||
|
|
bf9023d6f8 | ||
|
|
a8521b821e | ||
|
|
0799910e70 | ||
|
|
8b8b580f63 | ||
|
|
712b1cbc46 | ||
|
|
fba7c80b2f | ||
|
|
a4f5bc7320 | ||
|
|
48735cd001 | ||
|
|
123be552a4 | ||
|
|
aba8c5b499 | ||
|
|
56ec98524c | ||
|
|
9ccd93bfd8 | ||
|
|
f17dfdc9b3 | ||
|
|
39c1f99446 | ||
|
|
7e8849180d | ||
|
|
146d9a2a53 | ||
|
|
7cb6ccd6f5 | ||
|
|
9b910eced1 | ||
|
|
3e079e0489 | ||
|
|
1e660fb7e1 | ||
|
|
1770defea2 | ||
|
|
b94869249c | ||
|
|
a9e3ab9d18 | ||
|
|
fec45dfe9c | ||
|
|
5de175c1f3 | ||
|
|
8974ae4fb5 | ||
|
|
8aa36144dc | ||
|
|
c737b12a3b | ||
|
|
6384a8d006 | ||
|
|
d1e421f8a8 | ||
|
|
c0da28247d | ||
|
|
6cbfa6e734 | ||
|
|
faae785c44 | ||
|
|
b2221b114e | ||
|
|
47b2a26898 | ||
|
|
b5dfbcb5a2 | ||
|
|
247e867f18 | ||
|
|
6699018ed1 | ||
|
|
947db38bc9 | ||
|
|
16f39450b5 | ||
|
|
ffd27ac82c | ||
|
|
1fcdcc8725 | ||
|
|
7ad3610276 | ||
|
|
a41ab81a90 | ||
|
|
0f8c0b5fb3 | ||
|
|
57e20007db | ||
|
|
2901e7aec5 | ||
|
|
6b9fa2b628 | ||
|
|
53c843d9dd | ||
|
|
78d2b76313 | ||
|
|
d7704b13c0 | ||
|
|
76d2c69b46 | ||
|
|
febc2ec980 | ||
|
|
81aa24d4d3 | ||
|
|
d8f2125543 | ||
|
|
aec2c78bd2 | ||
|
|
5797fbb4e5 | ||
|
|
836e7ab998 | ||
|
|
0759a7664b | ||
|
|
0db17f803b | ||
|
|
4d57671bf1 | ||
|
|
30780ce445 | ||
|
|
3b55ac5420 | ||
|
|
32fbcaffd2 | ||
|
|
0a9a9ed57e | ||
|
|
0a768a5a7f | ||
|
|
6afd262a27 | ||
|
|
87077dfe6e | ||
|
|
7ed18e24e2 | ||
|
|
60834a5719 | ||
|
|
d5e2689d64 | ||
|
|
7b7a06b63f | ||
|
|
81f91caf0e | ||
|
|
3d727a2254 | ||
|
|
e4a4e2331a | ||
|
|
8682ec207e | ||
|
|
c8a5293645 | ||
|
|
15334c89d6 | ||
|
|
2eec459b47 | ||
|
|
947325e00d | ||
|
|
eae559b4c9 | ||
|
|
3134ec84de | ||
|
|
132d0e3c42 | ||
|
|
4bbdd30284 | ||
|
|
6d06320fb2 | ||
|
|
f3fe774f69 | ||
|
|
e053c014f7 | ||
|
|
9c8a8a1b44 | ||
|
|
1685249f3a | ||
|
|
97cef000fd | ||
|
|
8f45290beb | ||
|
|
9afe170413 | ||
|
|
dd6d9c44df | ||
|
|
fb5a71c47e | ||
|
|
75370e464d | ||
|
|
c55d0a82f2 | ||
|
|
f28084e653 | ||
|
|
6c76d6ada5 | ||
|
|
3a8cb6a613 | ||
|
|
a3820536ab | ||
|
|
36f97f08ad | ||
|
|
48cf099638 | ||
|
|
88d60d4b3d | ||
|
|
3189c7bcd6 | ||
|
|
d1da0d62ed | ||
|
|
72033f3822 | ||
|
|
c2523be4b4 | ||
|
|
7efc274e81 | ||
|
|
7c2d493bb5 | ||
|
|
9908b737b0 | ||
|
|
0dad9b1e76 | ||
|
|
a13c90bd27 | ||
|
|
84b5478b02 | ||
|
|
38340d3497 | ||
|
|
eeefc15e46 | ||
|
|
91d6e3a82a | ||
|
|
7387a4a8e1 | ||
|
|
d9dc399522 | ||
|
|
70158a64e7 | ||
|
|
1ca43c4727 | ||
|
|
fc96b6691e | ||
|
|
5a94f82f10 | ||
|
|
5b7c38a4dd | ||
|
|
f01489720f | ||
|
|
202116786c | ||
|
|
8b4551c39c | ||
|
|
29698362ed | ||
|
|
f24db8d0cd | ||
|
|
cfd2e6ff32 | ||
|
|
e96311eb7b | ||
|
|
0d97a51802 | ||
|
|
5e1b8f2abe | ||
|
|
e0c0451b52 | ||
|
|
9ddfa585ec | ||
|
|
0ea188faf8 | ||
|
|
e1ac838796 | ||
|
|
770a4279ee | ||
|
|
1cfbcba813 | ||
|
|
3890c72159 | ||
|
|
4223a5e38f | ||
|
|
809e1095bd | ||
|
|
fd28d0604e | ||
|
|
30c57f411d | ||
|
|
9cd8027901 | ||
|
|
969ac4d1d1 | ||
|
|
da548e3858 | ||
|
|
21a349343b | ||
|
|
4ee6bdf198 | ||
|
|
b13582a911 | ||
|
|
05ca59ed48 | ||
|
|
e1e60fdaa6 | ||
|
|
ca29fe80a6 | ||
|
|
35bf30910b | ||
|
|
db78c8e468 | ||
|
|
febd081202 | ||
|
|
5bbc1d44bf | ||
|
|
22b968ff53 | ||
|
|
a0477466d2 | ||
|
|
c2a766c2ee | ||
|
|
4ff39616b2 | ||
|
|
2201ed869e | ||
|
|
d0b3c09456 | ||
|
|
a560a610d9 | ||
|
|
a7db3d3a0f | ||
|
|
016001da67 | ||
|
|
41884bfcc5 | ||
|
|
dd2d84a080 | ||
|
|
599fdd6ffd | ||
|
|
b34b253814 | ||
|
|
68b42e1c1a | ||
|
|
8580978321 | ||
|
|
d93510e9f2 | ||
|
|
75ec3c0478 | ||
|
|
e85fd68d82 | ||
|
|
f247d7f0da | ||
|
|
b0568ca404 | ||
|
|
3c9b9e6370 | ||
|
|
db3d20f935 | ||
|
|
ccdf7b7c0e | ||
|
|
efa49391fc | ||
|
|
fb4bdaee2b | ||
|
|
c7389285f9 | ||
|
|
aa426d13a7 | ||
|
|
89c83dafc1 | ||
|
|
05e363e009 | ||
|
|
e17b289759 | ||
|
|
79e986cc60 | ||
|
|
22f3cf5ba6 | ||
|
|
3067bdaa15 | ||
|
|
f3ebde7d9f | ||
|
|
905c1d7f58 | ||
|
|
11800029f0 | ||
|
|
71901414d1 | ||
|
|
96fa8891c8 | ||
|
|
1082875b8e | ||
|
|
dc918c55b6 | ||
|
|
a8ba014b52 | ||
|
|
9a6aa3ce66 | ||
|
|
f2fbb2b27c | ||
|
|
829db8a0e4 | ||
|
|
9601bc677f | ||
|
|
aba30e9541 | ||
|
|
c84879717f | ||
|
|
b3c81bcedf | ||
|
|
7f4dcd1ced | ||
|
|
15f76339c8 | ||
|
|
99536eaf9d | ||
|
|
f8b4874799 | ||
|
|
cff64ee7d3 | ||
|
|
04ae9217e8 | ||
|
|
46da447429 | ||
|
|
4b080f7610 | ||
|
|
d6bbc4f89c | ||
|
|
4fadf3a130 | ||
|
|
73dc08e5f9 | ||
|
|
07c92ec2e8 | ||
|
|
b334f22223 | ||
|
|
aad65c1cab | ||
|
|
0ad26b7da7 | ||
|
|
775ac7ce8b | ||
|
|
689cc53255 | ||
|
|
5629655f74 | ||
|
|
54e7542414 | ||
|
|
464fee3e20 | ||
|
|
42ed0d7638 | ||
|
|
5a3fe151e4 | ||
|
|
afd5e2d057 | ||
|
|
508c491967 | ||
|
|
b117bbafe5 | ||
|
|
5392f815af | ||
|
|
4c271f8744 | ||
|
|
745169fae2 | ||
|
|
7f79352320 | ||
|
|
b020372972 | ||
|
|
5fe529f213 | ||
|
|
7c91c922e6 | ||
|
|
9c49dc6efd | ||
|
|
f635077a2c | ||
|
|
a9b5302a51 | ||
|
|
3d0a8f6987 | ||
|
|
786a252b23 | ||
|
|
f145225dbc | ||
|
|
b38a5ce89e | ||
|
|
344d2ec599 | ||
|
|
6bba9bc25d | ||
|
|
1851002fc1 | ||
|
|
b99e1ea38e | ||
|
|
2ecfa0a3ec | ||
|
|
ca0bc7c514 | ||
|
|
4122841b09 | ||
|
|
e94252ad65 | ||
|
|
6fc0c69b2e | ||
|
|
ced952a4c6 | ||
|
|
4411d21135 | ||
|
|
70683010a6 | ||
|
|
7da2becfbd | ||
|
|
8a61c8ebc2 | ||
|
|
ef9b2958ec | ||
|
|
e72e138267 | ||
|
|
4c389a9274 | ||
|
|
b9c646074d | ||
|
|
042a7d0925 | ||
|
|
c69b19e68f | ||
|
|
cf7d501bc4 | ||
|
|
68e5b812a9 | ||
|
|
5a93f1e00c | ||
|
|
891f00a011 | ||
|
|
137171a8a2 | ||
|
|
36892839f3 | ||
|
|
0fc87576f3 | ||
|
|
3ffefb5281 | ||
|
|
fd12d9901a | ||
|
|
c05282c38c | ||
|
|
a391e271c3 | ||
|
|
24193aaaa6 | ||
|
|
f9b907a6b1 | ||
|
|
b8866a2ce4 | ||
|
|
d42e409728 | ||
|
|
780cf5f362 | ||
|
|
0080e5f1b9 | ||
|
|
ad6fa5a504 | ||
|
|
db2e38ae4d | ||
|
|
35334333ac | ||
|
|
ec0bd6adc8 | ||
|
|
8e8d8834de | ||
|
|
bf059a6eda | ||
|
|
2834c7d95b | ||
|
|
2099161b7e | ||
|
|
a40a964b30 | ||
|
|
194241c5a3 | ||
|
|
32134b33ea | ||
|
|
9d894065c8 | ||
|
|
62c5d68fc6 | ||
|
|
0f998582b9 | ||
|
|
7263159428 | ||
|
|
52f06ec332 | ||
|
|
7f93bbd675 | ||
|
|
5c20a851c6 | ||
|
|
11acaa2957 | ||
|
|
fe7973ea24 | ||
|
|
ff2ca20235 | ||
|
|
a114ea3de4 | ||
|
|
e6bd36ec7c | ||
|
|
34e5f7e968 | ||
|
|
2f8b139131 | ||
|
|
b058cbe9fe | ||
|
|
443f98d2fa | ||
|
|
92f27cc0f0 | ||
|
|
208b722348 | ||
|
|
67509d7a2d | ||
|
|
c20bb27b67 | ||
|
|
8cdeaceed9 | ||
|
|
7bcd0dd97f | ||
|
|
5bb1f01dea | ||
|
|
297d0be2dc | ||
|
|
fdb38a227e | ||
|
|
7ccd202d36 | ||
|
|
177a997883 | ||
|
|
b3f6c75ade | ||
|
|
912ca62a7b | ||
|
|
952ebea2c5 | ||
|
|
0d27092ef5 | ||
|
|
ebf20dd108 | ||
|
|
7cc9b5f77c | ||
|
|
0ccc84959e | ||
|
|
ba9f2b85b6 | ||
|
|
ed61a7adf9 | ||
|
|
d708651c53 | ||
|
|
0d00936aac | ||
|
|
6347f02efe | ||
|
|
dfdc407bc6 | ||
|
|
ac2c62e560 | ||
|
|
4b8255aa28 | ||
|
|
b6fedf1420 | ||
|
|
c8b4787cb1 | ||
|
|
d43126f433 | ||
|
|
6f39434bdc | ||
|
|
9b202d5566 | ||
|
|
764e52fb20 | ||
|
|
6f17e89b16 | ||
|
|
d8e7a83226 | ||
|
|
c74d48691f | ||
|
|
7c8f42855d | ||
|
|
d1a765c179 | ||
|
|
2ed3c82de0 | ||
|
|
17b77cfbc1 | ||
|
|
65ba2e8df9 | ||
|
|
e7fdf7e77a | ||
|
|
aa5922a1b4 | ||
|
|
142902b7b3 | ||
|
|
10110dd940 | ||
|
|
192fb1cdc7 | ||
|
|
35efa551ef | ||
|
|
f53adc7a05 | ||
|
|
a21e897bb5 | ||
|
|
136737f25b | ||
|
|
95987a9c91 | ||
|
|
bbd9c84896 | ||
|
|
8ab130deba | ||
|
|
fbf294efd9 | ||
|
|
2824238b4d | ||
|
|
bb74a9d9c8 | ||
|
|
9ff476bd09 | ||
|
|
6ef3fea05e | ||
|
|
02ec25b008 | ||
|
|
4e75c576b2 | ||
|
|
90dd1f3913 | ||
|
|
a8ddf72318 | ||
|
|
5d9db8b2f5 | ||
|
|
672cd246ab | ||
|
|
936688ddac | ||
|
|
ff6c46f7ca | ||
|
|
4dccd725bf | ||
|
|
6f8745a89b | ||
|
|
f971a01801 | ||
|
|
3d1eedd4ef | ||
|
|
d073f9969c | ||
|
|
b21d842f0e | ||
|
|
9fa643484c | ||
|
|
64b64b61be | ||
|
|
433a5420cb | ||
|
|
e408ea51e2 | ||
|
|
cca6492069 | ||
|
|
141d5d3731 | ||
|
|
ebdc847ef1 | ||
|
|
2ea24516d2 | ||
|
|
dd04a46403 | ||
|
|
d99ec3e9c0 | ||
|
|
f403033ab1 | ||
|
|
11ef94c134 | ||
|
|
75e46128d4 | ||
|
|
e810577317 | ||
|
|
8ba4b56dba | ||
|
|
d69069fb09 | ||
|
|
74444f8eed | ||
|
|
6c43650cd3 | ||
|
|
181ee2b4f5 | ||
|
|
5bef733647 | ||
|
|
22cef47bc4 | ||
|
|
5b25e20a2e | ||
|
|
bb5c7a222c | ||
|
|
39ea6b0587 | ||
|
|
ddc6cb5277 | ||
|
|
b13a79880b | ||
|
|
53fdc2e148 | ||
|
|
9872d2e407 | ||
|
|
3ccf6de868 | ||
|
|
12461196c3 | ||
|
|
15ec80e80d | ||
|
|
d6eb72331c | ||
|
|
eea0ced627 | ||
|
|
94684324f4 | ||
|
|
194afa2d75 | ||
|
|
d96b2ef1fb | ||
|
|
ad40ea4195 | ||
|
|
65948034dd | ||
|
|
9d47ca4f12 | ||
|
|
27c7054505 | ||
|
|
02b59ba8f7 | ||
|
|
a5727262cd | ||
|
|
43545a4e17 | ||
|
|
adbe333414 | ||
|
|
5f80ce91e8 | ||
|
|
b6fa296d5a | ||
|
|
2e170ad06f | ||
|
|
a6720db749 | ||
|
|
aa6cf72718 | ||
|
|
15066c7345 | ||
|
|
88a95aeab0 | ||
|
|
abd6502c9d | ||
|
|
50feacad13 | ||
|
|
58f83da7bb | ||
|
|
3e9a21d3b9 | ||
|
|
9780f51558 | ||
|
|
bc022c77f4 | ||
|
|
8167ef2c4a | ||
|
|
a21eee26ab | ||
|
|
7075fe2c54 | ||
|
|
e82f8911a6 | ||
|
|
b515fa80d5 | ||
|
|
affc3f51f8 | ||
|
|
6078b11200 | ||
|
|
68a9504366 | ||
|
|
67ea8e06ba | ||
|
|
9d71655273 | ||
|
|
2f0b97a487 | ||
|
|
f69b869282 | ||
|
|
bc7cbf1173 | ||
|
|
ad6e3f96e6 | ||
|
|
fe712b7ec9 | ||
|
|
6a898c1e7c | ||
|
|
0990689df5 | ||
|
|
db0e03328c | ||
|
|
edf1e341da | ||
|
|
9969896876 | ||
|
|
9c5e34df0f | ||
|
|
dca5da02a0 | ||
|
|
c5f71d18c4 | ||
|
|
84a43fb651 | ||
|
|
2858ad3f7e | ||
|
|
9ddd260b22 | ||
|
|
bc34dc9e24 | ||
|
|
edc9825c04 | ||
|
|
70a751b58d | ||
|
|
fc037ccc95 | ||
|
|
74418106de | ||
|
|
c9e8de334a | ||
|
|
13fabc2e4a | ||
|
|
8dcf7af791 | ||
|
|
2d858da0f1 | ||
|
|
ee211803e4 | ||
|
|
c3d2ad92c5 | ||
|
|
7ce7dec272 | ||
|
|
12321a8880 | ||
|
|
148ab0278e | ||
|
|
d60dcb718b | ||
|
|
e914e56c48 | ||
|
|
24fa580519 | ||
|
|
35c57a862e | ||
|
|
e0c1394c33 | ||
|
|
6370350006 | ||
|
|
ad4b40fad6 | ||
|
|
3f72de78da | ||
|
|
4a76401c34 | ||
|
|
072c54977e | ||
|
|
5c7f168370 | ||
|
|
778d27f08a | ||
|
|
12840a8324 | ||
|
|
1f24ab0742 | ||
|
|
57d220a43b | ||
|
|
bc65de5987 | ||
|
|
df4a964496 | ||
|
|
72f3a9f3cf | ||
|
|
09d93abf1a | ||
|
|
2aa236e1f9 | ||
|
|
3cefe9f9b5 | ||
|
|
e249106ddf | ||
|
|
ab3738624f | ||
|
|
25ed3632f7 | ||
|
|
3adb7ca4b2 | ||
|
|
5fbb34f8f3 | ||
|
|
20acb9002a | ||
|
|
4ee83a2b31 | ||
|
|
909a9a903f | ||
|
|
ce091fd4e4 | ||
|
|
49725c9ea4 | ||
|
|
753b44e34f | ||
|
|
1b58f2592c | ||
|
|
e9bf225c75 | ||
|
|
f287b4625d | ||
|
|
43b0e80f93 | ||
|
|
92155de98d | ||
|
|
94d7ed300e | ||
|
|
924bd9e543 | ||
|
|
c4eadda389 | ||
|
|
f563e67e19 | ||
|
|
d7e4536e57 | ||
|
|
ab033d84b1 | ||
|
|
286da11172 | ||
|
|
ac926e4458 | ||
|
|
26d2feb4d8 | ||
|
|
ffabdd348c | ||
|
|
5fc561fa63 | ||
|
|
977a4f6323 | ||
|
|
8514f35474 | ||
|
|
e05bb196f0 | ||
|
|
c7666b065a | ||
|
|
0faafbff47 | ||
|
|
34fb2f9076 | ||
|
|
a6112feddb | ||
|
|
815aac28fb | ||
|
|
79a9127c04 | ||
|
|
31249da533 | ||
|
|
f4df690d9f | ||
|
|
239cb6a92b | ||
|
|
876a4125bf | ||
|
|
ffbf6cd8b4 | ||
|
|
39e42ba735 | ||
|
|
3345ff8448 | ||
|
|
44850f1699 | ||
|
|
8a2ae6860e | ||
|
|
1717555187 | ||
|
|
dc27638025 | ||
|
|
c85cc7d668 | ||
|
|
311f7241c6 | ||
|
|
b7b93f624c | ||
|
|
084837b936 | ||
|
|
4adb425337 | ||
|
|
e11246d46e | ||
|
|
f0beedb5ba | ||
|
|
bbd39b8185 | ||
|
|
f0624ccf89 | ||
|
|
e22a070dd3 | ||
|
|
0c27111260 | ||
|
|
5225d2e97f | ||
|
|
e5f2b3079e | ||
|
|
e6df0acad9 | ||
|
|
ba527761ef | ||
|
|
ceff9dca9b | ||
|
|
e040b88bf0 | ||
|
|
7c7eff8dba | ||
|
|
377757e743 | ||
|
|
53b4b4818b | ||
|
|
07d3d6cbe7 | ||
|
|
b71838a530 | ||
|
|
b118c3b681 | ||
|
|
e5e76d784e | ||
|
|
99761b195f | ||
|
|
24e0343156 | ||
|
|
0b70aa49d0 | ||
|
|
1e2caf4c9f | ||
|
|
28eae3bd86 | ||
|
|
4b3aaa7e0c | ||
|
|
164dd00490 | ||
|
|
9bd205a527 | ||
|
|
c0fa6c414c | ||
|
|
11a661ce3a | ||
|
|
c246b4a719 | ||
|
|
17617cc421 | ||
|
|
e1a4401ffa | ||
|
|
4b3a79c110 | ||
|
|
3c3c0f70be | ||
|
|
2d470b8deb | ||
|
|
9aa0d3ddab | ||
|
|
429620c48b | ||
|
|
5a906131eb | ||
|
|
1021c9ce92 | ||
|
|
ce3f11fd40 | ||
|
|
bb91b41c64 | ||
|
|
520460669c | ||
|
|
1c7d14169e | ||
|
|
ccda264648 | ||
|
|
2ff32b230e | ||
|
|
2dbd4f168e | ||
|
|
4ecf749f7e | ||
|
|
2de9e3e9be | ||
|
|
81c38e825c | ||
|
|
fd4a4114e6 | ||
|
|
cdda89cef7 | ||
|
|
104141eec1 | ||
|
|
4d907cecab | ||
|
|
b7d3bbbd82 | ||
|
|
8a5efef622 | ||
|
|
14ad83c6b8 | ||
|
|
1c5620ba25 | ||
|
|
982b4e6625 | ||
|
|
c3f7327187 | ||
|
|
8f5afe1848 | ||
|
|
36073586e7 | ||
|
|
c89518ead4 | ||
|
|
7cd0c55847 | ||
|
|
2dd1ad53f8 | ||
|
|
d35c448058 | ||
|
|
3a00277e93 | ||
|
|
0f6f89fa5b | ||
|
|
bbd173000f | ||
|
|
5b2fce0830 | ||
|
|
96738ab9d0 | ||
|
|
7045760490 | ||
|
|
9414449408 | ||
|
|
5f3bd778c0 | ||
|
|
f66486b0c7 | ||
|
|
2c02e6c4a0 | ||
|
|
94de061587 | ||
|
|
16adbab5d4 | ||
|
|
579f998519 | ||
|
|
85a96d1e06 | ||
|
|
b2630024a7 | ||
|
|
4f7ce91e7f | ||
|
|
a9241f6710 | ||
|
|
be1306f91a | ||
|
|
10ee6cd031 | ||
|
|
e5d252290d | ||
|
|
712dcee07f | ||
|
|
dda927da18 | ||
|
|
717b90366b | ||
|
|
a76b274e1a | ||
|
|
074341e421 | ||
|
|
acd5ce51db | ||
|
|
d3ea9662bf | ||
|
|
e945955d13 | ||
|
|
566c89e9d8 | ||
|
|
8c18817e2d | ||
|
|
35bd641d2a | ||
|
|
117e88c240 | ||
|
|
b3173bdddc | ||
|
|
61a4b0744d | ||
|
|
6387bf2d2e | ||
|
|
fe6339fc77 | ||
|
|
3f8c7c8d0d | ||
|
|
543c97987b | ||
|
|
06da52acfc | ||
|
|
5a2f34d71c | ||
|
|
8b2db071d8 | ||
|
|
3a1a9121eb | ||
|
|
f80b67bc50 | ||
|
|
fe823b6172 | ||
|
|
c4c60fd330 | ||
|
|
5a5b867c73 | ||
|
|
73f125dcc7 | ||
|
|
9bded74543 | ||
|
|
7e982a6658 | ||
|
|
604b44d6d8 | ||
|
|
22bbc2457e | ||
|
|
f0ea882165 | ||
|
|
f78154d282 | ||
|
|
cd5ecf3e5a | ||
|
|
a850a1b51b | ||
|
|
6e28d7a4a5 | ||
|
|
4a06f7cfd5 | ||
|
|
c1a362f8d3 | ||
|
|
9f4afcd944 | ||
|
|
4f1136d0cd | ||
|
|
127d3acd96 | ||
|
|
ccee347740 | ||
|
|
c3a143732c | ||
|
|
dc0b3a8d45 | ||
|
|
3b751a2017 | ||
|
|
230ce81eb8 | ||
|
|
e707f9d933 | ||
|
|
64ed383128 | ||
|
|
685499a0e0 | ||
|
|
705250f23d | ||
|
|
eb680086ef | ||
|
|
58964ce317 | ||
|
|
1128eb0e84 | ||
|
|
e41cbf5f32 | ||
|
|
f2b8ff9e8d | ||
|
|
cc3494437a | ||
|
|
88eada3494 | ||
|
|
37faccd014 | ||
|
|
30e6a258ad | ||
|
|
d24bc75519 | ||
|
|
92de467edc | ||
|
|
9b1d03fcfe | ||
|
|
4eda01949d | ||
|
|
062d18d5fa | ||
|
|
04a54598b3 | ||
|
|
79dcc6d4f1 | ||
|
|
57a74c156b | ||
|
|
6882e5c59f | ||
|
|
f7f8060447 | ||
|
|
53461d7515 | ||
|
|
9b87f4ba5e | ||
|
|
5e13549f74 | ||
|
|
87a21f5f5e | ||
|
|
9246e00163 | ||
|
|
1fd726eed7 | ||
|
|
bc7e59c9d7 | ||
|
|
179eaef29d | ||
|
|
f50ef4c23c | ||
|
|
86d6b67337 | ||
|
|
30ad28ffd1 | ||
|
|
69f6532b8d | ||
|
|
91d1b8d2cd | ||
|
|
baf9661530 | ||
|
|
2141046da9 | ||
|
|
266ad27998 | ||
|
|
f4a925a750 | ||
|
|
30ed563504 | ||
|
|
d347b28481 | ||
|
|
f8ae291090 | ||
|
|
45d1f27fb4 | ||
|
|
1a8267d55a | ||
|
|
b822e255d8 | ||
|
|
037b76750a | ||
|
|
e949f2f8d2 | ||
|
|
88c91d4752 | ||
|
|
3d7dbd6371 | ||
|
|
b3db1ba10b | ||
|
|
16f68d6b1b | ||
|
|
64da3465b8 | ||
|
|
12d256c7c8 | ||
|
|
3e32e01c30 | ||
|
|
ac3677d9ae | ||
|
|
0462cee9db | ||
|
|
cab95c5eed | ||
|
|
4205e49786 | ||
|
|
e755f0befa | ||
|
|
3e08e7aafa | ||
|
|
9f6ad864ed | ||
|
|
ec56b2760a | ||
|
|
e5a138d854 | ||
|
|
ad256e0b00 | ||
|
|
162b1b93db | ||
|
|
0ec66ba210 | ||
|
|
3d29967a8d | ||
|
|
5d3c00717a | ||
|
|
dc7fd74327 | ||
|
|
5b26017a8a | ||
|
|
3651852430 | ||
|
|
6bd454f77f | ||
|
|
08f3ad504c | ||
|
|
2f8ebc29e8 | ||
|
|
2856928b57 | ||
|
|
afbee641b1 | ||
|
|
95bbd67dea | ||
|
|
a3de0b2a59 | ||
|
|
719fec0a45 | ||
|
|
2fa09dbd95 | ||
|
|
3f3430de3f | ||
|
|
68eaf46d7c | ||
|
|
65e550a61c | ||
|
|
4da0c64583 | ||
|
|
0603a55492 | ||
|
|
a37b527bbd | ||
|
|
0af558345f | ||
|
|
44f815409d | ||
|
|
6e7f39edee | ||
|
|
6c84c0eca6 | ||
|
|
d90e658e3b | ||
|
|
85f3a71dd5 | ||
|
|
38ddfc0b61 | ||
|
|
ff01a197f3 | ||
|
|
6c44bbb53e | ||
|
|
f3f0157d3c | ||
|
|
4e81c7f724 | ||
|
|
51b9cd4e5a | ||
|
|
ecf59ac7d9 | ||
|
|
0941bd0fe5 | ||
|
|
8ebaf92006 | ||
|
|
62cd5e9c57 | ||
|
|
9192e2039a | ||
|
|
1885e2093b | ||
|
|
d2c36b8449 | ||
|
|
80c9f7223a | ||
|
|
c15d0dc672 | ||
|
|
0f7fa32d12 | ||
|
|
6933c278ce | ||
|
|
7fc717a839 | ||
|
|
4e435e6199 | ||
|
|
366ec16a63 | ||
|
|
04c9694ffa | ||
|
|
f7682c289a | ||
|
|
4b4a75475a | ||
|
|
55703b61b7 | ||
|
|
8545d15c85 | ||
|
|
87aac8cf03 | ||
|
|
f9977332a6 | ||
|
|
2dca056526 | ||
|
|
dd31a7ef93 | ||
|
|
a25c93b28e | ||
|
|
c0aec7d8f4 | ||
|
|
03ed8b7304 | ||
|
|
504bf02855 | ||
|
|
4d9ab81ef4 | ||
|
|
f3413815a9 | ||
|
|
bae19cb130 | ||
|
|
db501f689f | ||
|
|
b8561cab0a | ||
|
|
50f9baedba | ||
|
|
5d5e4ede1a | ||
|
|
8d78a5aa95 | ||
|
|
14839dc54e | ||
|
|
7912d268e9 | ||
|
|
8907a990a1 | ||
|
|
891ee3e789 | ||
|
|
037788f562 | ||
|
|
13d9c84dc9 | ||
|
|
e23144aecd | ||
|
|
23c77e8508 | ||
|
|
f08e2ece93 | ||
|
|
f64310320a | ||
|
|
695b7b793c | ||
|
|
986f92d0db | ||
|
|
2e4614cbc4 | ||
|
|
16aa04d539 | ||
|
|
9d95154b85 | ||
|
|
f8e1ab8f31 | ||
|
|
2d74c93232 | ||
|
|
17687fdea3 | ||
|
|
77b3d45e0e | ||
|
|
37196f1f0e | ||
|
|
b3b71d6f02 | ||
|
|
90b90e667a | ||
|
|
a094fb8104 | ||
|
|
96bcfff28b | ||
|
|
0ad469178a | ||
|
|
b3ca872cef | ||
|
|
3baed05728 | ||
|
|
aed370c7ce | ||
|
|
b9a7ce17fe | ||
|
|
2e0f765190 | ||
|
|
66df99f5fd | ||
|
|
7c3c68b84b | ||
|
|
6109067275 | ||
|
|
3369536cb8 | ||
|
|
fcbd255e99 | ||
|
|
f49948506b | ||
|
|
a11a20411b | ||
|
|
5e2f1b3fac | ||
|
|
75a14b8b45 | ||
|
|
b0c1714777 | ||
|
|
361ead59d3 | ||
|
|
41c5688fca | ||
|
|
c8dc037e94 | ||
|
|
a213ee960a | ||
|
|
3043296e52 | ||
|
|
be9a16e8a2 | ||
|
|
5e2dd589a1 | ||
|
|
9959578cbe | ||
|
|
971e91238a | ||
|
|
3143dc1e84 | ||
|
|
8898496eba | ||
|
|
7eb00bd24c | ||
|
|
a098bab114 | ||
|
|
7f6fd02d06 | ||
|
|
75e57baf6c | ||
|
|
68d8d95266 | ||
|
|
7d78cba38c | ||
|
|
7801575d99 | ||
|
|
1104bd821b | ||
|
|
b8b70e772e | ||
|
|
64c906b801 | ||
|
|
72ccd44681 | ||
|
|
7ca5e14938 | ||
|
|
6b6b3b724a | ||
|
|
86b50cc8ab | ||
|
|
10a27e7a27 | ||
|
|
edabd1bae7 | ||
|
|
f1b1da60ea | ||
|
|
0402dd521a | ||
|
|
11a5864969 | ||
|
|
da28db2ca4 | ||
|
|
c991de7ccd | ||
|
|
d9a3b6523c | ||
|
|
ea2651e39b | ||
|
|
a980cd9406 | ||
|
|
1c58b3a087 | ||
|
|
065d90c3f7 | ||
|
|
6c64965703 | ||
|
|
134829cbf2 | ||
|
|
aff3bff8b0 | ||
|
|
2ea84cd07e | ||
|
|
996b9e7e7b | ||
|
|
009ae02e32 | ||
|
|
552a37122a | ||
|
|
6ed1f4662d | ||
|
|
5f5f497cbd | ||
|
|
120fe63c0f | ||
|
|
181b165a4b | ||
|
|
dafd7e7b42 | ||
|
|
d9cdc8d26c | ||
|
|
d0722349e6 | ||
|
|
2ef80a5d34 | ||
|
|
e7761abf3c | ||
|
|
3905834807 | ||
|
|
60a58d4d8d | ||
|
|
8c2a77e84e | ||
|
|
e35facbb15 | ||
|
|
8528969efd | ||
|
|
9dffde74b2 | ||
|
|
b39f38350f | ||
|
|
f4daa9f527 | ||
|
|
2c745db544 | ||
|
|
3b37898eb2 | ||
|
|
aa2ea05af9 | ||
|
|
7316c1c46c | ||
|
|
38cb348201 | ||
|
|
be664c49c8 | ||
|
|
f09738678e | ||
|
|
0c35d9b057 | ||
|
|
cb9774bbd2 | ||
|
|
dd0edc1394 | ||
|
|
be44249c05 | ||
|
|
efb5019176 | ||
|
|
5153d35bb5 | ||
|
|
036f16b9ef | ||
|
|
436986d182 | ||
|
|
5d053128ac | ||
|
|
b5c5ecc074 | ||
|
|
56308fcbd1 | ||
|
|
628bdab21b | ||
|
|
df0397b10b | ||
|
|
334bfeecea | ||
|
|
3cf0257f34 | ||
|
|
6382fc11af | ||
|
|
b26067b0a0 | ||
|
|
947eac52f6 | ||
|
|
4c60409aaf | ||
|
|
eb5c588af9 | ||
|
|
3b6ad957e3 | ||
|
|
4acbf2e9a0 | ||
|
|
50f7a1a99c | ||
|
|
515f08d2da | ||
|
|
58ab77d237 | ||
|
|
fdbdf6f167 | ||
|
|
30c4a4786b | ||
|
|
a34d3bbab4 | ||
|
|
2310920e79 | ||
|
|
85f34602f4 | ||
|
|
4b016b441c | ||
|
|
27e3be3778 | ||
|
|
4954687a52 | ||
|
|
bb60107a3b | ||
|
|
ed18ead1ff | ||
|
|
e58506f1a5 | ||
|
|
08293c8721 | ||
|
|
6389a06903 | ||
|
|
8cf444ef31 | ||
|
|
1c8af28f26 | ||
|
|
9b472d62a9 | ||
|
|
f5dfc264ba | ||
|
|
8a70efafb5 | ||
|
|
b1c26aaa95 | ||
|
|
1a88996c47 | ||
|
|
3400c2c141 | ||
|
|
80bc9604ba | ||
|
|
669148bca0 | ||
|
|
26df3579a3 | ||
|
|
3c0ebd54ec | ||
|
|
b877bab48f | ||
|
|
e6e07e8f3f | ||
|
|
3f7261d7d9 | ||
|
|
26f16a3734 | ||
|
|
88fc1a6d24 | ||
|
|
2fc1d3cae6 | ||
|
|
28a67cad0d | ||
|
|
2bb0602ebb | ||
|
|
d82333519c | ||
|
|
5421bd8b1d | ||
|
|
f0c7e9bdfa | ||
|
|
0525515bee | ||
|
|
f5ad14b109 | ||
|
|
323d321a77 | ||
|
|
56833edae7 | ||
|
|
d57b5a320e | ||
|
|
563ad18f4e | ||
|
|
b4dc021381 | ||
|
|
ebda52b18b | ||
|
|
16ee1a825c | ||
|
|
a4f5ce08b9 | ||
|
|
aa41e4d2ce | ||
|
|
b8effaf42c | ||
|
|
0cbc529640 | ||
|
|
a8d4668c4d | ||
|
|
2de9912ac0 | ||
|
|
2038517861 | ||
|
|
33bf668697 | ||
|
|
2736e37e4a | ||
|
|
e4e1451eaa | ||
|
|
7c872d2d9e | ||
|
|
ab31040d5f | ||
|
|
45e1b5bce0 | ||
|
|
42d8f31eba | ||
|
|
c6a6230a56 | ||
|
|
a14de25661 | ||
|
|
09893fd728 | ||
|
|
e87bc3a83e | ||
|
|
6da9428d85 | ||
|
|
15bc6a1509 | ||
|
|
a4bf3c8088 | ||
|
|
6472c28473 | ||
|
|
f49f2af6cd | ||
|
|
061b9ba6c2 | ||
|
|
d4f8426ae4 | ||
|
|
ad974cfa0a | ||
|
|
e1fae8927f | ||
|
|
9ab85fd0b8 | ||
|
|
e0c9a71cd8 | ||
|
|
d44d87ee7f | ||
|
|
2b3f31700b | ||
|
|
12cb3e512f | ||
|
|
92706caddc | ||
|
|
893b2500c2 | ||
|
|
ef2da1902e | ||
|
|
9ce4990793 | ||
|
|
f274bec8fc | ||
|
|
00eb26a34f | ||
|
|
e42747f4e3 | ||
|
|
5ed3301cf5 | ||
|
|
442ab318fd | ||
|
|
6b16bb3ea1 | ||
|
|
d7f9afb3ba | ||
|
|
80ab4b5393 | ||
|
|
69b20aee05 | ||
|
|
38a018ebfa | ||
|
|
6695ca3f34 | ||
|
|
eb357fa58a | ||
|
|
e32494f684 | ||
|
|
39ec32b2ef | ||
|
|
d8b37a8d81 | ||
|
|
df2f623599 | ||
|
|
73357988e6 | ||
|
|
03c247a9ff | ||
|
|
092ce61908 | ||
|
|
b9d7674b20 | ||
|
|
8e3df5a38f | ||
|
|
23f9855768 | ||
|
|
2e76c874cc | ||
|
|
41403286d1 | ||
|
|
c3bc5fb0ff | ||
|
|
94ae9a95d7 | ||
|
|
bcffd70270 | ||
|
|
f08163fd72 | ||
|
|
c9d469fb91 | ||
|
|
25c88a1c6c | ||
|
|
7decb58bf7 | ||
|
|
d1ec19b30b | ||
|
|
74468cf799 | ||
|
|
411a6b1e49 | ||
|
|
e228165ff9 | ||
|
|
d615514799 | ||
|
|
ed717351cf | ||
|
|
4658244686 | ||
|
|
48ae5c69f4 | ||
|
|
4d065d577b |
4
.github/CODEOWNERS
vendored
Normal file
4
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Jonathan Rubenstein (JJRcop)
|
||||
# - Primary documentation manager. Does not require direct approval for every
|
||||
# - change, but should be consulted for large additions and changes.
|
||||
docs/ jrubcop@gmail.com
|
||||
84
.github/workflows/build.yml
vendored
84
.github/workflows/build.yml
vendored
@@ -4,15 +4,23 @@ jobs:
|
||||
client:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
cc: [gcc, clang]
|
||||
build_type:
|
||||
- Release
|
||||
- Debug
|
||||
compiler:
|
||||
- {cc: gcc, cxx: g++}
|
||||
- {cc: clang, cxx: clang++}
|
||||
wayland_shell: [xdg-shell, libdecor]
|
||||
build_type: [Release, Debug]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install libdecor PPA
|
||||
run: sudo add-apt-repository ppa:christianrauch/libdecoration
|
||||
if: ${{ matrix.wayland_shell == 'libdecor' }}
|
||||
- name: Install PipeWire repository
|
||||
run: |
|
||||
echo 'deb [trusted=yes] https://pipewire-ubuntu.quantum5.workers.dev ./' | sudo tee /etc/apt/sources.list.d/pipewire.list
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -20,26 +28,41 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get install \
|
||||
binutils-dev \
|
||||
libsdl2-dev libsdl2-ttf-dev \
|
||||
libspice-protocol-dev nettle-dev \
|
||||
libx11-dev libxss-dev libxi-dev \
|
||||
wayland-protocols
|
||||
libgl-dev libgles-dev \
|
||||
libx11-dev libxss-dev libxi-dev libxinerama-dev libxcursor-dev libxpresent-dev \
|
||||
libwayland-dev libxkbcommon-dev \
|
||||
libsamplerate0-dev libpipewire-0.3-dev libpulse-dev \
|
||||
$([ '${{ matrix.wayland_shell }}' = libdecor ] && echo 'libdecor-0-dev libdbus-1-dev') \
|
||||
$([ '${{ matrix.compiler.cc }}' = clang ] && echo 'clang-tools')
|
||||
sudo pip3 install pyenchant
|
||||
- name: Configure client
|
||||
env:
|
||||
CC: /usr/bin/${{ matrix.cc }}
|
||||
CC: /usr/bin/${{ matrix.compiler.cc }}
|
||||
CXX: /usr/bin/${{ matrix.compiler.cxx }}
|
||||
run: |
|
||||
mkdir client/build
|
||||
cd client/build
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_SDL=ON ..
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
|
||||
-DCMAKE_LINKER:FILEPATH=/usr/bin/ld \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DENABLE_LIBDECOR=${{ matrix.wayland_shell == 'libdecor' }} \
|
||||
..
|
||||
- name: Build client
|
||||
run: |
|
||||
cd client/build
|
||||
make -j$(nproc)
|
||||
- name: Checking help spelling
|
||||
run: ./client/build/looking-glass-client --help | ./doc/lgspell.py
|
||||
- name: Check GL function calls
|
||||
if: matrix.compiler.cc == 'clang'
|
||||
run: WAYLAND_SHELL='${{ matrix.wayland_shell }}' ./gl-check
|
||||
|
||||
module:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build kernel module
|
||||
@@ -50,15 +73,19 @@ jobs:
|
||||
host-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install PipeWire repository
|
||||
run: |
|
||||
echo 'deb [trusted=yes] https://pipewire-ubuntu.quantum5.workers.dev ./' | sudo tee /etc/apt/sources.list.d/pipewire.list
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install Linux host dependencies
|
||||
run: |
|
||||
sudo apt-get install binutils-dev libgl1-mesa-dev libxcb-xfixes0-dev
|
||||
sudo apt-get install binutils-dev libxcb-xfixes0-dev \
|
||||
libpipewire-0.3-dev
|
||||
- name: Configure Linux host
|
||||
run: |
|
||||
mkdir host/build
|
||||
@@ -72,7 +99,7 @@ jobs:
|
||||
host-windows-cross:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
@@ -94,11 +121,16 @@ jobs:
|
||||
run: |
|
||||
cd host/build
|
||||
makensis platform/Windows/installer.nsi
|
||||
- name: Build Windows host installer with IVSHMEM drivers
|
||||
run: |
|
||||
cd host/build
|
||||
curl https://dl.quantum2.xyz/ivshmem.tar.gz | tar xz
|
||||
makensis -DIVSHMEM platform/Windows/installer.nsi
|
||||
|
||||
host-windows-native:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Configure Windows host for native MinGW-w64
|
||||
@@ -113,7 +145,7 @@ jobs:
|
||||
- name: Build Windows host installer
|
||||
run: |
|
||||
cd host\build
|
||||
makensis platform\Windows\installer.nsi
|
||||
makensis -DBUILD_32BIT platform\Windows\installer.nsi
|
||||
|
||||
obs:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -121,7 +153,7 @@ jobs:
|
||||
matrix:
|
||||
cc: [gcc, clang]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
@@ -139,3 +171,21 @@ jobs:
|
||||
run: |
|
||||
cd obs/build
|
||||
make -j$(nproc)
|
||||
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install docs dependencies
|
||||
run: |
|
||||
sudo apt-get install python3-sphinx python3-sphinx-rtd-theme
|
||||
sudo pip3 install sphinxcontrib-spelling
|
||||
- name: Build docs
|
||||
run: |
|
||||
cd doc
|
||||
make dirhtml SPHINXOPTS='-b spelling -W' -j$(nproc)
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ module/modules.order
|
||||
*.o
|
||||
*.exe
|
||||
*/build
|
||||
__pycache__
|
||||
*.py[co]
|
||||
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -4,3 +4,12 @@
|
||||
[submodule "repos/PureSpice"]
|
||||
path = repos/PureSpice
|
||||
url = https://github.com/gnif/PureSpice
|
||||
[submodule "repos/cimgui"]
|
||||
path = repos/cimgui
|
||||
url = https://github.com/cimgui/cimgui.git
|
||||
[submodule "repos/wayland-protocols"]
|
||||
path = repos/wayland-protocols
|
||||
url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git
|
||||
[submodule "repos/nanosvg"]
|
||||
path = repos/nanosvg
|
||||
url = https://github.com/memononen/nanosvg.git
|
||||
|
||||
14
AUTHORS
14
AUTHORS
@@ -9,7 +9,7 @@ arcnmx <arcnmx@users.noreply.github.com> (arcnmx)
|
||||
TheCakeIsNaOH <TheCakeIsNaOH@gmail.com> (TheCakeIsNaOH)
|
||||
NamoDev <namodev@users.noreply.github.com> (NamoDev)
|
||||
feltcat <58396817+feltcat@users.noreply.github.com> (feltcat)
|
||||
Ali Abdel-Qader <abdelqaderali@protonmail.com>
|
||||
Ali Abdel-Qader <abdelqaderali@protonmail.com> (thrifty-txt)
|
||||
Jack Karamanian <karamanian.jack@gmail.com>
|
||||
Mikko Rasa <tdb@tdb.fi> (DataBeaver)
|
||||
Omar Pakker <Omar007@users.noreply.github.com> (Omar007)
|
||||
@@ -53,3 +53,15 @@ orcephrye <drakethebanditi@yahoo.com> (orcephrye)
|
||||
thejavascriptman <thejavascriptman@outlook.com> (thejavascriptman)
|
||||
vroad <396351+vroad@users.noreply.github.com> (vroad)
|
||||
williamvds <w.vigolodasilva@gmail.com> (williamvds)
|
||||
SytheZN <sythe.zn@gmail.com> (SytheZN)
|
||||
RTXUX <wyf@rtxux.xyz> (RTXUX)
|
||||
Vincent LaRocca <vincentmlarocca@gmail.com> (VMFortress)
|
||||
Johnathon Paul Weaver <weaver123_johnathon@hotmail.com> (8BallBomBom)
|
||||
Chris Spencer <spencercw@gmail.com> (spencercw)
|
||||
Mark Boorer <markboo99@gmail.com> (Shootfast)
|
||||
babbaj <babbaj45@gmail.com> (Babbaj)
|
||||
Matthew McMullin <matthew@mcmullin.one> (matthewjmc)
|
||||
Leonard Fricke <leonard.fricke98@gmail.com> (Leo1998)
|
||||
David Meier <meier_david_91@hotmail.com> (Kenny.ch)
|
||||
Daniel Cordero <looking-glass@0xdc.io> (0xdc)
|
||||
esi <git@esibun.net> (esibun)
|
||||
|
||||
@@ -19,4 +19,4 @@ https://looking-glass.io/downloads
|
||||
Source code for the documentation can be found in the `/doc` directory.
|
||||
|
||||
You may view this locally as HTML by running `make html` with `python3-sphinx`
|
||||
installed.
|
||||
and `python3-sphinx-rtd-theme` installed.
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(looking-glass-client C)
|
||||
project(looking-glass-client C CXX)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
|
||||
message(FATAL_ERROR
|
||||
"\n"
|
||||
"In-source builds are not supported\n"
|
||||
"See build instructions provided in: "
|
||||
"${PROJECT_TOP}/doc/build.rst\n"
|
||||
"Refusing to continue"
|
||||
)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_TOP}/cmake/" "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
|
||||
include(CheckSubmodule)
|
||||
include(GNUInstallDirs)
|
||||
include(CheckCCompilerFlag)
|
||||
include(FeatureSummary)
|
||||
|
||||
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" ON)
|
||||
if(OPTIMIZE_FOR_NATIVE)
|
||||
CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
if(COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
add_compile_options("-march=native")
|
||||
endif()
|
||||
else()
|
||||
add_compile_options("-march=nehalem" "-mtune=generic")
|
||||
endif()
|
||||
set(OPTIMIZE_FOR_NATIVE_DEFAULT ON)
|
||||
include(OptimizeForNative) # option(OPTIMIZE_FOR_NATIVE)
|
||||
include(UninstallTarget)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(FONTCONFIG REQUIRED IMPORTED_TARGET fontconfig)
|
||||
|
||||
option(ENABLE_OPENGL "Enable the OpenGL renderer" ON)
|
||||
add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.")
|
||||
@@ -32,9 +42,6 @@ add_feature_info(ENABLE_ASAN ENABLE_ASAN "AddressSanitizer support.")
|
||||
option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF)
|
||||
add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.")
|
||||
|
||||
option(ENABLE_SDL "Build with SDL support" OFF)
|
||||
add_feature_info(ENABLE_SDL ENABLE_SDL "SDL support.")
|
||||
|
||||
option(ENABLE_X11 "Build with X11 support" ON)
|
||||
add_feature_info(ENABLE_X11 ENABLE_X11 "X11 support.")
|
||||
|
||||
@@ -44,8 +51,14 @@ add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
|
||||
option(ENABLE_LIBDECOR "Build with libdecor support" OFF)
|
||||
add_feature_info(ENABLE_LIBDECOR ENABLE_LIBDECOR "libdecor support.")
|
||||
|
||||
if (NOT ENABLE_SDL AND NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
|
||||
message(FATAL_ERROR "One of ENABLE_SDL, ENABLE_X11, or ENABLE_WAYLAND must be on")
|
||||
option(ENABLE_PIPEWIRE "Build with PipeWire audio output support" ON)
|
||||
add_feature_info(ENABLE_PIPEWIRE ENABLE_PIPEWIRE "PipeWire audio support.")
|
||||
|
||||
option(ENABLE_PULSEAUDIO "Build with PulseAudio audio output support" ON)
|
||||
add_feature_info(ENABLE_PULSEAUDIO ENABLE_PULSEAUDIO "PulseAudio audio support.")
|
||||
|
||||
if (NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
|
||||
message(FATAL_ERROR "Either ENABLE_X11 or ENABLE_WAYLAND must be on")
|
||||
endif()
|
||||
|
||||
add_compile_options(
|
||||
@@ -53,6 +66,7 @@ add_compile_options(
|
||||
"-Wextra"
|
||||
"-Wno-sign-compare"
|
||||
"-Wno-unused-parameter"
|
||||
"$<$<COMPILE_LANGUAGE:C>:-Wstrict-prototypes>"
|
||||
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
|
||||
"-Werror"
|
||||
"-Wfatal-errors"
|
||||
@@ -83,68 +97,97 @@ if(ENABLE_UBSAN)
|
||||
set(EXE_FLAGS "${EXE_FLAGS} -fsanitize=undefined")
|
||||
endif()
|
||||
|
||||
find_package(GMP)
|
||||
|
||||
add_definitions(-D ATOMIC_LOCKING)
|
||||
add_definitions(-D GL_GLEXT_PROTOTYPES)
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/version.c
|
||||
${CMAKE_BINARY_DIR}/_version.c
|
||||
COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P
|
||||
${PROJECT_TOP}/version.cmake
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/version.c
|
||||
${CMAKE_BINARY_DIR}/_version.c
|
||||
COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P
|
||||
${PROJECT_TOP}/version.cmake
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${GMP_INCLUDE_DIR}
|
||||
${PROJECT_TOP}
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${PROJECT_TOP}/repos/nanosvg/src
|
||||
)
|
||||
|
||||
link_libraries(
|
||||
${GMP_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
rt
|
||||
m
|
||||
${CMAKE_DL_LIBS}
|
||||
rt
|
||||
m
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
${CMAKE_BINARY_DIR}/version.c
|
||||
src/main.c
|
||||
src/core.c
|
||||
src/app.c
|
||||
src/config.c
|
||||
src/keybind.c
|
||||
src/lg-renderer.c
|
||||
src/ll.c
|
||||
src/util.c
|
||||
src/clipboard.c
|
||||
src/kb.c
|
||||
src/egl_dynprocs.c
|
||||
${CMAKE_BINARY_DIR}/version.c
|
||||
src/main.c
|
||||
src/core.c
|
||||
src/app.c
|
||||
src/audio.c
|
||||
src/config.c
|
||||
src/keybind.c
|
||||
src/util.c
|
||||
src/clipboard.c
|
||||
src/kb.c
|
||||
src/gl_dynprocs.c
|
||||
src/egl_dynprocs.c
|
||||
src/eglutil.c
|
||||
src/overlay_utils.c
|
||||
src/render_queue.c
|
||||
|
||||
src/overlay/splash.c
|
||||
src/overlay/alert.c
|
||||
src/overlay/fps.c
|
||||
src/overlay/graphs.c
|
||||
src/overlay/help.c
|
||||
src/overlay/config.c
|
||||
src/overlay/msg.c
|
||||
src/overlay/status.c
|
||||
)
|
||||
|
||||
# Force cimgui to build as a static library.
|
||||
set(IMGUI_STATIC "yes" CACHE STRING "Build as a static library")
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/resources" "${CMAKE_BINARY_DIR}/resources")
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" )
|
||||
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" )
|
||||
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
|
||||
add_subdirectory("${PROJECT_TOP}/repos/cimgui" "${CMAKE_BINARY_DIR}/cimgui" EXCLUDE_FROM_ALL)
|
||||
|
||||
add_subdirectory(displayservers)
|
||||
add_subdirectory(renderers)
|
||||
add_subdirectory(fonts)
|
||||
|
||||
add_executable(looking-glass-client ${SOURCES})
|
||||
|
||||
target_compile_definitions(looking-glass-client PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1)
|
||||
|
||||
target_link_libraries(looking-glass-client
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
displayservers
|
||||
lgmp
|
||||
purespice
|
||||
renderers
|
||||
fonts
|
||||
${EXE_FLAGS}
|
||||
PkgConfig::FONTCONFIG
|
||||
lg_resources
|
||||
lg_common
|
||||
displayservers
|
||||
lgmp
|
||||
purespice
|
||||
renderers
|
||||
cimgui
|
||||
)
|
||||
|
||||
if (ENABLE_PIPEWIRE OR ENABLE_PULSEAUDIO)
|
||||
add_definitions(-D ENABLE_AUDIO)
|
||||
add_subdirectory(audiodevs)
|
||||
pkg_check_modules(SAMPLERATE REQUIRED IMPORTED_TARGET samplerate)
|
||||
target_link_libraries(looking-glass-client
|
||||
PkgConfig::SAMPLERATE
|
||||
audiodevs
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS looking-glass-client
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT binary)
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT binary)
|
||||
|
||||
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)
|
||||
|
||||
46
client/audiodevs/CMakeLists.txt
Normal file
46
client/audiodevs/CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(audiodevs LANGUAGES C)
|
||||
|
||||
set(AUDIODEV_H "${CMAKE_BINARY_DIR}/include/dynamic/audiodev.h")
|
||||
set(AUDIODEV_C "${CMAKE_BINARY_DIR}/src/audiodev.c")
|
||||
|
||||
file(WRITE ${AUDIODEV_H} "#include \"interface/audiodev.h\"\n\n")
|
||||
file(APPEND ${AUDIODEV_H} "extern struct LG_AudioDevOps * LG_AudioDevs[];\n\n")
|
||||
|
||||
file(WRITE ${AUDIODEV_C} "#include \"interface/audiodev.h\"\n\n")
|
||||
file(APPEND ${AUDIODEV_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(AUDIODEVS "_")
|
||||
set(AUDIODEVS_LINK "_")
|
||||
function(add_audiodev name)
|
||||
set(AUDIODEVS "${AUDIODEVS};${name}" PARENT_SCOPE)
|
||||
set(AUDIODEVS_LINK "${AUDIODEVS_LINK};audiodev_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove audiodevs here!
|
||||
if(ENABLE_PIPEWIRE)
|
||||
add_audiodev(PipeWire)
|
||||
endif()
|
||||
if(ENABLE_PULSEAUDIO)
|
||||
add_audiodev(PulseAudio)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT AUDIODEVS 0)
|
||||
list(REMOVE_AT AUDIODEVS_LINK 0)
|
||||
|
||||
list(LENGTH AUDIODEVS AUDIODEV_COUNT)
|
||||
file(APPEND ${AUDIODEV_H} "#define LG_AUDIODEV_COUNT ${AUDIODEV_COUNT}\n")
|
||||
|
||||
foreach(audiodev ${AUDIODEVS})
|
||||
file(APPEND ${AUDIODEV_C} "extern struct LG_AudioDevOps LGAD_${audiodev};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${AUDIODEV_C} "\nconst struct LG_AudioDevOps * LG_AudioDevs[] =\n{\n")
|
||||
foreach(audiodev ${AUDIODEVS})
|
||||
file(APPEND ${AUDIODEV_C} " &LGAD_${audiodev},\n")
|
||||
endforeach()
|
||||
file(APPEND ${AUDIODEV_C} " NULL\n};")
|
||||
|
||||
add_library(audiodevs STATIC ${AUDIODEV_C})
|
||||
target_link_libraries(audiodevs ${AUDIODEVS_LINK})
|
||||
21
client/audiodevs/PipeWire/CMakeLists.txt
Normal file
21
client/audiodevs/PipeWire/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(audiodev_PipeWire LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(AUDIODEV_PipeWire REQUIRED IMPORTED_TARGET
|
||||
libpipewire-0.3
|
||||
)
|
||||
|
||||
add_library(audiodev_PipeWire STATIC
|
||||
pipewire.c
|
||||
)
|
||||
|
||||
target_link_libraries(audiodev_PipeWire
|
||||
PkgConfig::AUDIODEV_PipeWire
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(audiodev_PipeWire
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
565
client/audiodevs/PipeWire/pipewire.c
Normal file
565
client/audiodevs/PipeWire/pipewire.c
Normal file
@@ -0,0 +1,565 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/audiodev.h"
|
||||
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/stringutils.h"
|
||||
#include "common/util.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STREAM_STATE_INACTIVE,
|
||||
STREAM_STATE_ACTIVE,
|
||||
STREAM_STATE_DRAINING
|
||||
}
|
||||
StreamState;
|
||||
|
||||
struct PipeWire
|
||||
{
|
||||
struct pw_loop * loop;
|
||||
struct pw_context * context;
|
||||
struct pw_thread_loop * thread;
|
||||
|
||||
struct
|
||||
{
|
||||
struct pw_stream * stream;
|
||||
struct spa_io_rate_match * rateMatch;
|
||||
|
||||
int channels;
|
||||
int sampleRate;
|
||||
int stride;
|
||||
LG_AudioPullFn pullFn;
|
||||
int maxPeriodFrames;
|
||||
int startFrames;
|
||||
|
||||
StreamState state;
|
||||
}
|
||||
playback;
|
||||
|
||||
struct
|
||||
{
|
||||
struct pw_stream * stream;
|
||||
|
||||
int channels;
|
||||
int sampleRate;
|
||||
int stride;
|
||||
LG_AudioPushFn pushFn;
|
||||
|
||||
bool active;
|
||||
}
|
||||
record;
|
||||
};
|
||||
|
||||
static struct PipeWire pw = {0};
|
||||
|
||||
static void pipewire_onPlaybackIoChanged(void * userdata, uint32_t id,
|
||||
void * data, uint32_t size)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case SPA_IO_RateMatch:
|
||||
pw.playback.rateMatch = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void pipewire_onPlaybackProcess(void * userdata)
|
||||
{
|
||||
struct pw_buffer * pbuf;
|
||||
|
||||
if (!(pbuf = pw_stream_dequeue_buffer(pw.playback.stream)))
|
||||
{
|
||||
DEBUG_WARN("out of buffers");
|
||||
return;
|
||||
}
|
||||
|
||||
struct spa_buffer * sbuf = pbuf->buffer;
|
||||
uint8_t * dst;
|
||||
|
||||
if (!(dst = sbuf->datas[0].data))
|
||||
return;
|
||||
|
||||
int frames = sbuf->datas[0].maxsize / pw.playback.stride;
|
||||
if (pw.playback.rateMatch && pw.playback.rateMatch->size > 0)
|
||||
frames = min(frames, pw.playback.rateMatch->size);
|
||||
|
||||
frames = pw.playback.pullFn(dst, frames);
|
||||
if (!frames)
|
||||
{
|
||||
sbuf->datas[0].chunk->size = 0;
|
||||
pw_stream_queue_buffer(pw.playback.stream, pbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
sbuf->datas[0].chunk->offset = 0;
|
||||
sbuf->datas[0].chunk->stride = pw.playback.stride;
|
||||
sbuf->datas[0].chunk->size = frames * pw.playback.stride;
|
||||
|
||||
pw_stream_queue_buffer(pw.playback.stream, pbuf);
|
||||
}
|
||||
|
||||
static void pipewire_onPlaybackDrained(void * userdata)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_active(pw.playback.stream, false);
|
||||
pw.playback.state = STREAM_STATE_INACTIVE;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static bool pipewire_init(void)
|
||||
{
|
||||
pw_init(NULL, NULL);
|
||||
|
||||
pw.loop = pw_loop_new(NULL);
|
||||
pw.context = pw_context_new(
|
||||
pw.loop,
|
||||
pw_properties_new(
|
||||
// Request real-time priority on the PipeWire threads
|
||||
PW_KEY_CONFIG_NAME, "client-rt.conf",
|
||||
NULL
|
||||
),
|
||||
0);
|
||||
if (!pw.context)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create a context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* this is just to test for PipeWire availabillity */
|
||||
struct pw_core * core = pw_context_connect(pw.context, NULL, 0);
|
||||
if (!core)
|
||||
goto err_context;
|
||||
|
||||
/* PipeWire is available so create the loop thread and start it */
|
||||
pw.thread = pw_thread_loop_new_full(pw.loop, "PipeWire", NULL);
|
||||
if (!pw.thread)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the thread loop");
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
pw_thread_loop_start(pw.thread);
|
||||
return true;
|
||||
|
||||
err_context:
|
||||
pw_context_destroy(pw.context);
|
||||
|
||||
err:
|
||||
pw_loop_destroy(pw.loop);
|
||||
pw_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void pipewire_playbackStopStream(void)
|
||||
{
|
||||
if (!pw.playback.stream)
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_destroy(pw.playback.stream);
|
||||
pw.playback.stream = NULL;
|
||||
pw.playback.rateMatch = NULL;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_playbackSetup(int channels, int sampleRate,
|
||||
int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames,
|
||||
LG_AudioPullFn pullFn)
|
||||
{
|
||||
const struct spa_pod * params[1];
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
static const struct pw_stream_events events =
|
||||
{
|
||||
.version = PW_VERSION_STREAM_EVENTS,
|
||||
.io_changed = pipewire_onPlaybackIoChanged,
|
||||
.process = pipewire_onPlaybackProcess,
|
||||
.drained = pipewire_onPlaybackDrained
|
||||
};
|
||||
|
||||
if (pw.playback.stream &&
|
||||
pw.playback.channels == channels &&
|
||||
pw.playback.sampleRate == sampleRate)
|
||||
{
|
||||
*maxPeriodFrames = pw.playback.maxPeriodFrames;
|
||||
*startFrames = pw.playback.startFrames;
|
||||
return;
|
||||
}
|
||||
|
||||
pipewire_playbackStopStream();
|
||||
|
||||
char requestedNodeLatency[32];
|
||||
snprintf(requestedNodeLatency, sizeof(requestedNodeLatency), "%d/%d",
|
||||
requestedPeriodFrames, sampleRate);
|
||||
|
||||
pw.playback.channels = channels;
|
||||
pw.playback.sampleRate = sampleRate;
|
||||
pw.playback.stride = sizeof(float) * channels;
|
||||
pw.playback.pullFn = pullFn;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw.playback.stream = pw_stream_new_simple(
|
||||
pw.loop,
|
||||
"Looking Glass",
|
||||
pw_properties_new(
|
||||
PW_KEY_NODE_NAME , "Looking Glass",
|
||||
PW_KEY_MEDIA_TYPE , "Audio",
|
||||
PW_KEY_MEDIA_CATEGORY, "Playback",
|
||||
PW_KEY_MEDIA_ROLE , "Music",
|
||||
PW_KEY_NODE_LATENCY , requestedNodeLatency,
|
||||
NULL
|
||||
),
|
||||
&events,
|
||||
NULL
|
||||
);
|
||||
|
||||
// The user can override the default node latency with the PIPEWIRE_LATENCY
|
||||
// environment variable, so get the actual node latency value from the stream.
|
||||
// The actual quantum size may be lower than this value depending on what else
|
||||
// is using the audio device, but we can treat this value as a maximum
|
||||
const struct pw_properties * properties =
|
||||
pw_stream_get_properties(pw.playback.stream);
|
||||
const char *actualNodeLatency =
|
||||
pw_properties_get(properties, PW_KEY_NODE_LATENCY);
|
||||
DEBUG_ASSERT(actualNodeLatency != NULL);
|
||||
|
||||
unsigned num, denom;
|
||||
if (sscanf(actualNodeLatency, "%u/%u", &num, &denom) != 2 ||
|
||||
denom != sampleRate)
|
||||
{
|
||||
DEBUG_WARN(
|
||||
"PIPEWIRE_LATENCY value '%s' is invalid or does not match stream sample "
|
||||
"rate; using %d/%d", actualNodeLatency, requestedPeriodFrames,
|
||||
sampleRate);
|
||||
|
||||
struct spa_dict_item items[] = {
|
||||
{ PW_KEY_NODE_LATENCY, requestedNodeLatency }
|
||||
};
|
||||
pw_stream_update_properties(pw.playback.stream,
|
||||
&SPA_DICT_INIT_ARRAY(items));
|
||||
|
||||
pw.playback.maxPeriodFrames = requestedPeriodFrames;
|
||||
}
|
||||
else
|
||||
pw.playback.maxPeriodFrames = num;
|
||||
|
||||
// If the previous quantum size was very small, PipeWire can request two full
|
||||
// periods almost immediately at the start of playback
|
||||
pw.playback.startFrames = pw.playback.maxPeriodFrames * 2;
|
||||
|
||||
*maxPeriodFrames = pw.playback.maxPeriodFrames;
|
||||
*startFrames = pw.playback.startFrames;
|
||||
|
||||
if (!pw.playback.stream)
|
||||
{
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
DEBUG_ERROR("Failed to create the stream");
|
||||
return;
|
||||
}
|
||||
|
||||
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
|
||||
&SPA_AUDIO_INFO_RAW_INIT(
|
||||
.format = SPA_AUDIO_FORMAT_F32,
|
||||
.channels = channels,
|
||||
.rate = sampleRate
|
||||
));
|
||||
|
||||
pw_stream_connect(
|
||||
pw.playback.stream,
|
||||
PW_DIRECTION_OUTPUT,
|
||||
PW_ID_ANY,
|
||||
PW_STREAM_FLAG_AUTOCONNECT |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS |
|
||||
PW_STREAM_FLAG_RT_PROCESS |
|
||||
PW_STREAM_FLAG_INACTIVE,
|
||||
params, 1);
|
||||
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_playbackStart(void)
|
||||
{
|
||||
if (!pw.playback.stream)
|
||||
return;
|
||||
|
||||
if (pw.playback.state != STREAM_STATE_ACTIVE)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
|
||||
switch (pw.playback.state)
|
||||
{
|
||||
case STREAM_STATE_INACTIVE:
|
||||
pw_stream_set_active(pw.playback.stream, true);
|
||||
pw.playback.state = STREAM_STATE_ACTIVE;
|
||||
break;
|
||||
|
||||
case STREAM_STATE_DRAINING:
|
||||
// We are in the middle of draining the PipeWire buffers; we need to
|
||||
// wait for this to complete before allowing the new playback to start
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_UNREACHABLE();
|
||||
}
|
||||
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
}
|
||||
|
||||
static void pipewire_playbackStop(void)
|
||||
{
|
||||
if (pw.playback.state != STREAM_STATE_ACTIVE)
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_flush(pw.playback.stream, true);
|
||||
pw.playback.state = STREAM_STATE_DRAINING;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_playbackVolume(int channels, const uint16_t volume[])
|
||||
{
|
||||
if (channels != pw.playback.channels)
|
||||
return;
|
||||
|
||||
float param[channels];
|
||||
for(int i = 0; i < channels; ++i)
|
||||
param[i] = 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_control(pw.playback.stream, SPA_PROP_channelVolumes,
|
||||
channels, param, 0);
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_playbackMute(bool mute)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
float val = mute ? 1.0f : 0.0f;
|
||||
pw_stream_set_control(pw.playback.stream, SPA_PROP_mute, 1, &val, 0);
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static size_t pipewire_playbackLatency(void)
|
||||
{
|
||||
struct pw_time time = { 0 };
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
#if PW_CHECK_VERSION(0, 3, 50)
|
||||
if (pw_stream_get_time_n(pw.playback.stream, &time, sizeof(time)) < 0)
|
||||
#else
|
||||
if (pw_stream_get_time(pw.playback.stream, &time) < 0)
|
||||
#endif
|
||||
DEBUG_ERROR("pw_stream_get_time failed");
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
|
||||
return time.delay + time.queued / pw.playback.stride;
|
||||
}
|
||||
|
||||
static void pipewire_recordStopStream(void)
|
||||
{
|
||||
if (!pw.record.stream)
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_destroy(pw.record.stream);
|
||||
pw.record.stream = NULL;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_onRecordProcess(void * userdata)
|
||||
{
|
||||
struct pw_buffer * pbuf;
|
||||
|
||||
if (!(pbuf = pw_stream_dequeue_buffer(pw.record.stream)))
|
||||
{
|
||||
DEBUG_WARN("out of buffers");
|
||||
return;
|
||||
}
|
||||
|
||||
struct spa_buffer * sbuf = pbuf->buffer;
|
||||
uint8_t * dst;
|
||||
|
||||
if (!(dst = sbuf->datas[0].data))
|
||||
return;
|
||||
|
||||
dst += sbuf->datas[0].chunk->offset;
|
||||
pw.record.pushFn(dst,
|
||||
min(
|
||||
sbuf->datas[0].chunk->size,
|
||||
sbuf->datas[0].maxsize - sbuf->datas[0].chunk->offset) / pw.record.stride
|
||||
);
|
||||
|
||||
pw_stream_queue_buffer(pw.record.stream, pbuf);
|
||||
}
|
||||
|
||||
static void pipewire_recordStart(int channels, int sampleRate,
|
||||
LG_AudioPushFn pushFn)
|
||||
{
|
||||
const struct spa_pod * params[1];
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
static const struct pw_stream_events events =
|
||||
{
|
||||
.version = PW_VERSION_STREAM_EVENTS,
|
||||
.process = pipewire_onRecordProcess
|
||||
};
|
||||
|
||||
if (pw.record.stream &&
|
||||
pw.record.channels == channels &&
|
||||
pw.record.sampleRate == sampleRate)
|
||||
{
|
||||
if (!pw.record.active)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_active(pw.record.stream, true);
|
||||
pw.record.active = true;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pipewire_recordStopStream();
|
||||
|
||||
pw.record.channels = channels;
|
||||
pw.record.sampleRate = sampleRate;
|
||||
pw.record.stride = sizeof(uint16_t) * channels;
|
||||
pw.record.pushFn = pushFn;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw.record.stream = pw_stream_new_simple(
|
||||
pw.loop,
|
||||
"Looking Glass",
|
||||
pw_properties_new(
|
||||
PW_KEY_NODE_NAME , "Looking Glass",
|
||||
PW_KEY_MEDIA_TYPE , "Audio",
|
||||
PW_KEY_MEDIA_CATEGORY, "Capture",
|
||||
PW_KEY_MEDIA_ROLE , "Music",
|
||||
NULL
|
||||
),
|
||||
&events,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (!pw.record.stream)
|
||||
{
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
DEBUG_ERROR("Failed to create the stream");
|
||||
return;
|
||||
}
|
||||
|
||||
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
|
||||
&SPA_AUDIO_INFO_RAW_INIT(
|
||||
.format = SPA_AUDIO_FORMAT_S16,
|
||||
.channels = channels,
|
||||
.rate = sampleRate
|
||||
));
|
||||
|
||||
pw_stream_connect(
|
||||
pw.record.stream,
|
||||
PW_DIRECTION_INPUT,
|
||||
PW_ID_ANY,
|
||||
PW_STREAM_FLAG_AUTOCONNECT |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS |
|
||||
PW_STREAM_FLAG_RT_PROCESS,
|
||||
params, 1);
|
||||
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
pw.record.active = true;
|
||||
}
|
||||
|
||||
static void pipewire_recordStop(void)
|
||||
{
|
||||
if (!pw.record.active)
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_active(pw.record.stream, false);
|
||||
pw.record.active = false;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_recordVolume(int channels, const uint16_t volume[])
|
||||
{
|
||||
if (channels != pw.record.channels)
|
||||
return;
|
||||
|
||||
float param[channels];
|
||||
for(int i = 0; i < channels; ++i)
|
||||
param[i] = 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_control(pw.record.stream, SPA_PROP_channelVolumes,
|
||||
channels, param, 0);
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_recordMute(bool mute)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
float val = mute ? 1.0f : 0.0f;
|
||||
pw_stream_set_control(pw.record.stream, SPA_PROP_mute, 1, &val, 0);
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_free(void)
|
||||
{
|
||||
pipewire_playbackStopStream();
|
||||
pipewire_recordStopStream();
|
||||
pw_thread_loop_stop(pw.thread);
|
||||
pw_thread_loop_destroy(pw.thread);
|
||||
pw_context_destroy(pw.context);
|
||||
pw_loop_destroy(pw.loop);
|
||||
|
||||
pw.loop = NULL;
|
||||
pw.context = NULL;
|
||||
pw.thread = NULL;
|
||||
|
||||
pw_deinit();
|
||||
}
|
||||
|
||||
struct LG_AudioDevOps LGAD_PipeWire =
|
||||
{
|
||||
.name = "PipeWire",
|
||||
.init = pipewire_init,
|
||||
.free = pipewire_free,
|
||||
.playback =
|
||||
{
|
||||
.setup = pipewire_playbackSetup,
|
||||
.start = pipewire_playbackStart,
|
||||
.stop = pipewire_playbackStop,
|
||||
.volume = pipewire_playbackVolume,
|
||||
.mute = pipewire_playbackMute,
|
||||
.latency = pipewire_playbackLatency
|
||||
},
|
||||
.record =
|
||||
{
|
||||
.start = pipewire_recordStart,
|
||||
.stop = pipewire_recordStop,
|
||||
.volume = pipewire_recordVolume,
|
||||
.mute = pipewire_recordMute
|
||||
}
|
||||
};
|
||||
21
client/audiodevs/PulseAudio/CMakeLists.txt
Normal file
21
client/audiodevs/PulseAudio/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(audiodev_PulseAudio LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(AUDIODEV_PulseAudio REQUIRED IMPORTED_TARGET
|
||||
libpulse
|
||||
)
|
||||
|
||||
add_library(audiodev_PulseAudio STATIC
|
||||
pulseaudio.c
|
||||
)
|
||||
|
||||
target_link_libraries(audiodev_PulseAudio
|
||||
PkgConfig::AUDIODEV_PulseAudio
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(audiodev_PulseAudio
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
393
client/audiodevs/PulseAudio/pulseaudio.c
Normal file
393
client/audiodevs/PulseAudio/pulseaudio.c
Normal file
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/audiodev.h"
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
struct PulseAudio
|
||||
{
|
||||
pa_threaded_mainloop * loop;
|
||||
pa_mainloop_api * api;
|
||||
pa_context * context;
|
||||
pa_operation * contextSub;
|
||||
|
||||
pa_stream * sink;
|
||||
int sinkIndex;
|
||||
bool sinkCorked;
|
||||
bool sinkMuted;
|
||||
bool sinkStarting;
|
||||
int sinkMaxPeriodFrames;
|
||||
int sinkStartFrames;
|
||||
int sinkSampleRate;
|
||||
int sinkChannels;
|
||||
int sinkStride;
|
||||
LG_AudioPullFn sinkPullFn;
|
||||
};
|
||||
|
||||
static struct PulseAudio pa = {0};
|
||||
|
||||
static void pulseaudio_sink_input_cb(pa_context *c, const pa_sink_input_info *i,
|
||||
int eol, void *userdata)
|
||||
{
|
||||
if (eol < 0 || eol == 1)
|
||||
return;
|
||||
|
||||
pa.sinkIndex = i->index;
|
||||
}
|
||||
|
||||
static void pulseaudio_subscribe_cb(pa_context *c,
|
||||
pa_subscription_event_type_t t, uint32_t index, void *userdata)
|
||||
{
|
||||
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
|
||||
{
|
||||
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
|
||||
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
|
||||
pa.sinkIndex = 0;
|
||||
else
|
||||
{
|
||||
pa_operation *o = pa_context_get_sink_input_info(c, index,
|
||||
pulseaudio_sink_input_cb, NULL);
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void pulseaudio_ctx_state_change_cb(pa_context * c, void * userdata)
|
||||
{
|
||||
switch (pa_context_get_state(c))
|
||||
{
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_READY:
|
||||
DEBUG_INFO("Connected to PulseAudio server");
|
||||
pa_context_set_subscribe_callback(c, pulseaudio_subscribe_cb, NULL);
|
||||
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL);
|
||||
pa_threaded_mainloop_signal(pa.loop, 0);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
if (pa.contextSub)
|
||||
{
|
||||
pa_operation_unref(pa.contextSub);
|
||||
pa.contextSub = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_FAILED:
|
||||
default:
|
||||
DEBUG_ERROR("context error: %s", pa_strerror(pa_context_errno(c)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool pulseaudio_init(void)
|
||||
{
|
||||
pa.loop = pa_threaded_mainloop_new();
|
||||
if (!pa.loop)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the main loop");
|
||||
goto err;
|
||||
}
|
||||
|
||||
pa.api = pa_threaded_mainloop_get_api(pa.loop);
|
||||
if (pa_signal_init(pa.api) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to init signals");
|
||||
goto err_loop;
|
||||
}
|
||||
|
||||
if (pa_threaded_mainloop_start(pa.loop) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to start the main loop");
|
||||
goto err_loop;
|
||||
}
|
||||
|
||||
pa_proplist * propList = pa_proplist_new();
|
||||
if (!propList)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the proplist");
|
||||
goto err_thread;
|
||||
}
|
||||
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "video");
|
||||
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
pa.context = pa_context_new_with_proplist(
|
||||
pa.api,
|
||||
"Looking Glass",
|
||||
propList);
|
||||
if (!pa.context)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the context");
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
pa_context_set_state_callback(pa.context,
|
||||
pulseaudio_ctx_state_change_cb, NULL);
|
||||
|
||||
if (pa_context_connect(pa.context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to connect to the context server");
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
for(;;)
|
||||
{
|
||||
pa_context_state_t state = pa_context_get_state(pa.context);
|
||||
if(!PA_CONTEXT_IS_GOOD(state))
|
||||
{
|
||||
DEBUG_ERROR("Context is bad");
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
if (state == PA_CONTEXT_READY)
|
||||
break;
|
||||
|
||||
pa_threaded_mainloop_wait(pa.loop);
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
pa_proplist_free(propList);
|
||||
return true;
|
||||
|
||||
err_context:
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
pa_proplist_free(propList);
|
||||
|
||||
err_thread:
|
||||
pa_threaded_mainloop_stop(pa.loop);
|
||||
|
||||
err_loop:
|
||||
pa_threaded_mainloop_free(pa.loop);
|
||||
|
||||
err:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void pulseaudio_sink_close_nl(void)
|
||||
{
|
||||
if (!pa.sink)
|
||||
return;
|
||||
|
||||
pa_stream_set_write_callback(pa.sink, NULL, NULL);
|
||||
pa_stream_flush(pa.sink, NULL, NULL);
|
||||
pa_stream_unref(pa.sink);
|
||||
pa.sink = NULL;
|
||||
}
|
||||
|
||||
static void pulseaudio_free(void)
|
||||
{
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
|
||||
pulseaudio_sink_close_nl();
|
||||
|
||||
pa_context_set_state_callback(pa.context, NULL, NULL);
|
||||
pa_context_set_subscribe_callback(pa.context, NULL, NULL);
|
||||
pa_context_disconnect(pa.context);
|
||||
pa_context_unref(pa.context);
|
||||
|
||||
if (pa.contextSub)
|
||||
{
|
||||
pa_operation_unref(pa.contextSub);
|
||||
pa.contextSub = NULL;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_state_cb(pa_stream * p, void * userdata)
|
||||
{
|
||||
if (pa.sinkStarting && pa_stream_get_state(pa.sink) == PA_STREAM_READY)
|
||||
{
|
||||
pa_stream_cork(pa.sink, 0, NULL, NULL);
|
||||
pa.sinkCorked = false;
|
||||
pa.sinkStarting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata)
|
||||
{
|
||||
// PulseAudio tries to pull data from the stream as soon as it is created for
|
||||
// some reason, even though it is corked
|
||||
if (pa.sinkCorked)
|
||||
return;
|
||||
|
||||
uint8_t * dst;
|
||||
|
||||
pa_stream_begin_write(p, (void **)&dst, &nbytes);
|
||||
|
||||
int frames = nbytes / pa.sinkStride;
|
||||
frames = pa.sinkPullFn(dst, frames);
|
||||
|
||||
pa_stream_write(p, dst, frames * pa.sinkStride, NULL, 0, PA_SEEK_RELATIVE);
|
||||
}
|
||||
|
||||
static void pulseaudio_underflow_cb(pa_stream * p, void * userdata)
|
||||
{
|
||||
DEBUG_WARN("Underflow");
|
||||
}
|
||||
|
||||
static void pulseaudio_overflow_cb(pa_stream * p, void * userdata)
|
||||
{
|
||||
DEBUG_WARN("Overflow");
|
||||
}
|
||||
|
||||
static void pulseaudio_setup(int channels, int sampleRate,
|
||||
int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames,
|
||||
LG_AudioPullFn pullFn)
|
||||
{
|
||||
if (pa.sink && pa.sinkChannels == channels && pa.sinkSampleRate == sampleRate)
|
||||
{
|
||||
*maxPeriodFrames = pa.sinkMaxPeriodFrames;
|
||||
*startFrames = pa.sinkStartFrames;
|
||||
return;
|
||||
}
|
||||
|
||||
pa_sample_spec spec = {
|
||||
.format = PA_SAMPLE_FLOAT32,
|
||||
.rate = sampleRate,
|
||||
.channels = channels
|
||||
};
|
||||
|
||||
int stride = channels * sizeof(float);
|
||||
int bufferSize = requestedPeriodFrames * 2 * stride;
|
||||
pa_buffer_attr attribs =
|
||||
{
|
||||
.maxlength = -1,
|
||||
.tlength = bufferSize,
|
||||
.prebuf = 0,
|
||||
.minreq = (uint32_t)-1
|
||||
};
|
||||
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
pulseaudio_sink_close_nl();
|
||||
|
||||
pa.sinkChannels = channels;
|
||||
pa.sinkSampleRate = sampleRate;
|
||||
|
||||
pa.sink = pa_stream_new(pa.context, "Looking Glass", &spec, NULL);
|
||||
pa_stream_set_state_callback (pa.sink, pulseaudio_state_cb , NULL);
|
||||
pa_stream_set_write_callback (pa.sink, pulseaudio_write_cb , NULL);
|
||||
pa_stream_set_underflow_callback(pa.sink, pulseaudio_underflow_cb, NULL);
|
||||
pa_stream_set_overflow_callback (pa.sink, pulseaudio_overflow_cb , NULL);
|
||||
|
||||
pa_stream_connect_playback(pa.sink, NULL, &attribs, PA_STREAM_START_CORKED,
|
||||
NULL, NULL);
|
||||
|
||||
pa.sinkStride = stride;
|
||||
pa.sinkPullFn = pullFn;
|
||||
pa.sinkMaxPeriodFrames = requestedPeriodFrames;
|
||||
pa.sinkCorked = true;
|
||||
pa.sinkStarting = false;
|
||||
|
||||
// If something else is, or was recently using a small latency value,
|
||||
// PulseAudio can request way more data at startup than is reasonable
|
||||
pa.sinkStartFrames = requestedPeriodFrames * 4;
|
||||
|
||||
*maxPeriodFrames = requestedPeriodFrames;
|
||||
*startFrames = pa.sinkStartFrames;
|
||||
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_start(void)
|
||||
{
|
||||
if (!pa.sink)
|
||||
return;
|
||||
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
|
||||
pa_stream_state_t state = pa_stream_get_state(pa.sink);
|
||||
if (state == PA_STREAM_CREATING)
|
||||
pa.sinkStarting = true;
|
||||
else
|
||||
{
|
||||
pa_stream_cork(pa.sink, 0, NULL, NULL);
|
||||
pa.sinkCorked = false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_stop(void)
|
||||
{
|
||||
if (!pa.sink)
|
||||
return;
|
||||
|
||||
bool needLock = !pa_threaded_mainloop_in_thread(pa.loop);
|
||||
if (needLock)
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
|
||||
pa_stream_cork(pa.sink, 1, NULL, NULL);
|
||||
pa.sinkCorked = true;
|
||||
pa.sinkStarting = false;
|
||||
|
||||
if (needLock)
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_volume(int channels, const uint16_t volume[])
|
||||
{
|
||||
if (!pa.sink || !pa.sinkIndex)
|
||||
return;
|
||||
|
||||
struct pa_cvolume v = { .channels = channels };
|
||||
for(int i = 0; i < channels; ++i)
|
||||
v.values[i] = pa_sw_volume_from_linear(
|
||||
9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787);
|
||||
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
pa_context_set_sink_input_volume(pa.context, pa.sinkIndex, &v, NULL, NULL);
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_mute(bool mute)
|
||||
{
|
||||
if (!pa.sink || !pa.sinkIndex || pa.sinkMuted == mute)
|
||||
return;
|
||||
|
||||
pa.sinkMuted = mute;
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
pa_context_set_sink_input_mute(pa.context, pa.sinkIndex, mute, NULL, NULL);
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
struct LG_AudioDevOps LGAD_PulseAudio =
|
||||
{
|
||||
.name = "PulseAudio",
|
||||
.init = pulseaudio_init,
|
||||
.free = pulseaudio_free,
|
||||
.playback =
|
||||
{
|
||||
.setup = pulseaudio_setup,
|
||||
.start = pulseaudio_start,
|
||||
.stop = pulseaudio_stop,
|
||||
.volume = pulseaudio_volume,
|
||||
.mute = pulseaudio_mute
|
||||
}
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
# Try to find the GMP librairies
|
||||
# GMP_FOUND - system has GMP lib
|
||||
# GMP_INCLUDE_DIR - the GMP include directory
|
||||
# GMP_LIBRARIES - Libraries needed to use GMP
|
||||
|
||||
if (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
|
||||
# Already in cache, be silent
|
||||
set(GMP_FIND_QUIETLY TRUE)
|
||||
endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
|
||||
|
||||
find_path(GMP_INCLUDE_DIR NAMES gmp.h )
|
||||
find_library(GMP_LIBRARIES NAMES gmp libgmp )
|
||||
find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx )
|
||||
MESSAGE(STATUS "GMP libs: " ${GMP_LIBRARIES} " " ${GMPXX_LIBRARIES} )
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES)
|
||||
|
||||
mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES)
|
||||
@@ -27,11 +27,6 @@ if (ENABLE_X11)
|
||||
add_displayserver(X11)
|
||||
endif()
|
||||
|
||||
# SDL must be last as it's the fallback implemntation
|
||||
if (ENABLE_SDL)
|
||||
add_displayserver(SDL)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT DISPLAYSERVERS 0)
|
||||
list(REMOVE_AT DISPLAYSERVERS_LINK 0)
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_SDL LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DISPLAYSERVER_SDL_PKGCONFIG REQUIRED
|
||||
sdl2
|
||||
)
|
||||
|
||||
add_library(displayserver_SDL STATIC
|
||||
sdl.c
|
||||
)
|
||||
|
||||
target_link_libraries(displayserver_SDL
|
||||
${DISPLAYSERVER_SDL_PKGCONFIG_LIBRARIES}
|
||||
${DISPLAYSERVER_SDL_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_SDL
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_SDL_PKGCONFIG_INCLUDE_DIRS}
|
||||
${DISPLAYSERVER_SDL_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,197 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
//FIXME: this should be made static once config.c is no longer using SDL
|
||||
//scancodes
|
||||
uint32_t sdl_to_xfree86[SDL_NUM_SCANCODES] =
|
||||
{
|
||||
[SDL_SCANCODE_UNKNOWN] /* = USB 0 */ = KEY_RESERVED,
|
||||
[SDL_SCANCODE_A] /* = USB 4 */ = KEY_A,
|
||||
[SDL_SCANCODE_B] /* = USB 5 */ = KEY_B,
|
||||
[SDL_SCANCODE_C] /* = USB 6 */ = KEY_C,
|
||||
[SDL_SCANCODE_D] /* = USB 7 */ = KEY_D,
|
||||
[SDL_SCANCODE_E] /* = USB 8 */ = KEY_E,
|
||||
[SDL_SCANCODE_F] /* = USB 9 */ = KEY_F,
|
||||
[SDL_SCANCODE_G] /* = USB 10 */ = KEY_G,
|
||||
[SDL_SCANCODE_H] /* = USB 11 */ = KEY_H,
|
||||
[SDL_SCANCODE_I] /* = USB 12 */ = KEY_I,
|
||||
[SDL_SCANCODE_J] /* = USB 13 */ = KEY_J,
|
||||
[SDL_SCANCODE_K] /* = USB 14 */ = KEY_K,
|
||||
[SDL_SCANCODE_L] /* = USB 15 */ = KEY_L,
|
||||
[SDL_SCANCODE_M] /* = USB 16 */ = KEY_M,
|
||||
[SDL_SCANCODE_N] /* = USB 17 */ = KEY_N,
|
||||
[SDL_SCANCODE_O] /* = USB 18 */ = KEY_O,
|
||||
[SDL_SCANCODE_P] /* = USB 19 */ = KEY_P,
|
||||
[SDL_SCANCODE_Q] /* = USB 20 */ = KEY_Q,
|
||||
[SDL_SCANCODE_R] /* = USB 21 */ = KEY_R,
|
||||
[SDL_SCANCODE_S] /* = USB 22 */ = KEY_S,
|
||||
[SDL_SCANCODE_T] /* = USB 23 */ = KEY_T,
|
||||
[SDL_SCANCODE_U] /* = USB 24 */ = KEY_U,
|
||||
[SDL_SCANCODE_V] /* = USB 25 */ = KEY_V,
|
||||
[SDL_SCANCODE_W] /* = USB 26 */ = KEY_W,
|
||||
[SDL_SCANCODE_X] /* = USB 27 */ = KEY_X,
|
||||
[SDL_SCANCODE_Y] /* = USB 28 */ = KEY_Y,
|
||||
[SDL_SCANCODE_Z] /* = USB 29 */ = KEY_Z,
|
||||
[SDL_SCANCODE_1] /* = USB 30 */ = KEY_1,
|
||||
[SDL_SCANCODE_2] /* = USB 31 */ = KEY_2,
|
||||
[SDL_SCANCODE_3] /* = USB 32 */ = KEY_3,
|
||||
[SDL_SCANCODE_4] /* = USB 33 */ = KEY_4,
|
||||
[SDL_SCANCODE_5] /* = USB 34 */ = KEY_5,
|
||||
[SDL_SCANCODE_6] /* = USB 35 */ = KEY_6,
|
||||
[SDL_SCANCODE_7] /* = USB 36 */ = KEY_7,
|
||||
[SDL_SCANCODE_8] /* = USB 37 */ = KEY_8,
|
||||
[SDL_SCANCODE_9] /* = USB 38 */ = KEY_9,
|
||||
[SDL_SCANCODE_0] /* = USB 39 */ = KEY_0,
|
||||
[SDL_SCANCODE_RETURN] /* = USB 40 */ = KEY_ENTER,
|
||||
[SDL_SCANCODE_ESCAPE] /* = USB 41 */ = KEY_ESC,
|
||||
[SDL_SCANCODE_BACKSPACE] /* = USB 42 */ = KEY_BACKSPACE,
|
||||
[SDL_SCANCODE_TAB] /* = USB 43 */ = KEY_TAB,
|
||||
[SDL_SCANCODE_SPACE] /* = USB 44 */ = KEY_SPACE,
|
||||
[SDL_SCANCODE_MINUS] /* = USB 45 */ = KEY_MINUS,
|
||||
[SDL_SCANCODE_EQUALS] /* = USB 46 */ = KEY_EQUAL,
|
||||
[SDL_SCANCODE_LEFTBRACKET] /* = USB 47 */ = KEY_LEFTBRACE,
|
||||
[SDL_SCANCODE_RIGHTBRACKET] /* = USB 48 */ = KEY_RIGHTBRACE,
|
||||
[SDL_SCANCODE_BACKSLASH] /* = USB 49 */ = KEY_BACKSLASH,
|
||||
[SDL_SCANCODE_SEMICOLON] /* = USB 51 */ = KEY_SEMICOLON,
|
||||
[SDL_SCANCODE_APOSTROPHE] /* = USB 52 */ = KEY_APOSTROPHE,
|
||||
[SDL_SCANCODE_GRAVE] /* = USB 53 */ = KEY_GRAVE,
|
||||
[SDL_SCANCODE_COMMA] /* = USB 54 */ = KEY_COMMA,
|
||||
[SDL_SCANCODE_PERIOD] /* = USB 55 */ = KEY_DOT,
|
||||
[SDL_SCANCODE_SLASH] /* = USB 56 */ = KEY_SLASH,
|
||||
[SDL_SCANCODE_CAPSLOCK] /* = USB 57 */ = KEY_CAPSLOCK,
|
||||
[SDL_SCANCODE_F1] /* = USB 58 */ = KEY_F1,
|
||||
[SDL_SCANCODE_F2] /* = USB 59 */ = KEY_F2,
|
||||
[SDL_SCANCODE_F3] /* = USB 60 */ = KEY_F3,
|
||||
[SDL_SCANCODE_F4] /* = USB 61 */ = KEY_F4,
|
||||
[SDL_SCANCODE_F5] /* = USB 62 */ = KEY_F5,
|
||||
[SDL_SCANCODE_F6] /* = USB 63 */ = KEY_F6,
|
||||
[SDL_SCANCODE_F7] /* = USB 64 */ = KEY_F7,
|
||||
[SDL_SCANCODE_F8] /* = USB 65 */ = KEY_F8,
|
||||
[SDL_SCANCODE_F9] /* = USB 66 */ = KEY_F9,
|
||||
[SDL_SCANCODE_F10] /* = USB 67 */ = KEY_F10,
|
||||
[SDL_SCANCODE_F11] /* = USB 68 */ = KEY_F11,
|
||||
[SDL_SCANCODE_F12] /* = USB 69 */ = KEY_F12,
|
||||
[SDL_SCANCODE_PRINTSCREEN] /* = USB 70 */ = KEY_SYSRQ,
|
||||
[SDL_SCANCODE_SCROLLLOCK] /* = USB 71 */ = KEY_SCROLLLOCK,
|
||||
[SDL_SCANCODE_PAUSE] /* = USB 72 */ = KEY_PAUSE,
|
||||
[SDL_SCANCODE_INSERT] /* = USB 73 */ = KEY_INSERT,
|
||||
[SDL_SCANCODE_HOME] /* = USB 74 */ = KEY_HOME,
|
||||
[SDL_SCANCODE_PAGEUP] /* = USB 75 */ = KEY_PAGEUP,
|
||||
[SDL_SCANCODE_DELETE] /* = USB 76 */ = KEY_DELETE,
|
||||
[SDL_SCANCODE_END] /* = USB 77 */ = KEY_END,
|
||||
[SDL_SCANCODE_PAGEDOWN] /* = USB 78 */ = KEY_PAGEDOWN,
|
||||
[SDL_SCANCODE_RIGHT] /* = USB 79 */ = KEY_RIGHT,
|
||||
[SDL_SCANCODE_LEFT] /* = USB 80 */ = KEY_LEFT,
|
||||
[SDL_SCANCODE_DOWN] /* = USB 81 */ = KEY_DOWN,
|
||||
[SDL_SCANCODE_UP] /* = USB 82 */ = KEY_UP,
|
||||
[SDL_SCANCODE_NUMLOCKCLEAR] /* = USB 83 */ = KEY_NUMLOCK,
|
||||
[SDL_SCANCODE_KP_DIVIDE] /* = USB 84 */ = KEY_KPSLASH,
|
||||
[SDL_SCANCODE_KP_MULTIPLY] /* = USB 85 */ = KEY_KPASTERISK,
|
||||
[SDL_SCANCODE_KP_MINUS] /* = USB 86 */ = KEY_KPMINUS,
|
||||
[SDL_SCANCODE_KP_PLUS] /* = USB 87 */ = KEY_KPPLUS,
|
||||
[SDL_SCANCODE_KP_ENTER] /* = USB 88 */ = KEY_KPENTER,
|
||||
[SDL_SCANCODE_KP_1] /* = USB 89 */ = KEY_KP1,
|
||||
[SDL_SCANCODE_KP_2] /* = USB 90 */ = KEY_KP2,
|
||||
[SDL_SCANCODE_KP_3] /* = USB 91 */ = KEY_KP3,
|
||||
[SDL_SCANCODE_KP_4] /* = USB 92 */ = KEY_KP4,
|
||||
[SDL_SCANCODE_KP_5] /* = USB 93 */ = KEY_KP5,
|
||||
[SDL_SCANCODE_KP_6] /* = USB 94 */ = KEY_KP6,
|
||||
[SDL_SCANCODE_KP_7] /* = USB 95 */ = KEY_KP7,
|
||||
[SDL_SCANCODE_KP_8] /* = USB 96 */ = KEY_KP8,
|
||||
[SDL_SCANCODE_KP_9] /* = USB 97 */ = KEY_KP9,
|
||||
[SDL_SCANCODE_KP_0] /* = USB 98 */ = KEY_KP0,
|
||||
[SDL_SCANCODE_KP_PERIOD] /* = USB 99 */ = KEY_KPDOT,
|
||||
[SDL_SCANCODE_NONUSBACKSLASH] /* = USB 100 */ = KEY_102ND,
|
||||
[SDL_SCANCODE_APPLICATION] /* = USB 101 */ = KEY_COMPOSE,
|
||||
[SDL_SCANCODE_POWER] /* = USB 102 */ = KEY_POWER,
|
||||
[SDL_SCANCODE_KP_EQUALS] /* = USB 103 */ = KEY_KPEQUAL,
|
||||
[SDL_SCANCODE_F13] /* = USB 104 */ = KEY_CONFIG,
|
||||
[SDL_SCANCODE_F14] /* = USB 105 */ = KEY_F14,
|
||||
[SDL_SCANCODE_F15] /* = USB 106 */ = KEY_F15,
|
||||
[SDL_SCANCODE_F16] /* = USB 107 */ = KEY_F16,
|
||||
[SDL_SCANCODE_F17] /* = USB 108 */ = KEY_F17,
|
||||
[SDL_SCANCODE_F18] /* = USB 109 */ = KEY_F18,
|
||||
[SDL_SCANCODE_F19] /* = USB 110 */ = KEY_F19,
|
||||
[SDL_SCANCODE_F20] /* = USB 111 */ = KEY_F20,
|
||||
[SDL_SCANCODE_HELP] /* = USB 117 */ = KEY_HELP,
|
||||
[SDL_SCANCODE_MENU] /* = USB 118 */ = KEY_MENU,
|
||||
[SDL_SCANCODE_STOP] /* = USB 120 */ = KEY_CANCEL,
|
||||
[SDL_SCANCODE_AGAIN] /* = USB 121 */ = KEY_AGAIN,
|
||||
[SDL_SCANCODE_UNDO] /* = USB 122 */ = KEY_UNDO,
|
||||
[SDL_SCANCODE_CUT] /* = USB 123 */ = KEY_CUT,
|
||||
[SDL_SCANCODE_COPY] /* = USB 124 */ = KEY_COPY,
|
||||
[SDL_SCANCODE_PASTE] /* = USB 125 */ = KEY_PASTE,
|
||||
[SDL_SCANCODE_FIND] /* = USB 126 */ = KEY_FIND,
|
||||
[SDL_SCANCODE_MUTE] /* = USB 127 */ = KEY_MUTE,
|
||||
[SDL_SCANCODE_VOLUMEUP] /* = USB 128 */ = KEY_VOLUMEUP,
|
||||
[SDL_SCANCODE_VOLUMEDOWN] /* = USB 129 */ = KEY_VOLUMEDOWN,
|
||||
[SDL_SCANCODE_KP_COMMA] /* = USB 133 */ = KEY_KPCOMMA,
|
||||
[SDL_SCANCODE_INTERNATIONAL1] /* = USB 135 */ = KEY_RO,
|
||||
[SDL_SCANCODE_INTERNATIONAL2] /* = USB 136 */ = KEY_KATAKANAHIRAGANA,
|
||||
[SDL_SCANCODE_INTERNATIONAL3] /* = USB 137 */ = KEY_YEN,
|
||||
[SDL_SCANCODE_INTERNATIONAL4] /* = USB 138 */ = KEY_HENKAN,
|
||||
[SDL_SCANCODE_INTERNATIONAL5] /* = USB 139 */ = KEY_MUHENKAN,
|
||||
[SDL_SCANCODE_LANG1] /* = USB 144 */ = KEY_HANGEUL,
|
||||
[SDL_SCANCODE_LANG2] /* = USB 145 */ = KEY_HANJA,
|
||||
[SDL_SCANCODE_SYSREQ] /* = USB 154 */ = KEY_RIGHTSHIFT,
|
||||
[SDL_SCANCODE_CANCEL] /* = USB 155 */ = KEY_STOP,
|
||||
[SDL_SCANCODE_KP_LEFTPAREN] /* = USB 182 */ = KEY_KPLEFTPAREN,
|
||||
[SDL_SCANCODE_KP_RIGHTPAREN] /* = USB 183 */ = KEY_KPRIGHTPAREN,
|
||||
[SDL_SCANCODE_KP_PLUSMINUS] /* = USB 215 */ = KEY_KPPLUSMINUS,
|
||||
[SDL_SCANCODE_LCTRL] /* = USB 224 */ = KEY_LEFTCTRL,
|
||||
[SDL_SCANCODE_LSHIFT] /* = USB 225 */ = KEY_LEFTSHIFT,
|
||||
[SDL_SCANCODE_LALT] /* = USB 226 */ = KEY_LEFTALT,
|
||||
[SDL_SCANCODE_LGUI] /* = USB 227 */ = KEY_LEFTMETA,
|
||||
[SDL_SCANCODE_RCTRL] /* = USB 228 */ = KEY_RIGHTCTRL,
|
||||
[SDL_SCANCODE_RSHIFT] /* = USB 229 */ = KEY_RIGHTSHIFT,
|
||||
[SDL_SCANCODE_RALT] /* = USB 230 */ = KEY_RIGHTALT,
|
||||
[SDL_SCANCODE_RGUI] /* = USB 231 */ = KEY_RIGHTMETA,
|
||||
[SDL_SCANCODE_MODE] /* = USB 257 */ = KEY_ZENKAKUHANKAKU,
|
||||
[SDL_SCANCODE_AUDIONEXT] /* = USB 258 */ = KEY_NEXTSONG,
|
||||
[SDL_SCANCODE_AUDIOPREV] /* = USB 259 */ = KEY_PREVIOUSSONG,
|
||||
[SDL_SCANCODE_AUDIOSTOP] /* = USB 260 */ = KEY_STOPCD,
|
||||
[SDL_SCANCODE_AUDIOPLAY] /* = USB 261 */ = KEY_PLAYPAUSE,
|
||||
[SDL_SCANCODE_MEDIASELECT] /* = USB 263 */ = KEY_MEDIA,
|
||||
[SDL_SCANCODE_WWW] /* = USB 264 */ = KEY_WWW,
|
||||
[SDL_SCANCODE_MAIL] /* = USB 265 */ = KEY_MAIL,
|
||||
[SDL_SCANCODE_CALCULATOR] /* = USB 266 */ = KEY_CALC,
|
||||
[SDL_SCANCODE_COMPUTER] /* = USB 267 */ = KEY_COMPUTER,
|
||||
[SDL_SCANCODE_AC_SEARCH] /* = USB 268 */ = KEY_SEARCH,
|
||||
[SDL_SCANCODE_AC_HOME] /* = USB 269 */ = KEY_HOMEPAGE,
|
||||
[SDL_SCANCODE_AC_BACK] /* = USB 270 */ = KEY_BACK,
|
||||
[SDL_SCANCODE_AC_FORWARD] /* = USB 271 */ = KEY_FORWARD,
|
||||
[SDL_SCANCODE_AC_REFRESH] /* = USB 273 */ = KEY_REFRESH,
|
||||
[SDL_SCANCODE_AC_BOOKMARKS] /* = USB 274 */ = KEY_BOOKMARKS,
|
||||
[SDL_SCANCODE_BRIGHTNESSDOWN] /* = USB 275 */ = KEY_BRIGHTNESSDOWN,
|
||||
[SDL_SCANCODE_BRIGHTNESSUP] /* = USB 276 */ = KEY_BRIGHTNESSUP,
|
||||
[SDL_SCANCODE_DISPLAYSWITCH] /* = USB 277 */ = KEY_SWITCHVIDEOMODE,
|
||||
[SDL_SCANCODE_KBDILLUMTOGGLE] /* = USB 278 */ = KEY_KBDILLUMTOGGLE,
|
||||
[SDL_SCANCODE_KBDILLUMDOWN] /* = USB 279 */ = KEY_KBDILLUMDOWN,
|
||||
[SDL_SCANCODE_KBDILLUMUP] /* = USB 280 */ = KEY_KBDILLUMUP,
|
||||
[SDL_SCANCODE_EJECT] /* = USB 281 */ = KEY_EJECTCD,
|
||||
[SDL_SCANCODE_SLEEP] /* = USB 282 */ = KEY_SLEEP,
|
||||
[SDL_SCANCODE_APP1] /* = USB 283 */ = KEY_PROG1,
|
||||
[SDL_SCANCODE_APP2] /* = USB 284 */ = KEY_PROG2,
|
||||
[SDL_SCANCODE_AUDIOREWIND] /* = USB 285 */ = KEY_REWIND,
|
||||
[SDL_SCANCODE_AUDIOFASTFORWARD] /* = USB 286 */ = KEY_FASTFORWARD,
|
||||
};
|
||||
@@ -1,561 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
#include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
#include <wayland-egl.h>
|
||||
#endif
|
||||
|
||||
#include "app.h"
|
||||
#include "kb.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "common/types.h"
|
||||
#include "common/debug.h"
|
||||
#include "util.h"
|
||||
|
||||
struct SDLDSState
|
||||
{
|
||||
SDL_Window * window;
|
||||
SDL_Cursor * cursor;
|
||||
|
||||
EGLNativeWindowType wlDisplay;
|
||||
|
||||
bool keyboardGrabbed;
|
||||
bool pointerGrabbed;
|
||||
bool exiting;
|
||||
};
|
||||
|
||||
static struct SDLDSState sdl;
|
||||
|
||||
/* forwards */
|
||||
static int sdlEventFilter(void * userdata, SDL_Event * event);
|
||||
|
||||
static void sdlSetup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool sdlProbe(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdlEarlyInit(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdlInit(const LG_DSInitParams params)
|
||||
{
|
||||
memset(&sdl, 0, sizeof(sdl));
|
||||
|
||||
// Allow screensavers for now: we will enable and disable as needed.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
DEBUG_ERROR("SDL_Init Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (params.opengl)
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
sdl.window = SDL_CreateWindow(
|
||||
params.title,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.x,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.y,
|
||||
params.w,
|
||||
params.h,
|
||||
(
|
||||
SDL_WINDOW_HIDDEN |
|
||||
(params.resizable ? SDL_WINDOW_RESIZABLE : 0) |
|
||||
(params.borderless ? SDL_WINDOW_BORDERLESS : 0) |
|
||||
(params.maximize ? SDL_WINDOW_MAXIMIZED : 0) |
|
||||
(params.opengl ? SDL_WINDOW_OPENGL : 0)
|
||||
)
|
||||
);
|
||||
|
||||
if (sdl.window == NULL)
|
||||
{
|
||||
DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError());
|
||||
goto fail_init;
|
||||
}
|
||||
|
||||
const uint8_t data[4] = {0xf, 0x9, 0x9, 0xf};
|
||||
const uint8_t mask[4] = {0xf, 0xf, 0xf, 0xf};
|
||||
sdl.cursor = SDL_CreateCursor(data, mask, 8, 4, 4, 0);
|
||||
SDL_SetCursor(sdl.cursor);
|
||||
|
||||
SDL_ShowWindow(sdl.window);
|
||||
|
||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
||||
|
||||
if (params.fullscreen)
|
||||
SDL_SetWindowFullscreen(sdl.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
|
||||
if (!params.center)
|
||||
SDL_SetWindowPosition(sdl.window, params.x, params.y);
|
||||
|
||||
// ensure mouse acceleration is identical in server mode
|
||||
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
||||
|
||||
SDL_SetEventFilter(sdlEventFilter, NULL);
|
||||
return true;
|
||||
|
||||
fail_init:
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sdlStartup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void sdlShutdown(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void sdlFree(void)
|
||||
{
|
||||
SDL_DestroyWindow(sdl.window);
|
||||
|
||||
if (sdl.cursor)
|
||||
SDL_FreeCursor(sdl.cursor);
|
||||
|
||||
if (sdl.window)
|
||||
SDL_DestroyWindow(sdl.window);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
static bool sdlGetProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
static EGLDisplay sdlGetEGLDisplay(void)
|
||||
{
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(sdl.window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
EGLNativeDisplayType native;
|
||||
EGLenum platform;
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
native = (EGLNativeDisplayType)wminfo.info.x11.display;
|
||||
platform = EGL_PLATFORM_X11_KHR;
|
||||
break;
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
native = (EGLNativeDisplayType)wminfo.info.wl.display;
|
||||
platform = EGL_PLATFORM_WAYLAND_KHR;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
|
||||
if (util_hasGLExt(early_exts, "EGL_KHR_platform_base") &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplay)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplay");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplay(platform, native, NULL);
|
||||
}
|
||||
|
||||
if (util_hasGLExt(early_exts, "EGL_EXT_platform_base") &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplayEXT(platform, native, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO("Using eglGetDisplay");
|
||||
return eglGetDisplay(native);
|
||||
}
|
||||
|
||||
static EGLNativeWindowType sdlGetEGLNativeWindow(void)
|
||||
{
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(sdl.window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
return (EGLNativeWindowType)wminfo.info.x11.window;
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
{
|
||||
if (sdl.wlDisplay)
|
||||
return sdl.wlDisplay;
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSize(sdl.window, &width, &height);
|
||||
sdl.wlDisplay = (EGLNativeWindowType)wl_egl_window_create(
|
||||
wminfo.info.wl.surface, width, height);
|
||||
|
||||
return sdl.wlDisplay;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void sdlEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
|
||||
{
|
||||
eglSwapBuffers(display, surface);
|
||||
}
|
||||
#endif //ENABLE_EGL
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
static LG_DSGLContext sdlGLCreateContext(void)
|
||||
{
|
||||
return (LG_DSGLContext)SDL_GL_CreateContext(sdl.window);
|
||||
}
|
||||
|
||||
static void sdlGLDeleteContext(LG_DSGLContext context)
|
||||
{
|
||||
SDL_GL_DeleteContext((SDL_GLContext)context);
|
||||
}
|
||||
|
||||
static void sdlGLMakeCurrent(LG_DSGLContext context)
|
||||
{
|
||||
SDL_GL_MakeCurrent(sdl.window, (SDL_GLContext)context);
|
||||
}
|
||||
|
||||
static void sdlGLSetSwapInterval(int interval)
|
||||
{
|
||||
SDL_GL_SetSwapInterval(interval);
|
||||
}
|
||||
|
||||
static void sdlGLSwapBuffers(void)
|
||||
{
|
||||
SDL_GL_SwapWindow(sdl.window);
|
||||
}
|
||||
#endif //ENABLE_OPENGL
|
||||
|
||||
static int sdlEventFilter(void * userdata, SDL_Event * event)
|
||||
{
|
||||
switch(event->type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
app_handleCloseEvent();
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
// stop motion events during the warp out of the window
|
||||
if (sdl.exiting)
|
||||
break;
|
||||
|
||||
app_updateCursorPos(event->motion.x, event->motion.y);
|
||||
app_handleMouseRelative(event->motion.xrel, event->motion.yrel,
|
||||
event->motion.xrel, event->motion.yrel);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
{
|
||||
int button = event->button.button;
|
||||
if (button > 3)
|
||||
button += 2;
|
||||
|
||||
app_handleButtonPress(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
{
|
||||
int button = event->button.button;
|
||||
if (button > 3)
|
||||
button += 2;
|
||||
|
||||
app_handleButtonRelease(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
{
|
||||
int button = event->wheel.y > 0 ? 4 : 5;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
{
|
||||
SDL_Scancode sc = event->key.keysym.scancode;
|
||||
app_handleKeyPress(sdl_to_xfree86[sc]);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
SDL_Scancode sc = event->key.keysym.scancode;
|
||||
app_handleKeyRelease(sdl_to_xfree86[sc]);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch(event->window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
app_handleEnterEvent(true);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
sdl.exiting = false;
|
||||
app_handleEnterEvent(false);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
app_handleFocusEvent(true);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
app_handleFocusEvent(false);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
{
|
||||
struct Border border;
|
||||
SDL_GetWindowBordersSize(
|
||||
sdl.window,
|
||||
&border.top,
|
||||
&border.left,
|
||||
&border.bottom,
|
||||
&border.right
|
||||
);
|
||||
app_handleResizeEvent(
|
||||
event->window.data1,
|
||||
event->window.data2,
|
||||
1,
|
||||
border);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
app_updateWindowPos(event->window.data1, event->window.data2);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
app_handleCloseEvent();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sdlShowPointer(bool show)
|
||||
{
|
||||
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
|
||||
}
|
||||
|
||||
static void sdlGrabPointer(void)
|
||||
{
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
sdl.pointerGrabbed = true;
|
||||
}
|
||||
|
||||
static void sdlUngrabPointer(void)
|
||||
{
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
sdl.pointerGrabbed = false;
|
||||
}
|
||||
|
||||
static void sdlGrabKeyboard(void)
|
||||
{
|
||||
if (sdl.pointerGrabbed)
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
else
|
||||
{
|
||||
DEBUG_WARN("SDL does not support grabbing only the keyboard, grabbing all");
|
||||
sdl.pointerGrabbed = true;
|
||||
}
|
||||
|
||||
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
sdl.keyboardGrabbed = true;
|
||||
}
|
||||
|
||||
static void sdlUngrabKeyboard(void)
|
||||
{
|
||||
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0");
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
if (sdl.pointerGrabbed)
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
sdl.keyboardGrabbed = false;
|
||||
}
|
||||
|
||||
static void sdlWarpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if (sdl.exiting)
|
||||
return;
|
||||
|
||||
sdl.exiting = exiting;
|
||||
|
||||
// if exiting turn off relative mode
|
||||
if (exiting)
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
|
||||
// issue the warp
|
||||
SDL_WarpMouseInWindow(sdl.window, x, y);
|
||||
}
|
||||
|
||||
static void sdlRealignPointer(void)
|
||||
{
|
||||
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
static bool sdlIsValidPointerPos(int x, int y)
|
||||
{
|
||||
const int displays = SDL_GetNumVideoDisplays();
|
||||
for(int i = 0; i < displays; ++i)
|
||||
{
|
||||
SDL_Rect r;
|
||||
SDL_GetDisplayBounds(i, &r);
|
||||
if ((x >= r.x && x < r.x + r.w) &&
|
||||
(y >= r.y && y < r.y + r.h))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sdlInhibitIdle(void)
|
||||
{
|
||||
SDL_DisableScreenSaver();
|
||||
}
|
||||
|
||||
static void sdlUninhibitIdle(void)
|
||||
{
|
||||
SDL_EnableScreenSaver();
|
||||
}
|
||||
|
||||
static void sdlWait(unsigned int time)
|
||||
{
|
||||
SDL_WaitEventTimeout(NULL, time);
|
||||
}
|
||||
|
||||
static void sdlSetWindowSize(int x, int y)
|
||||
{
|
||||
SDL_SetWindowSize(sdl.window, x, y);
|
||||
}
|
||||
|
||||
static void sdlSetFullscreen(bool fs)
|
||||
{
|
||||
SDL_SetWindowFullscreen(sdl.window, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
}
|
||||
|
||||
static bool sdlGetFullscreen(void)
|
||||
{
|
||||
return (SDL_GetWindowFlags(sdl.window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
|
||||
}
|
||||
|
||||
static void sdlMinimize(void)
|
||||
{
|
||||
SDL_MinimizeWindow(sdl.window);
|
||||
}
|
||||
|
||||
struct LG_DisplayServerOps LGDS_SDL =
|
||||
{
|
||||
.setup = sdlSetup,
|
||||
.probe = sdlProbe,
|
||||
.earlyInit = sdlEarlyInit,
|
||||
.init = sdlInit,
|
||||
.startup = sdlStartup,
|
||||
.shutdown = sdlShutdown,
|
||||
.free = sdlFree,
|
||||
.getProp = sdlGetProp,
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
.getEGLDisplay = sdlGetEGLDisplay,
|
||||
.getEGLNativeWindow = sdlGetEGLNativeWindow,
|
||||
.eglSwapBuffers = sdlEGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
.glCreateContext = sdlGLCreateContext,
|
||||
.glDeleteContext = sdlGLDeleteContext,
|
||||
.glMakeCurrent = sdlGLMakeCurrent,
|
||||
.glSetSwapInterval = sdlGLSetSwapInterval,
|
||||
.glSwapBuffers = sdlGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
.showPointer = sdlShowPointer,
|
||||
.grabPointer = sdlGrabPointer,
|
||||
.ungrabPointer = sdlUngrabPointer,
|
||||
.capturePointer = sdlGrabPointer,
|
||||
.uncapturePointer = sdlUngrabPointer,
|
||||
.grabKeyboard = sdlGrabKeyboard,
|
||||
.ungrabKeyboard = sdlUngrabKeyboard,
|
||||
.warpPointer = sdlWarpPointer,
|
||||
.realignPointer = sdlRealignPointer,
|
||||
.isValidPointerPos = sdlIsValidPointerPos,
|
||||
.inhibitIdle = sdlInhibitIdle,
|
||||
.uninhibitIdle = sdlUninhibitIdle,
|
||||
.wait = sdlWait,
|
||||
.setWindowSize = sdlSetWindowSize,
|
||||
.setFullscreen = sdlSetFullscreen,
|
||||
.getFullscreen = sdlGetFullscreen,
|
||||
.minimize = sdlMinimize,
|
||||
|
||||
/* SDL does not have clipboard support */
|
||||
.cbInit = NULL,
|
||||
};
|
||||
@@ -2,91 +2,100 @@ cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_Wayland LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED
|
||||
wayland-client
|
||||
pkg_check_modules(DISPLAYSERVER_Wayland REQUIRED IMPORTED_TARGET
|
||||
wayland-client
|
||||
wayland-cursor
|
||||
xkbcommon
|
||||
)
|
||||
|
||||
#pkg_check_modules(DISPLAYSERVER_Wayland_OPT_PKGCONFIG
|
||||
#)
|
||||
|
||||
set(DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES "")
|
||||
set(displayserver_Wayland_SHELL_SRC "")
|
||||
|
||||
if (ENABLE_LIBDECOR)
|
||||
pkg_check_modules(DISPLAYSERVER_Wayland_LIBDECOR REQUIRED
|
||||
libdecor-0.1
|
||||
)
|
||||
list(APPEND DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES ${DISPLAYSERVER_Wayland_LIBDECOR_LIBRARIES})
|
||||
list(APPEND DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS ${DISPLAYSERVER_Wayland_LIBDECOR_INCLUDE_DIRS})
|
||||
list(APPEND displayserver_Wayland_SHELL_SRC shell_libdecor.c)
|
||||
add_definitions(-D ENABLE_LIBDECOR)
|
||||
pkg_check_modules(DISPLAYSERVER_Wayland_LIBDECOR REQUIRED IMPORTED_TARGET
|
||||
libdecor-0
|
||||
)
|
||||
list(APPEND DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES PkgConfig::DISPLAYSERVER_Wayland_LIBDECOR)
|
||||
list(APPEND displayserver_Wayland_SHELL_SRC shell_libdecor.c)
|
||||
add_compile_definitions(ENABLE_LIBDECOR)
|
||||
else()
|
||||
list(APPEND displayserver_Wayland_SHELL_SRC shell_xdg.c)
|
||||
list(APPEND displayserver_Wayland_SHELL_SRC shell_xdg.c)
|
||||
endif()
|
||||
|
||||
add_library(displayserver_Wayland STATIC
|
||||
clipboard.c
|
||||
cursor.c
|
||||
gl.c
|
||||
idle.c
|
||||
input.c
|
||||
output.c
|
||||
poll.c
|
||||
state.c
|
||||
registry.c
|
||||
wayland.c
|
||||
window.c
|
||||
${displayserver_Wayland_SHELL_SRC}
|
||||
activation.c
|
||||
clipboard.c
|
||||
cursor.c
|
||||
gl.c
|
||||
idle.c
|
||||
input.c
|
||||
output.c
|
||||
poll.c
|
||||
presentation.c
|
||||
state.c
|
||||
registry.c
|
||||
wayland.c
|
||||
window.c
|
||||
${displayserver_Wayland_SHELL_SRC}
|
||||
)
|
||||
|
||||
target_link_libraries(displayserver_Wayland
|
||||
${DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES}
|
||||
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
PkgConfig::DISPLAYSERVER_Wayland
|
||||
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_Wayland
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS}
|
||||
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
|
||||
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
|
||||
pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15)
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir)
|
||||
|
||||
macro(wayland_generate protocol_file output_file)
|
||||
add_custom_command(OUTPUT "${output_file}.h"
|
||||
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h"
|
||||
DEPENDS "${protocol_file}"
|
||||
VERBATIM)
|
||||
add_custom_command(OUTPUT "${output_file}.h"
|
||||
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h"
|
||||
DEPENDS "${protocol_file}"
|
||||
VERBATIM)
|
||||
|
||||
add_custom_command(OUTPUT "${output_file}.c"
|
||||
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c"
|
||||
DEPENDS "${protocol_file}"
|
||||
VERBATIM)
|
||||
add_custom_command(OUTPUT "${output_file}.c"
|
||||
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c"
|
||||
DEPENDS "${protocol_file}"
|
||||
VERBATIM)
|
||||
|
||||
target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c")
|
||||
target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c")
|
||||
endmacro()
|
||||
|
||||
set(WAYLAND_PROTOCOLS_BASE "${PROJECT_TOP}/repos/wayland-protocols")
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland")
|
||||
include_directories("${CMAKE_BINARY_DIR}/wayland")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-client-protocol")
|
||||
"${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol")
|
||||
"${WAYLAND_PROTOCOLS_BASE}/stable/presentation-time/presentation-time.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-presentation-time-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol")
|
||||
"${WAYLAND_PROTOCOLS_BASE}/stable/viewporter/viewporter.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-viewporter-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol")
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol")
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol")
|
||||
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-output/xdg-output-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-output-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/staging/xdg-activation/xdg-activation-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-activation-v1-client-protocol")
|
||||
|
||||
71
client/displayservers/Wayland/activation.c
Normal file
71
client/displayservers/Wayland/activation.c
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
bool waylandActivationInit(void)
|
||||
{
|
||||
if (!wlWm.xdgActivation)
|
||||
DEBUG_WARN("xdg_activation_v1 not exported by compositor, will not be able "
|
||||
"to request host focus on behalf of guest applications");
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandActivationFree(void)
|
||||
{
|
||||
if (wlWm.xdgActivation)
|
||||
{
|
||||
xdg_activation_v1_destroy(wlWm.xdgActivation);
|
||||
}
|
||||
}
|
||||
|
||||
static void activationTokenDone(void * data,
|
||||
struct xdg_activation_token_v1 * xdgToken, const char * token)
|
||||
{
|
||||
xdg_activation_v1_activate(wlWm.xdgActivation, token, wlWm.surface);
|
||||
xdg_activation_token_v1_destroy(xdgToken);
|
||||
}
|
||||
|
||||
static const struct xdg_activation_token_v1_listener activationTokenListener = {
|
||||
.done = &activationTokenDone,
|
||||
};
|
||||
|
||||
void waylandActivationRequestActivation(void)
|
||||
{
|
||||
if (!wlWm.xdgActivation) return;
|
||||
|
||||
struct xdg_activation_token_v1 * token =
|
||||
xdg_activation_v1_get_activation_token(wlWm.xdgActivation);
|
||||
|
||||
if (!token)
|
||||
{
|
||||
DEBUG_ERROR("failed to retrieve XDG activation token");
|
||||
return;
|
||||
}
|
||||
|
||||
xdg_activation_token_v1_add_listener(token, &activationTokenListener, NULL);
|
||||
xdg_activation_token_v1_set_surface(token, wlWm.surface);
|
||||
xdg_activation_token_v1_commit(token);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -299,7 +298,7 @@ static void dataDeviceHandleEnter(void * data, struct wl_data_device * device,
|
||||
uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, wl_fixed_t syW,
|
||||
struct wl_data_offer * offer)
|
||||
{
|
||||
assert(wlCb.dndOffer == NULL);
|
||||
DEBUG_ASSERT(wlCb.dndOffer == NULL);
|
||||
wlCb.dndOffer = offer;
|
||||
|
||||
struct DataOffer * extra = wl_data_offer_get_user_data(offer);
|
||||
@@ -447,7 +446,7 @@ void waylandCBRequest(LG_ClipboardData type)
|
||||
wl_data_offer_receive(wlCb.offer, wlCb.mimetypes[type], fds[1]);
|
||||
close(fds[1]);
|
||||
|
||||
struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead));
|
||||
struct ClipboardRead * data = malloc(sizeof(*data));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to read clipboard");
|
||||
@@ -513,13 +512,20 @@ error:
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void dataSourceHandleTarget(void * data, struct wl_data_source * source,
|
||||
const char * mimetype)
|
||||
{
|
||||
// Certain Wayland clients send this for copy-paste operations even though
|
||||
// it only makes sense for drag-and-drop. We just do nothing.
|
||||
}
|
||||
|
||||
static void dataSourceHandleSend(void * data, struct wl_data_source * source,
|
||||
const char * mimetype, int fd)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
if (containsMimetype(transfer->mimetypes, mimetype))
|
||||
{
|
||||
struct ClipboardWrite * data = malloc(sizeof(struct ClipboardWrite));
|
||||
struct ClipboardWrite * data = malloc(sizeof(*data));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite");
|
||||
@@ -548,14 +554,15 @@ static void dataSourceHandleCancelled(void * data,
|
||||
}
|
||||
|
||||
static const struct wl_data_source_listener dataSourceListener = {
|
||||
.send = dataSourceHandleSend,
|
||||
.target = dataSourceHandleTarget,
|
||||
.send = dataSourceHandleSend,
|
||||
.cancelled = dataSourceHandleCancelled,
|
||||
};
|
||||
|
||||
static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size)
|
||||
{
|
||||
struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer));
|
||||
struct WCBTransfer * transfer = malloc(sizeof(*transfer));
|
||||
if (!transfer)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating WCBTransfer");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -38,7 +38,7 @@ static const uint32_t cursorBitmap[] = {
|
||||
0x000000, 0x000000, 0x000000, 0x000000,
|
||||
};
|
||||
|
||||
static struct wl_buffer * createCursorBuffer(void)
|
||||
static struct wl_buffer * createSquareCursorBuffer(void)
|
||||
{
|
||||
int fd = memfd_create("lg-cursor", 0);
|
||||
if (fd < 0)
|
||||
@@ -74,6 +74,68 @@ fail:
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool loadThemedCursor(const char * name, struct wl_surface ** surface,
|
||||
struct Point * hotspot)
|
||||
{
|
||||
struct wl_cursor * cursor = wl_cursor_theme_get_cursor(wlWm.cursorTheme, name);
|
||||
if (!cursor)
|
||||
return false;
|
||||
|
||||
struct wl_buffer * buffer = wl_cursor_image_get_buffer(cursor->images[0]);
|
||||
if (!buffer)
|
||||
return false;
|
||||
|
||||
*surface = wl_compositor_create_surface(wlWm.compositor);
|
||||
if (!*surface)
|
||||
return NULL;
|
||||
|
||||
wl_surface_attach(*surface, buffer, 0, 0);
|
||||
wl_surface_set_buffer_scale(*surface, wlWm.cursorScale);
|
||||
wl_surface_commit(*surface);
|
||||
|
||||
*hotspot = (struct Point) {
|
||||
.x = cursor->images[0]->hotspot_x,
|
||||
.y = cursor->images[0]->hotspot_y,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char ** nameLists[LG_POINTER_COUNT] = {
|
||||
[LG_POINTER_ARROW ] = (const char *[]) { "left_ptr", "arrow", NULL },
|
||||
[LG_POINTER_INPUT ] = (const char *[]) { "text", "xterm", "ibeam", NULL },
|
||||
[LG_POINTER_MOVE ] = (const char *[]) {
|
||||
"move", "4498f0e0c1937ffe01fd06f973665830", "9081237383d90e509aa00f00170e968f", NULL
|
||||
},
|
||||
[LG_POINTER_RESIZE_NS ] = (const char *[]) {
|
||||
"sb_v_double_arrow", "size_ver", "v_double_arrow",
|
||||
"2870a09082c103050810ffdffffe0204", "00008160000006810000408080010102", NULL
|
||||
},
|
||||
[LG_POINTER_RESIZE_EW ] = (const char *[]) {
|
||||
"sb_h_double_arrow", "size_hor", "h_double_arrow",
|
||||
"14fef782d02440884392942c11205230", "028006030e0e7ebffc7f7070c0600140", NULL
|
||||
},
|
||||
[LG_POINTER_RESIZE_NESW] = (const char *[]) {
|
||||
"fd_double_arrow", "size_bdiag", "fcf1c3c7cd4491d801f1e1c78f100000", NULL
|
||||
},
|
||||
[LG_POINTER_RESIZE_NWSE] = (const char *[]) {
|
||||
"bd_double_arrow", "size_fdiag", "c7088f0f3e6c8088236ef8e1e3e70000", NULL
|
||||
},
|
||||
[LG_POINTER_HAND ] = (const char *[]) {
|
||||
"hand", "pointing_hand", "hand1", "hand2", "pointer",
|
||||
"e29285e634086352946a0e7090d73106", "9d800788f1b08800ae810202380a0822", NULL
|
||||
},
|
||||
[LG_POINTER_NOT_ALLOWED] = (const char *[]) { "crossed_circle", "not-allowed", NULL },
|
||||
};
|
||||
|
||||
static void reloadCursors(void)
|
||||
{
|
||||
if (wlWm.cursorTheme)
|
||||
for (LG_DSPointer pointer = LG_POINTER_ARROW; pointer < LG_POINTER_COUNT; ++pointer)
|
||||
for (const char ** names = nameLists[pointer]; *names; ++names)
|
||||
if (loadThemedCursor(*names, wlWm.cursors + pointer, wlWm.cursorHot + pointer))
|
||||
break;
|
||||
}
|
||||
|
||||
bool waylandCursorInit(void)
|
||||
{
|
||||
if (!wlWm.compositor)
|
||||
@@ -82,27 +144,79 @@ bool waylandCursorInit(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
wlWm.cursorBuffer = createCursorBuffer();
|
||||
if (wlWm.cursorBuffer)
|
||||
wlWm.cursorSquareBuffer = createSquareCursorBuffer();
|
||||
if (wlWm.cursorSquareBuffer)
|
||||
{
|
||||
wlWm.cursor = wl_compositor_create_surface(wlWm.compositor);
|
||||
wl_surface_attach(wlWm.cursor, wlWm.cursorBuffer, 0, 0);
|
||||
wl_surface_commit(wlWm.cursor);
|
||||
wlWm.cursors[LG_POINTER_SQUARE] = wl_compositor_create_surface(wlWm.compositor);
|
||||
wl_surface_attach(wlWm.cursors[LG_POINTER_SQUARE], wlWm.cursorSquareBuffer, 0, 0);
|
||||
wl_surface_commit(wlWm.cursors[LG_POINTER_SQUARE]);
|
||||
}
|
||||
|
||||
wlWm.cursorThemeName = getenv("XCURSOR_THEME");
|
||||
wlWm.cursorSize = 24;
|
||||
|
||||
const char * cursorSizeEnv = getenv("XCURSOR_SIZE");
|
||||
if (cursorSizeEnv)
|
||||
{
|
||||
int size = atoi(cursorSizeEnv);
|
||||
if (size)
|
||||
wlWm.cursorSize = size;
|
||||
}
|
||||
|
||||
wlWm.cursorTheme = wl_cursor_theme_load(wlWm.cursorThemeName, wlWm.cursorSize, wlWm.shm);
|
||||
wlWm.cursorScale = 1;
|
||||
reloadCursors();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandCursorFree(void)
|
||||
{
|
||||
if (wlWm.cursor)
|
||||
wl_surface_destroy(wlWm.cursor);
|
||||
if (wlWm.cursorBuffer)
|
||||
wl_buffer_destroy(wlWm.cursorBuffer);
|
||||
for (int i = 0; i < LG_POINTER_COUNT; ++i)
|
||||
if (wlWm.cursors[i])
|
||||
wl_surface_destroy(wlWm.cursors[i]);
|
||||
if (wlWm.cursorTheme)
|
||||
wl_cursor_theme_destroy(wlWm.cursorTheme);
|
||||
if (wlWm.cursorSquareBuffer)
|
||||
wl_buffer_destroy(wlWm.cursorSquareBuffer);
|
||||
}
|
||||
|
||||
void waylandShowPointer(bool show)
|
||||
void waylandCursorScaleChange(void)
|
||||
{
|
||||
wlWm.showPointer = show;
|
||||
wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, show ? wlWm.cursor : NULL, 0, 0);
|
||||
int newScale = ceil(wl_fixed_to_double(wlWm.scale));
|
||||
if (newScale == wlWm.cursorScale)
|
||||
return;
|
||||
|
||||
struct wl_cursor_theme * new = wl_cursor_theme_load(wlWm.cursorThemeName,
|
||||
wlWm.cursorSize * newScale, wlWm.shm);
|
||||
|
||||
if (!new)
|
||||
return;
|
||||
|
||||
struct wl_surface * old[LG_POINTER_COUNT];
|
||||
memcpy(old, wlWm.cursors, sizeof(old));
|
||||
memset(wlWm.cursors, 0, sizeof(wlWm.cursors));
|
||||
|
||||
if (wlWm.cursorTheme)
|
||||
wl_cursor_theme_destroy(wlWm.cursorTheme);
|
||||
|
||||
wlWm.cursorTheme = new;
|
||||
wlWm.cursorScale = newScale;
|
||||
reloadCursors();
|
||||
|
||||
waylandSetPointer(wlWm.cursorId);
|
||||
|
||||
for (int i = 0; i < LG_POINTER_COUNT; ++i)
|
||||
if (old[i])
|
||||
wl_surface_destroy(old[i]);
|
||||
}
|
||||
|
||||
void waylandSetPointer(LG_DSPointer pointer)
|
||||
{
|
||||
wlWm.cursorId = pointer;
|
||||
wlWm.cursor = wlWm.cursors[pointer];
|
||||
wlWm.cursorHotX = wlWm.cursorHot[pointer].x;
|
||||
wlWm.cursorHotY = wlWm.cursorHot[pointer].y;
|
||||
if (wlWm.pointer)
|
||||
wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, wlWm.cursor, wlWm.cursorHotX, wlWm.cursorHotY);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
#include "egl_dynprocs.h"
|
||||
#include "eglutil.h"
|
||||
|
||||
bool waylandEGLInit(int w, int h)
|
||||
{
|
||||
@@ -71,62 +72,63 @@ EGLDisplay waylandGetEGLDisplay(void)
|
||||
|
||||
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
|
||||
{
|
||||
if (!wlWm.eglSwapWithDamageInit)
|
||||
if (!wlWm.swapWithDamage.init)
|
||||
{
|
||||
const char *exts = eglQueryString(display, EGL_EXTENSIONS);
|
||||
wlWm.eglSwapWithDamageInit = true;
|
||||
if (wl_proxy_get_version((struct wl_proxy *) wlWm.surface) < 4)
|
||||
{
|
||||
DEBUG_INFO("Swapping buffers with damage: not supported, need wl_compositor v4");
|
||||
else if (util_hasGLExt(exts, "EGL_KHR_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageKHR)
|
||||
{
|
||||
wlWm.eglSwapWithDamage = g_egl_dynProcs.eglSwapBuffersWithDamageKHR;
|
||||
DEBUG_INFO("Using EGL_KHR_swap_buffers_with_damage");
|
||||
}
|
||||
else if (util_hasGLExt(exts, "EGL_EXT_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageEXT)
|
||||
{
|
||||
wlWm.eglSwapWithDamage = g_egl_dynProcs.eglSwapBuffersWithDamageEXT;
|
||||
DEBUG_INFO("Using EGL_EXT_swap_buffers_with_damage");
|
||||
swapWithDamageDisable(&wlWm.swapWithDamage);
|
||||
}
|
||||
else
|
||||
DEBUG_INFO("Swapping buffers with damage: not supported");
|
||||
swapWithDamageInit(&wlWm.swapWithDamage, display);
|
||||
}
|
||||
|
||||
if (wlWm.eglSwapWithDamage && count)
|
||||
{
|
||||
if (count * 4 > wlWm.eglDamageRectCount)
|
||||
{
|
||||
free(wlWm.eglDamageRects);
|
||||
wlWm.eglDamageRects = malloc(sizeof(EGLint) * count * 4);
|
||||
if (!wlWm.eglDamageRects)
|
||||
DEBUG_FATAL("Out of memory");
|
||||
wlWm.eglDamageRectCount = count * 4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
wlWm.eglDamageRects[i*4+0] = damage[i].x;
|
||||
wlWm.eglDamageRects[i*4+1] = damage[i].y;
|
||||
wlWm.eglDamageRects[i*4+2] = damage[i].w;
|
||||
wlWm.eglDamageRects[i*4+3] = damage[i].h;
|
||||
}
|
||||
|
||||
wlWm.eglSwapWithDamage(display, surface, wlWm.eglDamageRects, count);
|
||||
}
|
||||
else
|
||||
eglSwapBuffers(display, surface);
|
||||
waylandPresentationFrame();
|
||||
swapWithDamage(&wlWm.swapWithDamage, display, surface, damage, count);
|
||||
|
||||
if (wlWm.needsResize)
|
||||
{
|
||||
wl_egl_window_resize(wlWm.eglWindow, wlWm.width * wlWm.scale, wlWm.height * wlWm.scale, 0, 0);
|
||||
wl_surface_set_buffer_scale(wlWm.surface, wlWm.scale);
|
||||
bool skipResize = false;
|
||||
wl_egl_window_resize(wlWm.eglWindow, wl_fixed_to_int(wlWm.width * wlWm.scale),
|
||||
wl_fixed_to_int(wlWm.height * wlWm.scale), 0, 0);
|
||||
|
||||
if (wlWm.width == 0 || wlWm.height == 0)
|
||||
skipResize = true;
|
||||
else if (wlWm.fractionalScale)
|
||||
{
|
||||
wl_surface_set_buffer_scale(wlWm.surface, 1);
|
||||
if (!wlWm.viewport)
|
||||
wlWm.viewport = wp_viewporter_get_viewport(wlWm.viewporter, wlWm.surface);
|
||||
wp_viewport_set_source(wlWm.viewport, 0, 0, wlWm.width * wlWm.scale, wlWm.height * wlWm.scale);
|
||||
wp_viewport_set_destination(wlWm.viewport, wlWm.width, wlWm.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wlWm.viewport)
|
||||
{
|
||||
// Clearing the source and destination rectangles should happen in wp_viewport_destroy.
|
||||
// However, wlroots does not clear the rectangle until fixed in 456c6e22 (2021-08-02).
|
||||
// This should be kept to work around old versions of wlroots.
|
||||
wl_fixed_t clear = wl_fixed_from_int(-1);
|
||||
wp_viewport_set_source(wlWm.viewport, clear, clear, clear, clear);
|
||||
wp_viewport_set_destination(wlWm.viewport, -1, -1);
|
||||
|
||||
wp_viewport_destroy(wlWm.viewport);
|
||||
wlWm.viewport = NULL;
|
||||
}
|
||||
wl_surface_set_buffer_scale(wlWm.surface, wl_fixed_to_int(wlWm.scale));
|
||||
}
|
||||
|
||||
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
|
||||
wl_region_add(region, 0, 0, wlWm.width, wlWm.height);
|
||||
wl_surface_set_opaque_region(wlWm.surface, region);
|
||||
wl_region_destroy(region);
|
||||
|
||||
app_handleResizeEvent(wlWm.width, wlWm.height, wlWm.scale, (struct Border) {0, 0, 0, 0});
|
||||
wlWm.needsResize = false;
|
||||
app_handleResizeEvent(wlWm.width, wlWm.height, wl_fixed_to_double(wlWm.scale),
|
||||
(struct Border) {0, 0, 0, 0});
|
||||
app_invalidateWindow(true);
|
||||
waylandStopWaitFrame();
|
||||
wlWm.needsResize = skipResize;
|
||||
}
|
||||
|
||||
waylandShellAckConfigureIfNeeded();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -20,11 +20,14 @@
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
@@ -52,7 +55,7 @@ static void pointerEnterHandler(void * data, struct wl_pointer * pointer,
|
||||
wlWm.pointerInSurface = true;
|
||||
app_handleEnterEvent(true);
|
||||
|
||||
wl_pointer_set_cursor(pointer, serial, wlWm.showPointer ? wlWm.cursor : NULL, 0, 0);
|
||||
wl_pointer_set_cursor(pointer, serial, wlWm.cursor, wlWm.cursorHotX, wlWm.cursorHotY);
|
||||
wlWm.pointerEnterSerial = serial;
|
||||
|
||||
wlWm.cursorX = wl_fixed_to_double(sxW);
|
||||
@@ -85,11 +88,15 @@ static void pointerLeaveHandler(void * data, struct wl_pointer * pointer,
|
||||
static void pointerAxisHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, uint32_t axis, wl_fixed_t value)
|
||||
{
|
||||
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
return;
|
||||
|
||||
int button = value > 0 ?
|
||||
5 /* SPICE_MOUSE_BUTTON_DOWN */ :
|
||||
4 /* SPICE_MOUSE_BUTTON_UP */;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
app_handleWheelMotion(wl_fixed_to_double(value) / 15.0);
|
||||
}
|
||||
|
||||
static int mapWaylandToSpiceButton(uint32_t button)
|
||||
@@ -155,9 +162,64 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
|
||||
static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t format, int fd, uint32_t size)
|
||||
{
|
||||
if (!wlWm.xkb)
|
||||
goto done;
|
||||
|
||||
if (wlWm.keymap)
|
||||
{
|
||||
xkb_keymap_unref(wlWm.keymap);
|
||||
wlWm.keymap = NULL;
|
||||
}
|
||||
|
||||
if (wlWm.xkbState)
|
||||
{
|
||||
xkb_state_unref(wlWm.xkbState);
|
||||
wlWm.xkbState = NULL;
|
||||
}
|
||||
|
||||
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
|
||||
{
|
||||
DEBUG_WARN("Unsupported keymap format, keyboard input will not work: %d", format);
|
||||
goto done;
|
||||
}
|
||||
|
||||
char * map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (map == MAP_FAILED)
|
||||
{
|
||||
DEBUG_ERROR("Failed to mmap keymap: %s", strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
wlWm.keymap = xkb_keymap_new_from_string(wlWm.xkb, map,
|
||||
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
|
||||
if (!wlWm.keymap)
|
||||
DEBUG_WARN("Failed to load keymap, keyboard input will not work");
|
||||
|
||||
munmap(map, size);
|
||||
|
||||
if (wlWm.keymap)
|
||||
{
|
||||
wlWm.xkbState = xkb_state_new(wlWm.keymap);
|
||||
if (!wlWm.xkbState)
|
||||
DEBUG_WARN("Failed to create xkb_state");
|
||||
}
|
||||
|
||||
done:
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static int getCharcode(uint32_t key)
|
||||
{
|
||||
key += 8; // xkb scancode is evdev scancode + 8
|
||||
xkb_keysym_t sym = xkb_state_key_get_one_sym(wlWm.xkbState, key);
|
||||
if (sym == XKB_KEY_NoSymbol)
|
||||
return 0;
|
||||
|
||||
sym = xkb_keysym_to_upper(sym);
|
||||
return xkb_keysym_to_utf32(sym);
|
||||
}
|
||||
|
||||
static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
|
||||
{
|
||||
@@ -170,7 +232,7 @@ static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
|
||||
|
||||
uint32_t * key;
|
||||
wl_array_for_each(key, keys)
|
||||
app_handleKeyPress(*key);
|
||||
app_handleKeyPress(*key, getCharcode(*key));
|
||||
}
|
||||
|
||||
static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard,
|
||||
@@ -191,16 +253,44 @@ static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard,
|
||||
return;
|
||||
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
app_handleKeyPress(key);
|
||||
app_handleKeyPress(key, getCharcode(key));
|
||||
else
|
||||
app_handleKeyRelease(key);
|
||||
app_handleKeyRelease(key, getCharcode(key));
|
||||
|
||||
if (!wlWm.xkbState || !app_isOverlayMode() || state != WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
return;
|
||||
|
||||
key += 8; // xkb scancode is evdev scancode + 8
|
||||
int size = xkb_state_key_get_utf8(wlWm.xkbState, key, NULL, 0);
|
||||
|
||||
if (size <= 0)
|
||||
return;
|
||||
|
||||
char buffer[size + 1];
|
||||
xkb_state_key_get_utf8(wlWm.xkbState, key, buffer, size + 1);
|
||||
app_handleKeyboardTyped(buffer);
|
||||
}
|
||||
|
||||
static void keyboardModifiersHandler(void * data,
|
||||
struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed,
|
||||
uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
||||
{
|
||||
// Do nothing.
|
||||
if (!wlWm.xkbState)
|
||||
return;
|
||||
|
||||
xkb_state_update_mask(wlWm.xkbState, modsDepressed, modsLatched, modsLocked, 0, 0, group);
|
||||
app_handleKeyboardModifiers(
|
||||
xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0,
|
||||
xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0,
|
||||
xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0,
|
||||
xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0
|
||||
);
|
||||
|
||||
app_handleKeyboardLEDs(
|
||||
xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_NUM) > 0,
|
||||
xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_CAPS) > 0,
|
||||
xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_SCROLL) > 0
|
||||
);
|
||||
}
|
||||
|
||||
static const struct wl_keyboard_listener keyboardListener = {
|
||||
@@ -211,20 +301,53 @@ static const struct wl_keyboard_listener keyboardListener = {
|
||||
.modifiers = keyboardModifiersHandler,
|
||||
};
|
||||
|
||||
static void waylandCleanUpPointer(void)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.confineLock, {
|
||||
if (wlWm.lockedPointer)
|
||||
{
|
||||
zwp_locked_pointer_v1_destroy(wlWm.lockedPointer);
|
||||
wlWm.lockedPointer = NULL;
|
||||
}
|
||||
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_destroy(wlWm.confinedPointer);
|
||||
wlWm.confinedPointer = NULL;
|
||||
}
|
||||
});
|
||||
|
||||
if (wlWm.relativePointer)
|
||||
{
|
||||
zwp_relative_pointer_v1_destroy(wlWm.relativePointer);
|
||||
wlWm.relativePointer = NULL;
|
||||
}
|
||||
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wlWm.pointer = NULL;
|
||||
}
|
||||
|
||||
// Seat-handling listeners.
|
||||
|
||||
static void handlePointerCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
|
||||
if (!hasPointer && wlWm.pointer)
|
||||
{
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wlWm.pointer = NULL;
|
||||
}
|
||||
waylandCleanUpPointer();
|
||||
else if (hasPointer && !wlWm.pointer)
|
||||
{
|
||||
wlWm.pointer = wl_seat_get_pointer(wlWm.seat);
|
||||
wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL);
|
||||
waylandSetPointer(wlWm.cursorId);
|
||||
|
||||
if (wlWm.warpSupport)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,18 +412,13 @@ bool waylandInputInit(void)
|
||||
DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by "
|
||||
"compositor, keyboard will not be grabbed");
|
||||
|
||||
wlWm.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
if (!wlWm.xkb)
|
||||
DEBUG_WARN("Failed to initialize xkb, keyboard input will not work");
|
||||
|
||||
wl_seat_add_listener(wlWm.seat, &seatListener, NULL);
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
|
||||
if (wlWm.warpSupport)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
LG_LOCK_INIT(wlWm.confineLock);
|
||||
|
||||
return true;
|
||||
@@ -310,9 +428,25 @@ void waylandInputFree(void)
|
||||
{
|
||||
waylandUngrabPointer();
|
||||
LG_LOCK_FREE(wlWm.confineLock);
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wl_keyboard_destroy(wlWm.keyboard);
|
||||
|
||||
if (wlWm.pointer)
|
||||
waylandCleanUpPointer();
|
||||
|
||||
// The only legal way the keyboard can be null is if it never existed.
|
||||
// When unplugged, the compositor must have an inert object.
|
||||
if (wlWm.keyboard)
|
||||
wl_keyboard_destroy(wlWm.keyboard);
|
||||
|
||||
wl_seat_destroy(wlWm.seat);
|
||||
|
||||
if (wlWm.xkbState)
|
||||
xkb_state_unref(wlWm.xkbState);
|
||||
|
||||
if (wlWm.keymap)
|
||||
xkb_keymap_unref(wlWm.keymap);
|
||||
|
||||
if (wlWm.xkb)
|
||||
xkb_context_unref(wlWm.xkb);
|
||||
}
|
||||
|
||||
void waylandGrabPointer(void)
|
||||
@@ -331,7 +465,7 @@ void waylandGrabPointer(void)
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.confineLock,
|
||||
{
|
||||
if (!wlWm.confinedPointer)
|
||||
if (!wlWm.confinedPointer && !wlWm.lockedPointer)
|
||||
{
|
||||
wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
|
||||
@@ -340,16 +474,19 @@ void waylandGrabPointer(void)
|
||||
});
|
||||
}
|
||||
|
||||
void waylandUngrabPointer(void)
|
||||
inline static void internalUngrabPointer(bool lock)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.confineLock,
|
||||
if (lock)
|
||||
LG_LOCK(wlWm.confineLock);
|
||||
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_destroy(wlWm.confinedPointer);
|
||||
wlWm.confinedPointer = NULL;
|
||||
}
|
||||
});
|
||||
zwp_confined_pointer_v1_destroy(wlWm.confinedPointer);
|
||||
wlWm.confinedPointer = NULL;
|
||||
}
|
||||
|
||||
if (lock)
|
||||
LG_UNLOCK(wlWm.confineLock);
|
||||
|
||||
if (!wlWm.warpSupport)
|
||||
{
|
||||
@@ -367,6 +504,11 @@ void waylandUngrabPointer(void)
|
||||
}
|
||||
}
|
||||
|
||||
void waylandUngrabPointer(void)
|
||||
{
|
||||
internalUngrabPointer(true);
|
||||
}
|
||||
|
||||
void waylandCapturePointer(void)
|
||||
{
|
||||
if (!wlWm.warpSupport)
|
||||
@@ -407,10 +549,8 @@ void waylandUncapturePointer(void)
|
||||
* - if the user has opted to use captureInputOnly mode.
|
||||
*/
|
||||
if (!wlWm.warpSupport || !app_isFormatValid() || app_isCaptureOnlyMode())
|
||||
{
|
||||
waylandUngrabPointer();
|
||||
}
|
||||
else
|
||||
internalUngrabPointer(false);
|
||||
else if (wlWm.pointer)
|
||||
{
|
||||
wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
|
||||
@@ -444,7 +584,7 @@ void waylandWarpPointer(int x, int y, bool exiting)
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.confineLock,
|
||||
{
|
||||
if (!wlWm.lockedPointer)
|
||||
if (wlWm.lockedPointer)
|
||||
{
|
||||
LG_UNLOCK(wlWm.confineLock);
|
||||
return;
|
||||
@@ -487,7 +627,10 @@ void waylandRealignPointer(void)
|
||||
|
||||
void waylandGuestPointerUpdated(double x, double y, double localX, double localY)
|
||||
{
|
||||
if (!wlWm.warpSupport || !wlWm.pointerInSurface || wlWm.lockedPointer)
|
||||
if ( !wlWm.pointer ||
|
||||
!wlWm.warpSupport ||
|
||||
!wlWm.pointerInSurface ||
|
||||
wlWm.lockedPointer )
|
||||
return;
|
||||
|
||||
waylandWarpPointer((int) localX, (int) localY, false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -27,29 +27,63 @@
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
static void outputGeometryHandler(void * data, struct wl_output * output, int32_t x, int32_t y,
|
||||
static void outputUpdateScale(struct WaylandOutput * node)
|
||||
{
|
||||
wl_fixed_t original = node->scale;
|
||||
|
||||
if (!wlWm.useFractionalScale || !wlWm.viewporter || !node->logicalWidth)
|
||||
node->scale = wl_fixed_from_int(node->scaleInt);
|
||||
else
|
||||
{
|
||||
int32_t modeWidth = node->modeRotate ? node->modeHeight : node->modeWidth;
|
||||
node->scale = wl_fixed_from_double(1.0 * modeWidth / node->logicalWidth);
|
||||
}
|
||||
|
||||
if (original != node->scale)
|
||||
waylandWindowUpdateScale();
|
||||
}
|
||||
|
||||
static void outputGeometryHandler(void * opaque, struct wl_output * output, int32_t x, int32_t y,
|
||||
int32_t physical_width, int32_t physical_height, int32_t subpixel, const char * make,
|
||||
const char * model, int32_t output_transform)
|
||||
{
|
||||
// Do nothing.
|
||||
struct WaylandOutput * node = opaque;
|
||||
|
||||
switch (output_transform)
|
||||
{
|
||||
case WL_OUTPUT_TRANSFORM_90:
|
||||
case WL_OUTPUT_TRANSFORM_270:
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
|
||||
node->modeRotate = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
node->modeRotate = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void outputModeHandler(void * data, struct wl_output * wl_output, uint32_t flags,
|
||||
static void outputModeHandler(void * opaque, struct wl_output * wl_output, uint32_t flags,
|
||||
int32_t width, int32_t height, int32_t refresh)
|
||||
{
|
||||
// Do nothing.
|
||||
if (!(flags & WL_OUTPUT_MODE_CURRENT))
|
||||
return;
|
||||
|
||||
struct WaylandOutput * node = opaque;
|
||||
node->modeWidth = width;
|
||||
node->modeHeight = height;
|
||||
}
|
||||
|
||||
static void outputDoneHandler(void * data, struct wl_output * output)
|
||||
static void outputDoneHandler(void * opaque, struct wl_output * output)
|
||||
{
|
||||
// Do nothing.
|
||||
struct WaylandOutput * node = opaque;
|
||||
outputUpdateScale(node);
|
||||
}
|
||||
|
||||
static void outputScaleHandler(void * opaque, struct wl_output * output, int32_t scale)
|
||||
{
|
||||
struct WaylandOutput * node = opaque;
|
||||
node->scale = scale;
|
||||
waylandWindowUpdateScale();
|
||||
node->scaleInt = scale;
|
||||
}
|
||||
|
||||
static const struct wl_output_listener outputListener = {
|
||||
@@ -59,6 +93,45 @@ static const struct wl_output_listener outputListener = {
|
||||
.scale = outputScaleHandler,
|
||||
};
|
||||
|
||||
static void xdgOutputLogicalPositionHandler(void * opaque, struct zxdg_output_v1 * xdgOutput,
|
||||
int32_t x, int32_t y)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void xdgOutputLogicalSizeHandler(void * opaque, struct zxdg_output_v1 * xdgOutput,
|
||||
int32_t width, int32_t height)
|
||||
{
|
||||
struct WaylandOutput * node = opaque;
|
||||
node->logicalWidth = width;
|
||||
node->logicalHeight = height;
|
||||
}
|
||||
|
||||
static void xdgOutputDoneHandler(void * opaque, struct zxdg_output_v1 * xdgOutput)
|
||||
{
|
||||
struct WaylandOutput * node = opaque;
|
||||
outputUpdateScale(node);
|
||||
}
|
||||
|
||||
static void xdgOutputNameHandler(void * opaque, struct zxdg_output_v1 * xdgOutput, const char * name)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void xdgOutputDescriptionHandler(void * opaque, struct zxdg_output_v1 * xdgOutput,
|
||||
const char * description)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct zxdg_output_v1_listener xdgOutputListener = {
|
||||
.logical_position = xdgOutputLogicalPositionHandler,
|
||||
.logical_size = xdgOutputLogicalSizeHandler,
|
||||
.done = xdgOutputDoneHandler,
|
||||
.name = xdgOutputNameHandler,
|
||||
.description = xdgOutputDescriptionHandler,
|
||||
};
|
||||
|
||||
bool waylandOutputInit(void)
|
||||
{
|
||||
wl_list_init(&wlWm.outputs);
|
||||
@@ -73,6 +146,8 @@ void waylandOutputFree(void)
|
||||
{
|
||||
if (node->version >= 3)
|
||||
wl_output_release(node->output);
|
||||
if (node->xdgOutput)
|
||||
zxdg_output_v1_destroy(node->xdgOutput);
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
}
|
||||
@@ -80,13 +155,14 @@ void waylandOutputFree(void)
|
||||
|
||||
void waylandOutputBind(uint32_t name, uint32_t version)
|
||||
{
|
||||
struct WaylandOutput * node = malloc(sizeof(struct WaylandOutput));
|
||||
struct WaylandOutput * node = calloc(1, sizeof(struct WaylandOutput));
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
if (version < 2)
|
||||
{
|
||||
DEBUG_WARN("wl_output version too old: expected >= 2, got %d", version);
|
||||
free(node);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,6 +179,13 @@ void waylandOutputBind(uint32_t name, uint32_t version)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wlWm.xdgOutputManager)
|
||||
{
|
||||
node->xdgOutput = zxdg_output_manager_v1_get_xdg_output(wlWm.xdgOutputManager, node->output);
|
||||
if (node->xdgOutput)
|
||||
zxdg_output_v1_add_listener(node->xdgOutput, &xdgOutputListener, node);
|
||||
}
|
||||
|
||||
wl_output_add_listener(node->output, &outputListener, node);
|
||||
wl_list_insert(&wlWm.outputs, &node->link);
|
||||
}
|
||||
@@ -117,6 +200,8 @@ void waylandOutputTryUnbind(uint32_t name)
|
||||
{
|
||||
if (node->version >= 3)
|
||||
wl_output_release(node->output);
|
||||
if (node->xdgOutput)
|
||||
zxdg_output_v1_destroy(node->xdgOutput);
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
break;
|
||||
@@ -124,7 +209,7 @@ void waylandOutputTryUnbind(uint32_t name)
|
||||
}
|
||||
}
|
||||
|
||||
int32_t waylandOutputGetScale(struct wl_output * output)
|
||||
wl_fixed_t waylandOutputGetScale(struct wl_output * output)
|
||||
{
|
||||
struct WaylandOutput * node;
|
||||
|
||||
@@ -133,4 +218,3 @@ int32_t waylandOutputGetScale(struct wl_output * output)
|
||||
return node->scale;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -30,8 +30,13 @@
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#ifdef ENABLE_LIBDECOR
|
||||
#include <libdecor.h>
|
||||
#endif
|
||||
|
||||
#define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait
|
||||
|
||||
#ifndef ENABLE_LIBDECOR
|
||||
static void waylandDisplayCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
if (events & EPOLLERR)
|
||||
@@ -40,6 +45,7 @@ static void waylandDisplayCallback(uint32_t events, void * opaque)
|
||||
wl_display_read_events(wlWm.display);
|
||||
wl_display_dispatch_pending(wlWm.display);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool waylandPollInit(void)
|
||||
{
|
||||
@@ -55,21 +61,27 @@ bool waylandPollInit(void)
|
||||
LG_LOCK_INIT(wlWm.pollLock);
|
||||
LG_LOCK_INIT(wlWm.pollFreeLock);
|
||||
|
||||
#ifndef ENABLE_LIBDECOR
|
||||
wlWm.displayFd = wl_display_get_fd(wlWm.display);
|
||||
if (!waylandPollRegister(wlWm.displayFd, waylandDisplayCallback, NULL, EPOLLIN))
|
||||
{
|
||||
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandWait(unsigned int time)
|
||||
{
|
||||
#ifdef ENABLE_LIBDECOR
|
||||
libdecor_dispatch(wlWm.libdecor, 0);
|
||||
#else
|
||||
while (wl_display_prepare_read(wlWm.display))
|
||||
wl_display_dispatch_pending(wlWm.display);
|
||||
wl_display_flush(wlWm.display);
|
||||
#endif
|
||||
|
||||
struct epoll_event events[EPOLL_EVENTS];
|
||||
int count;
|
||||
@@ -77,21 +89,29 @@ void waylandWait(unsigned int time)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
DEBUG_INFO("epoll failed: %s", strerror(errno));
|
||||
#ifndef ENABLE_LIBDECOR
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef ENABLE_LIBDECOR
|
||||
bool sawDisplay = false;
|
||||
#endif
|
||||
for (int i = 0; i < count; ++i) {
|
||||
struct WaylandPoll * poll = events[i].data.ptr;
|
||||
if (!poll->removed)
|
||||
poll->callback(events[i].events, poll->opaque);
|
||||
#ifndef ENABLE_LIBDECOR
|
||||
if (poll->fd == wlWm.displayFd)
|
||||
sawDisplay = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef ENABLE_LIBDECOR
|
||||
if (!sawDisplay)
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
#endif
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
@@ -115,7 +135,7 @@ static void waylandPollRemoveNode(struct WaylandPoll * node)
|
||||
|
||||
bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events)
|
||||
{
|
||||
struct WaylandPoll * node = malloc(sizeof(struct WaylandPoll));
|
||||
struct WaylandPoll * node = malloc(sizeof(*node));
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
|
||||
120
client/displayservers/Wayland/presentation.c
Normal file
120
client/displayservers/Wayland/presentation.c
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/time.h"
|
||||
|
||||
struct FrameData
|
||||
{
|
||||
struct timespec sent;
|
||||
};
|
||||
|
||||
static void presentationClockId(void * data,
|
||||
struct wp_presentation * presentation, uint32_t clkId)
|
||||
{
|
||||
wlWm.clkId = clkId;
|
||||
}
|
||||
|
||||
static const struct wp_presentation_listener presentationListener = {
|
||||
.clock_id = presentationClockId,
|
||||
};
|
||||
|
||||
static void presentationFeedbackSyncOutput(void * data,
|
||||
struct wp_presentation_feedback * feedback, struct wl_output * output)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void presentationFeedbackPresented(void * opaque,
|
||||
struct wp_presentation_feedback * feedback, uint32_t tvSecHi, uint32_t tvSecLo,
|
||||
uint32_t tvNsec, uint32_t refresh, uint32_t seqHi, uint32_t seqLo, uint32_t flags)
|
||||
{
|
||||
struct FrameData * data = opaque;
|
||||
struct timespec present = {
|
||||
.tv_sec = (uint64_t) tvSecHi << 32 | tvSecLo,
|
||||
.tv_nsec = tvNsec,
|
||||
};
|
||||
struct timespec delta;
|
||||
|
||||
tsDiff(&delta, &present, &data->sent);
|
||||
ringbuffer_push(wlWm.photonTimings, &(float){ delta.tv_sec + delta.tv_nsec * 1e-6f });
|
||||
free(data);
|
||||
wp_presentation_feedback_destroy(feedback);
|
||||
}
|
||||
|
||||
static void presentationFeedbackDiscarded(void * data,
|
||||
struct wp_presentation_feedback * feedback)
|
||||
{
|
||||
free(data);
|
||||
wp_presentation_feedback_destroy(feedback);
|
||||
}
|
||||
|
||||
static const struct wp_presentation_feedback_listener presentationFeedbackListener = {
|
||||
.sync_output = presentationFeedbackSyncOutput,
|
||||
.presented = presentationFeedbackPresented,
|
||||
.discarded = presentationFeedbackDiscarded,
|
||||
};
|
||||
|
||||
bool waylandPresentationInit(void)
|
||||
{
|
||||
if (wlWm.presentation)
|
||||
{
|
||||
wlWm.photonTimings = ringbuffer_new(256, sizeof(float));
|
||||
wlWm.photonGraph = app_registerGraph("PHOTON", wlWm.photonTimings,
|
||||
0.0f, 30.0f, NULL);
|
||||
wp_presentation_add_listener(wlWm.presentation, &presentationListener, NULL);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandPresentationFree(void)
|
||||
{
|
||||
if (!wlWm.presentation)
|
||||
return;
|
||||
|
||||
wp_presentation_destroy(wlWm.presentation);
|
||||
app_unregisterGraph(wlWm.photonGraph);
|
||||
ringbuffer_free(&wlWm.photonTimings);
|
||||
}
|
||||
|
||||
void waylandPresentationFrame(void)
|
||||
{
|
||||
if (!wlWm.presentation)
|
||||
return;
|
||||
|
||||
struct FrameData * data = malloc(sizeof(*data));
|
||||
if (clock_gettime(wlWm.clkId, &data->sent))
|
||||
{
|
||||
DEBUG_ERROR("clock_gettime failed: %s\n", strerror(errno));
|
||||
free(data);
|
||||
}
|
||||
|
||||
struct wp_presentation_feedback * feedback = wp_presentation_feedback(wlWm.presentation, wlWm.surface);
|
||||
wp_presentation_feedback_add_listener(feedback, &presentationFeedbackListener, data);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -47,6 +47,12 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry,
|
||||
wlWm.xdgDecorationManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zxdg_decoration_manager_v1_interface, 1);
|
||||
#endif
|
||||
else if (!strcmp(interface, wp_presentation_interface.name))
|
||||
wlWm.presentation = wl_registry_bind(wlWm.registry, name,
|
||||
&wp_presentation_interface, 1);
|
||||
else if (!strcmp(interface, wp_viewporter_interface.name))
|
||||
wlWm.viewporter = wl_registry_bind(wlWm.registry, name,
|
||||
&wp_viewporter_interface, 1);
|
||||
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name))
|
||||
wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_relative_pointer_manager_v1_interface, 1);
|
||||
@@ -62,6 +68,13 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry,
|
||||
else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name))
|
||||
wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_idle_inhibit_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, zxdg_output_manager_v1_interface.name) && version >= 2)
|
||||
wlWm.xdgOutputManager = wl_registry_bind(wlWm.registry, name,
|
||||
// we only need v2 to run, but v3 saves a callback
|
||||
&zxdg_output_manager_v1_interface, version > 3 ? 3 : version);
|
||||
else if (!strcmp(interface, xdg_activation_v1_interface.name))
|
||||
wlWm.xdgActivation = wl_registry_bind(wlWm.registry, name,
|
||||
&xdg_activation_v1_interface, 1);
|
||||
}
|
||||
|
||||
static void registryGlobalRemoveHandler(void * data,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -20,8 +20,10 @@
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include <libdecor.h>
|
||||
#include <wayland-client.h>
|
||||
@@ -49,27 +51,32 @@ static void libdecorHandleError(struct libdecor * context, enum libdecor_error e
|
||||
static void libdecorFrameConfigure(struct libdecor_frame * frame,
|
||||
struct libdecor_configuration * configuration, void * opaque)
|
||||
{
|
||||
if (!wlWm.configured)
|
||||
{
|
||||
xdg_surface_ack_configure(libdecor_frame_get_xdg_surface(frame), configuration->serial);
|
||||
wlWm.configured = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int width, height;
|
||||
if (libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
|
||||
if (libdecor_configuration_get_content_size(configuration, frame, &width, &height))
|
||||
{
|
||||
wlWm.width = width;
|
||||
wlWm.height = height;
|
||||
|
||||
struct libdecor_state * state = libdecor_state_new(wlWm.width, wlWm.height);
|
||||
libdecor_frame_commit(wlWm.libdecorFrame, state, NULL);
|
||||
libdecor_state_free(state);
|
||||
}
|
||||
|
||||
enum libdecor_window_state windowState;
|
||||
if (libdecor_configuration_get_window_state(configuration, &windowState))
|
||||
wlWm.fullscreen = windowState & LIBDECOR_WINDOW_STATE_FULLSCREEN;
|
||||
|
||||
struct libdecor_state * state = libdecor_state_new(wlWm.width, wlWm.height);
|
||||
libdecor_frame_commit(frame, state, wlWm.configured ? NULL : configuration);
|
||||
libdecor_state_free(state);
|
||||
|
||||
if (wlWm.configured)
|
||||
{
|
||||
wlWm.needsResize = true;
|
||||
wlWm.resizeSerial = configuration->serial;
|
||||
}
|
||||
else
|
||||
wlWm.configured = true;
|
||||
wlWm.needsResize = true;
|
||||
wlWm.resizeSerial = configuration->serial;
|
||||
app_invalidateWindow(true);
|
||||
waylandStopWaitFrame();
|
||||
}
|
||||
|
||||
static void libdecorFrameClose(struct libdecor_frame * frame, void * opaque)
|
||||
@@ -94,7 +101,12 @@ static struct libdecor_frame_interface libdecorFrameListener = {
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless)
|
||||
static void libdecorCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
libdecor_dispatch(wlWm.libdecor, 0);
|
||||
}
|
||||
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable)
|
||||
{
|
||||
wlWm.libdecor = libdecor_new(wlWm.display, &libdecorListener);
|
||||
wlWm.libdecorFrame = libdecor_decorate(wlWm.libdecor, wlWm.surface, &libdecorFrameListener, NULL);
|
||||
@@ -103,9 +115,19 @@ bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool b
|
||||
libdecor_frame_set_title(wlWm.libdecorFrame, title);
|
||||
libdecor_frame_map(wlWm.libdecorFrame);
|
||||
|
||||
while (!wlWm.configured)
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
if (resizable)
|
||||
libdecor_frame_set_capabilities(wlWm.libdecorFrame, LIBDECOR_ACTION_RESIZE);
|
||||
else
|
||||
libdecor_frame_unset_capabilities(wlWm.libdecorFrame, LIBDECOR_ACTION_RESIZE);
|
||||
|
||||
while (!wlWm.configured)
|
||||
libdecor_dispatch(wlWm.libdecor, 0);
|
||||
|
||||
if (!waylandPollRegister(libdecor_get_fd(wlWm.libdecor), libdecorCallback, NULL, EPOLLIN))
|
||||
{
|
||||
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -124,6 +146,8 @@ void waylandSetFullscreen(bool fs)
|
||||
libdecor_frame_set_fullscreen(wlWm.libdecorFrame, NULL);
|
||||
else
|
||||
libdecor_frame_unset_fullscreen(wlWm.libdecorFrame);
|
||||
|
||||
libdecor_frame_set_visibility(wlWm.libdecorFrame, !fs);
|
||||
}
|
||||
|
||||
bool waylandGetFullscreen(void)
|
||||
@@ -135,3 +159,20 @@ void waylandMinimize(void)
|
||||
{
|
||||
libdecor_frame_set_minimized(wlWm.libdecorFrame);
|
||||
}
|
||||
|
||||
void waylandShellResize(int w, int h)
|
||||
{
|
||||
if (!libdecor_frame_is_floating(wlWm.libdecorFrame))
|
||||
return;
|
||||
|
||||
wlWm.width = w;
|
||||
wlWm.height = h;
|
||||
|
||||
struct libdecor_state * state = libdecor_state_new(w, h);
|
||||
libdecor_frame_commit(wlWm.libdecorFrame, state, NULL);
|
||||
libdecor_state_free(state);
|
||||
|
||||
wlWm.needsResize = true;
|
||||
app_invalidateWindow(true);
|
||||
waylandStopWaitFrame();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -46,6 +46,8 @@ static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface,
|
||||
{
|
||||
wlWm.needsResize = true;
|
||||
wlWm.resizeSerial = serial;
|
||||
app_invalidateWindow(true);
|
||||
waylandStopWaitFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -63,15 +65,30 @@ static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel,
|
||||
int32_t width, int32_t height, struct wl_array * states)
|
||||
{
|
||||
wlWm.width = width;
|
||||
wlWm.height = height;
|
||||
wlWm.width = width;
|
||||
wlWm.height = height;
|
||||
wlWm.fullscreen = false;
|
||||
wlWm.floating = true;
|
||||
|
||||
enum xdg_toplevel_state * state;
|
||||
wl_array_for_each(state, states)
|
||||
{
|
||||
if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN)
|
||||
switch (*state)
|
||||
{
|
||||
case XDG_TOPLEVEL_STATE_FULLSCREEN:
|
||||
wlWm.fullscreen = true;
|
||||
// fallthrough
|
||||
case XDG_TOPLEVEL_STATE_MAXIMIZED:
|
||||
case XDG_TOPLEVEL_STATE_TILED_LEFT:
|
||||
case XDG_TOPLEVEL_STATE_TILED_RIGHT:
|
||||
case XDG_TOPLEVEL_STATE_TILED_TOP:
|
||||
case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
|
||||
wlWm.floating = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +102,7 @@ static const struct xdg_toplevel_listener xdgToplevelListener = {
|
||||
.close = xdgToplevelClose,
|
||||
};
|
||||
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless)
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable)
|
||||
{
|
||||
if (!wlWm.xdgWmBase)
|
||||
{
|
||||
@@ -151,3 +168,17 @@ void waylandMinimize(void)
|
||||
{
|
||||
xdg_toplevel_set_minimized(wlWm.xdgToplevel);
|
||||
}
|
||||
|
||||
void waylandShellResize(int w, int h)
|
||||
{
|
||||
if (!wlWm.floating)
|
||||
return;
|
||||
|
||||
wlWm.width = w;
|
||||
wlWm.height = h;
|
||||
xdg_surface_set_window_geometry(wlWm.xdgSurface, 0, 0, w, h);
|
||||
|
||||
wlWm.needsResize = true;
|
||||
app_invalidateWindow(true);
|
||||
waylandStopWaitFrame();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -37,6 +37,13 @@ static struct Option waylandOptions[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "wayland",
|
||||
.name = "fractionScale",
|
||||
.description = "Enable fractional scale",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -66,7 +73,8 @@ static bool waylandInit(const LG_DSInitParams params)
|
||||
memset(&wlWm, 0, sizeof(wlWm));
|
||||
wl_list_init(&wlWm.surfaceOutputs);
|
||||
|
||||
wlWm.warpSupport = option_get_bool("wayland", "warpSupport");
|
||||
wlWm.warpSupport = option_get_bool("wayland", "warpSupport");
|
||||
wlWm.useFractionalScale = option_get_bool("wayland", "fractionScale");
|
||||
|
||||
wlWm.display = wl_display_connect(NULL);
|
||||
wlWm.width = params.w;
|
||||
@@ -81,21 +89,27 @@ static bool waylandInit(const LG_DSInitParams params)
|
||||
if (!waylandRegistryInit())
|
||||
return false;
|
||||
|
||||
if (!waylandActivationInit())
|
||||
return false;
|
||||
|
||||
if (!waylandIdleInit())
|
||||
return false;
|
||||
|
||||
if (!waylandPresentationInit())
|
||||
return false;
|
||||
|
||||
if (!waylandCursorInit())
|
||||
return false;
|
||||
|
||||
if (!waylandInputInit())
|
||||
return false;
|
||||
|
||||
if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless))
|
||||
if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless, params.resizable))
|
||||
return false;
|
||||
|
||||
if (!waylandEGLInit(params.w, params.h))
|
||||
return false;
|
||||
|
||||
if (!waylandCursorInit())
|
||||
return false;
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (params.opengl && !waylandOpenGLInit())
|
||||
return false;
|
||||
@@ -119,6 +133,7 @@ static void waylandFree(void)
|
||||
{
|
||||
waylandIdleFree();
|
||||
waylandWindowFree();
|
||||
waylandPresentationFree();
|
||||
waylandInputFree();
|
||||
waylandOutputFree();
|
||||
waylandRegistryFree();
|
||||
@@ -139,6 +154,7 @@ static bool waylandGetProp(LG_DSProperty prop, void * ret)
|
||||
|
||||
struct LG_DisplayServerOps LGDS_Wayland =
|
||||
{
|
||||
.name = "Wayland",
|
||||
.setup = waylandSetup,
|
||||
.probe = waylandProbe,
|
||||
.earlyInit = waylandEarlyInit,
|
||||
@@ -161,8 +177,11 @@ struct LG_DisplayServerOps LGDS_Wayland =
|
||||
.glSetSwapInterval = waylandGLSetSwapInterval,
|
||||
.glSwapBuffers = waylandGLSwapBuffers,
|
||||
#endif
|
||||
.waitFrame = waylandWaitFrame,
|
||||
.skipFrame = waylandSkipFrame,
|
||||
.stopWaitFrame = waylandStopWaitFrame,
|
||||
.guestPointerUpdated = waylandGuestPointerUpdated,
|
||||
.showPointer = waylandShowPointer,
|
||||
.setPointer = waylandSetPointer,
|
||||
.grabPointer = waylandGrabPointer,
|
||||
.ungrabPointer = waylandUngrabPointer,
|
||||
.capturePointer = waylandCapturePointer,
|
||||
@@ -172,6 +191,7 @@ struct LG_DisplayServerOps LGDS_Wayland =
|
||||
.warpPointer = waylandWarpPointer,
|
||||
.realignPointer = waylandRealignPointer,
|
||||
.isValidPointerPos = waylandIsValidPointerPos,
|
||||
.requestActivation = waylandActivationRequestActivation,
|
||||
.inhibitIdle = waylandInhibitIdle,
|
||||
.uninhibitIdle = waylandUninhibitIdle,
|
||||
.wait = waylandWait,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -22,24 +22,32 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-cursor.h>
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
# include <wayland-egl.h>
|
||||
# include <EGL/egl.h>
|
||||
# include <EGL/eglext.h>
|
||||
# include "eglutil.h"
|
||||
#endif
|
||||
|
||||
#include "app.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/countedbuffer.h"
|
||||
#include "common/ringbuffer.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-presentation-time-client-protocol.h"
|
||||
#include "wayland-viewporter-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-xdg-output-unstable-v1-client-protocol.h"
|
||||
#include "wayland-xdg-activation-v1-client-protocol.h"
|
||||
|
||||
typedef void (*WaylandPollCallback)(uint32_t events, void * opaque);
|
||||
|
||||
@@ -55,8 +63,15 @@ struct WaylandPoll
|
||||
struct WaylandOutput
|
||||
{
|
||||
uint32_t name;
|
||||
int32_t scale;
|
||||
wl_fixed_t scale;
|
||||
int32_t scaleInt;
|
||||
int32_t logicalWidth;
|
||||
int32_t logicalHeight;
|
||||
int32_t modeWidth;
|
||||
int32_t modeHeight;
|
||||
bool modeRotate;
|
||||
struct wl_output * output;
|
||||
struct zxdg_output_v1 * xdgOutput;
|
||||
uint32_t version;
|
||||
struct wl_list link;
|
||||
};
|
||||
@@ -74,6 +89,10 @@ enum EGLSwapWithDamageState {
|
||||
SWAP_WITH_DAMAGE_EXT,
|
||||
};
|
||||
|
||||
struct xkb_context;
|
||||
struct xkb_keymap;
|
||||
struct xkb_state;
|
||||
|
||||
struct WaylandDSState
|
||||
{
|
||||
bool pointerGrabbed;
|
||||
@@ -88,9 +107,12 @@ struct WaylandDSState
|
||||
struct wl_shm * shm;
|
||||
struct wl_compositor * compositor;
|
||||
|
||||
int32_t width, height, scale;
|
||||
int32_t width, height;
|
||||
wl_fixed_t scale;
|
||||
bool fractionalScale;
|
||||
bool needsResize;
|
||||
bool fullscreen;
|
||||
bool floating;
|
||||
uint32_t resizeSerial;
|
||||
bool configured;
|
||||
bool warpSupport;
|
||||
@@ -98,10 +120,7 @@ struct WaylandDSState
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
struct wl_egl_window * eglWindow;
|
||||
bool eglSwapWithDamageInit;
|
||||
eglSwapBuffersWithDamageKHR_t eglSwapWithDamage;
|
||||
EGLint * eglDamageRects;
|
||||
int eglDamageRectCount;
|
||||
struct SwapWithDamageData swapWithDamage;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
@@ -110,6 +129,11 @@ struct WaylandDSState
|
||||
EGLSurface glSurface;
|
||||
#endif
|
||||
|
||||
struct wp_presentation * presentation;
|
||||
clockid_t clkId;
|
||||
RingBuffer photonTimings;
|
||||
GraphHandle photonGraph;
|
||||
|
||||
#ifdef ENABLE_LIBDECOR
|
||||
struct libdecor * libdecor;
|
||||
struct libdecor_frame * libdecorFrame;
|
||||
@@ -121,8 +145,17 @@ struct WaylandDSState
|
||||
struct zxdg_toplevel_decoration_v1 * xdgToplevelDecoration;
|
||||
#endif
|
||||
|
||||
struct wl_surface * cursor;
|
||||
struct wl_buffer * cursorBuffer;
|
||||
const char * cursorThemeName;
|
||||
int cursorSize;
|
||||
int cursorScale;
|
||||
struct wl_cursor_theme * cursorTheme;
|
||||
struct wl_buffer * cursorSquareBuffer;
|
||||
struct wl_surface * cursors[LG_POINTER_COUNT];
|
||||
struct Point cursorHot[LG_POINTER_COUNT];
|
||||
LG_DSPointer cursorId;
|
||||
struct wl_surface * cursor;
|
||||
int cursorHotX;
|
||||
int cursorHotY;
|
||||
|
||||
struct wl_data_device_manager * dataDeviceManager;
|
||||
|
||||
@@ -132,6 +165,9 @@ struct WaylandDSState
|
||||
struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager;
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor;
|
||||
uint32_t keyboardEnterSerial;
|
||||
struct xkb_context * xkb;
|
||||
struct xkb_state * xkbState;
|
||||
struct xkb_keymap * keymap;
|
||||
|
||||
struct wl_pointer * pointer;
|
||||
struct zwp_relative_pointer_manager_v1 * relativePointerManager;
|
||||
@@ -146,8 +182,16 @@ struct WaylandDSState
|
||||
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
|
||||
struct zwp_idle_inhibitor_v1 * idleInhibitor;
|
||||
|
||||
struct xdg_activation_v1 * xdgActivation;
|
||||
|
||||
struct wp_viewporter * viewporter;
|
||||
struct wp_viewport * viewport;
|
||||
struct zxdg_output_manager_v1 * xdgOutputManager;
|
||||
struct wl_list outputs; // WaylandOutput::link
|
||||
struct wl_list surfaceOutputs; // SurfaceOutput::link
|
||||
bool useFractionalScale;
|
||||
|
||||
LGEvent * frameEvent;
|
||||
|
||||
struct wl_list poll; // WaylandPoll::link
|
||||
struct wl_list pollFree; // WaylandPoll::link
|
||||
@@ -191,6 +235,11 @@ struct WCBState
|
||||
extern struct WaylandDSState wlWm;
|
||||
extern struct WCBState wlCb;
|
||||
|
||||
// activation module
|
||||
bool waylandActivationInit(void);
|
||||
void waylandActivationFree(void);
|
||||
void waylandActivationRequestActivation(void);
|
||||
|
||||
// clipboard module
|
||||
bool waylandCBInit(void);
|
||||
void waylandCBRequest(LG_ClipboardData type);
|
||||
@@ -201,7 +250,8 @@ void waylandCBInvalidate(void);
|
||||
// cursor module
|
||||
bool waylandCursorInit(void);
|
||||
void waylandCursorFree(void);
|
||||
void waylandShowPointer(bool show);
|
||||
void waylandSetPointer(LG_DSPointer pointer);
|
||||
void waylandCursorScaleChange(void);
|
||||
|
||||
// gl module
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
@@ -247,7 +297,7 @@ bool waylandOutputInit(void);
|
||||
void waylandOutputFree(void);
|
||||
void waylandOutputBind(uint32_t name, uint32_t version);
|
||||
void waylandOutputTryUnbind(uint32_t name);
|
||||
int32_t waylandOutputGetScale(struct wl_output * output);
|
||||
wl_fixed_t waylandOutputGetScale(struct wl_output * output);
|
||||
|
||||
// poll module
|
||||
bool waylandPollInit(void);
|
||||
@@ -255,20 +305,29 @@ void waylandWait(unsigned int time);
|
||||
bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events);
|
||||
bool waylandPollUnregister(int fd);
|
||||
|
||||
// presentation module
|
||||
bool waylandPresentationInit(void);
|
||||
void waylandPresentationFrame(void);
|
||||
void waylandPresentationFree(void);
|
||||
|
||||
// registry module
|
||||
bool waylandRegistryInit(void);
|
||||
void waylandRegistryFree(void);
|
||||
|
||||
// shell module
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless);
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable);
|
||||
void waylandShellAckConfigureIfNeeded(void);
|
||||
void waylandSetFullscreen(bool fs);
|
||||
bool waylandGetFullscreen(void);
|
||||
void waylandMinimize(void);
|
||||
void waylandShellResize(int w, int h);
|
||||
|
||||
// window module
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless);
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable);
|
||||
void waylandWindowFree(void);
|
||||
void waylandWindowUpdateScale(void);
|
||||
void waylandSetWindowSize(int x, int y);
|
||||
bool waylandIsValidPointerPos(int x, int y);
|
||||
bool waylandWaitFrame(void);
|
||||
void waylandSkipFrame(void);
|
||||
void waylandStopWaitFrame(void);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -27,17 +27,18 @@
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/event.h"
|
||||
|
||||
// Surface-handling listeners.
|
||||
|
||||
void waylandWindowUpdateScale(void)
|
||||
{
|
||||
int32_t maxScale = 0;
|
||||
wl_fixed_t maxScale = 0;
|
||||
struct SurfaceOutput * node;
|
||||
|
||||
wl_list_for_each(node, &wlWm.surfaceOutputs, link)
|
||||
{
|
||||
int32_t scale = waylandOutputGetScale(node->output);
|
||||
wl_fixed_t scale = waylandOutputGetScale(node->output);
|
||||
if (scale > maxScale)
|
||||
maxScale = scale;
|
||||
}
|
||||
@@ -45,13 +46,23 @@ void waylandWindowUpdateScale(void)
|
||||
if (maxScale)
|
||||
{
|
||||
wlWm.scale = maxScale;
|
||||
wlWm.fractionalScale = wl_fixed_from_int(wl_fixed_to_int(maxScale)) != maxScale;
|
||||
wlWm.needsResize = true;
|
||||
waylandCursorScaleChange();
|
||||
app_invalidateWindow(true);
|
||||
waylandStopWaitFrame();
|
||||
}
|
||||
}
|
||||
|
||||
static void wlSurfaceEnterHandler(void * data, struct wl_surface * surface, struct wl_output * output)
|
||||
{
|
||||
struct SurfaceOutput * node = malloc(sizeof(struct SurfaceOutput));
|
||||
struct SurfaceOutput * node = malloc(sizeof(*node));
|
||||
if (!node)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
node->output = output;
|
||||
wl_list_insert(&wlWm.surfaceOutputs, &node->link);
|
||||
waylandWindowUpdateScale();
|
||||
@@ -74,9 +85,17 @@ static const struct wl_surface_listener wlSurfaceListener = {
|
||||
.leave = wlSurfaceLeaveHandler,
|
||||
};
|
||||
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless)
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable)
|
||||
{
|
||||
wlWm.scale = 1;
|
||||
wlWm.scale = wl_fixed_from_int(1);
|
||||
|
||||
wlWm.frameEvent = lgCreateEvent(true, 0);
|
||||
if (!wlWm.frameEvent)
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize event for waitFrame");
|
||||
return false;
|
||||
}
|
||||
lgSignalEvent(wlWm.frameEvent);
|
||||
|
||||
if (!wlWm.compositor)
|
||||
{
|
||||
@@ -93,7 +112,7 @@ bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool
|
||||
|
||||
wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL);
|
||||
|
||||
if (!waylandShellInit(title, fullscreen, maximize, borderless))
|
||||
if (!waylandShellInit(title, fullscreen, maximize, borderless, resizable))
|
||||
return false;
|
||||
|
||||
wl_surface_commit(wlWm.surface);
|
||||
@@ -103,14 +122,47 @@ bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool
|
||||
void waylandWindowFree(void)
|
||||
{
|
||||
wl_surface_destroy(wlWm.surface);
|
||||
lgFreeEvent(wlWm.frameEvent);
|
||||
}
|
||||
|
||||
void waylandSetWindowSize(int x, int y)
|
||||
{
|
||||
// FIXME: implement.
|
||||
waylandShellResize(x, y);
|
||||
}
|
||||
|
||||
bool waylandIsValidPointerPos(int x, int y)
|
||||
{
|
||||
return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height;
|
||||
}
|
||||
|
||||
static void frameHandler(void * opaque, struct wl_callback * callback, unsigned int data)
|
||||
{
|
||||
lgSignalEvent(wlWm.frameEvent);
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
static const struct wl_callback_listener frame_listener = {
|
||||
.done = frameHandler,
|
||||
};
|
||||
|
||||
bool waylandWaitFrame(void)
|
||||
{
|
||||
lgWaitEvent(wlWm.frameEvent, TIMEOUT_INFINITE);
|
||||
|
||||
struct wl_callback * callback = wl_surface_frame(wlWm.surface);
|
||||
if (callback)
|
||||
wl_callback_add_listener(callback, &frame_listener, NULL);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void waylandSkipFrame(void)
|
||||
{
|
||||
// If we decided to not render, we must commit the surface so that the callback is registered.
|
||||
wl_surface_commit(wlWm.surface);
|
||||
}
|
||||
|
||||
void waylandStopWaitFrame(void)
|
||||
{
|
||||
lgSignalEvent(wlWm.frameEvent);
|
||||
}
|
||||
|
||||
@@ -2,29 +2,31 @@ cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_X11 LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DISPLAYSERVER_X11_PKGCONFIG REQUIRED
|
||||
x11
|
||||
xi
|
||||
xfixes
|
||||
xscrnsaver
|
||||
xinerama
|
||||
pkg_check_modules(DISPLAYSERVER_X11 REQUIRED IMPORTED_TARGET
|
||||
x11
|
||||
xi
|
||||
xfixes
|
||||
xscrnsaver
|
||||
xinerama
|
||||
xcursor
|
||||
xpresent
|
||||
xkbcommon
|
||||
)
|
||||
|
||||
add_library(displayserver_X11 STATIC
|
||||
x11.c
|
||||
atoms.c
|
||||
clipboard.c
|
||||
x11.c
|
||||
atoms.c
|
||||
clipboard.c
|
||||
)
|
||||
|
||||
add_definitions(-D GLX_GLXEXT_PROTOTYPES)
|
||||
|
||||
target_link_libraries(displayserver_X11
|
||||
${DISPLAYSERVER_X11_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
PkgConfig::DISPLAYSERVER_X11
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_X11
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_X11_PKGCONFIG_INCLUDE_DIRS}
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -22,16 +22,23 @@
|
||||
#define _H_X11DS_ATOMS_
|
||||
|
||||
#define DEF_ATOMS() \
|
||||
DEF_ATOM(_NET_SUPPORTING_WM_CHECK, True) \
|
||||
DEF_ATOM(_NET_SUPPORTED, True) \
|
||||
DEF_ATOM(_NET_WM_NAME, True) \
|
||||
DEF_ATOM(_NET_REQUEST_FRAME_EXTENTS, True) \
|
||||
DEF_ATOM(_NET_FRAME_EXTENTS, True) \
|
||||
DEF_ATOM(_NET_WM_BYPASS_COMPOSITOR, False) \
|
||||
DEF_ATOM(_NET_WM_ICON, True) \
|
||||
DEF_ATOM(_NET_WM_STATE, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_FULLSCREEN, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_FOCUSED, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_MAXIMIZED_VERT, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_DEMANDS_ATTENTION, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE_NORMAL, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE_UTILITY, True) \
|
||||
DEF_ATOM(_NET_WM_PID, True) \
|
||||
DEF_ATOM(WM_DELETE_WINDOW, True) \
|
||||
DEF_ATOM(_MOTIF_WM_HINTS, True) \
|
||||
\
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -103,7 +103,7 @@ bool x11CBEventThread(const XEvent xe)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool x11CBInit()
|
||||
bool x11CBInit(void)
|
||||
{
|
||||
x11cb.aCurSelection = BadValue;
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
@@ -123,8 +123,6 @@ bool x11CBInit()
|
||||
return false;
|
||||
}
|
||||
|
||||
XFixesSelectSelectionInput(x11.display, x11.window,
|
||||
XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask);
|
||||
XFixesSelectSelectionInput(x11.display, x11.window,
|
||||
x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
|
||||
|
||||
@@ -153,7 +151,13 @@ static void x11CBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
|
||||
static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
||||
{
|
||||
XEvent * s = (XEvent *)malloc(sizeof(XEvent));
|
||||
XEvent * s = malloc(sizeof(*s));
|
||||
if (!s)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
s->xselection.type = SelectionNotify;
|
||||
s->xselection.requestor = e.requestor;
|
||||
s->xselection.selection = e.selection;
|
||||
@@ -205,7 +209,7 @@ send:
|
||||
|
||||
static void x11CBSelectionClear(const XSelectionClearEvent e)
|
||||
{
|
||||
if (e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD)
|
||||
if (e.selection != x11atoms.CLIPBOARD)
|
||||
return;
|
||||
|
||||
x11cb.aCurSelection = BadValue;
|
||||
@@ -291,7 +295,7 @@ out:
|
||||
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
||||
{
|
||||
// check if the selection is valid and it isn't ourself
|
||||
if ((e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD) ||
|
||||
if (e.selection != x11atoms.CLIPBOARD ||
|
||||
e.owner == x11.window || e.owner == 0)
|
||||
{
|
||||
return;
|
||||
@@ -396,7 +400,6 @@ void x11CBNotice(LG_ClipboardData type)
|
||||
{
|
||||
x11cb.haveRequest = true;
|
||||
x11cb.type = type;
|
||||
XSetSelectionOwner(x11.display, XA_PRIMARY , x11.window, CurrentTime);
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, x11.window, CurrentTime);
|
||||
XFlush(x11.display);
|
||||
}
|
||||
@@ -404,7 +407,6 @@ void x11CBNotice(LG_ClipboardData type)
|
||||
void x11CBRelease(void)
|
||||
{
|
||||
x11cb.haveRequest = false;
|
||||
XSetSelectionOwner(x11.display, XA_PRIMARY , None, CurrentTime);
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, None, CurrentTime);
|
||||
XFlush(x11.display);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
bool x11CBEventThread(const XEvent xe);
|
||||
|
||||
bool x11CBInit();
|
||||
bool x11CBInit(void);
|
||||
void x11CBNotice(LG_ClipboardData type);
|
||||
void x11CBRelease(void);
|
||||
void x11CBRequest(LG_ClipboardData type);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -21,22 +21,62 @@
|
||||
#ifndef _H_X11DS_X11_
|
||||
#define _H_X11DS_X11_
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
#include <GL/glx.h>
|
||||
|
||||
#include "interface/displayserver.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/types.h"
|
||||
|
||||
enum Modifiers
|
||||
{
|
||||
MOD_CTRL_LEFT = 0,
|
||||
MOD_CTRL_RIGHT,
|
||||
MOD_SHIFT_LEFT,
|
||||
MOD_SHIFT_RIGHT,
|
||||
MOD_ALT_LEFT,
|
||||
MOD_ALT_RIGHT,
|
||||
MOD_SUPER_LEFT,
|
||||
MOD_SUPER_RIGHT,
|
||||
};
|
||||
|
||||
#define MOD_COUNT (MOD_SUPER_RIGHT + 1)
|
||||
|
||||
struct X11DSState
|
||||
{
|
||||
Display * display;
|
||||
Window window;
|
||||
XVisualInfo * visual;
|
||||
int xinputOp;
|
||||
|
||||
int minKeycode, maxKeycode;
|
||||
int symsPerKeycode;
|
||||
KeySym * keysyms;
|
||||
|
||||
//Extended Window Manager Hints
|
||||
//ref: https://specifications.freedesktop.org/wm-spec/latest/
|
||||
bool ewmhSupport;
|
||||
bool ewmhHasFocusEvent;
|
||||
|
||||
_Atomic(uint64_t) lastWMEvent;
|
||||
bool invalidateAll;
|
||||
|
||||
int xpresentOp;
|
||||
bool jitRender;
|
||||
_Atomic(uint64_t) presentMsc, presentUst;
|
||||
uint32_t presentSerial;
|
||||
Pixmap presentPixmap;
|
||||
XserverRegion presentRegion;
|
||||
LGEvent * frameEvent;
|
||||
|
||||
LGThread * eventThread;
|
||||
|
||||
int xinputOp;
|
||||
int pointerDev;
|
||||
int keyboardDev;
|
||||
int xValuator;
|
||||
@@ -51,8 +91,11 @@ struct X11DSState
|
||||
struct Rect rect;
|
||||
struct Border border;
|
||||
|
||||
Cursor blankCursor;
|
||||
Cursor squareCursor;
|
||||
Cursor cursors[LG_POINTER_COUNT];
|
||||
|
||||
XIM xim;
|
||||
XIC xic;
|
||||
bool modifiers[MOD_COUNT];
|
||||
|
||||
// XFixes vars
|
||||
int eventBase;
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(fonts LANGUAGES C)
|
||||
|
||||
set(FONT_H "${CMAKE_BINARY_DIR}/include/dynamic/fonts.h")
|
||||
set(FONT_C "${CMAKE_BINARY_DIR}/src/fonts.c")
|
||||
|
||||
file(WRITE ${FONT_H} "#include \"interface/font.h\"\n\n")
|
||||
file(APPEND ${FONT_H} "extern LG_Font * LG_Fonts[];\n\n")
|
||||
|
||||
file(WRITE ${FONT_C} "#include \"interface/font.h\"\n\n")
|
||||
file(APPEND ${FONT_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(FONTS "_")
|
||||
set(FONTS_LINK "_")
|
||||
function(add_font name)
|
||||
set(FONTS "${FONTS};${name}" PARENT_SCOPE)
|
||||
set(FONTS_LINK "${FONTS_LINK};font_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove fonts here!
|
||||
add_font(freetype)
|
||||
|
||||
list(REMOVE_AT FONTS 0)
|
||||
list(REMOVE_AT FONTS_LINK 0)
|
||||
|
||||
list(LENGTH FONTS FONT_COUNT)
|
||||
file(APPEND ${FONT_H} "#define LG_FONT_COUNT ${FONT_COUNT}\n")
|
||||
|
||||
foreach(font ${FONTS})
|
||||
file(APPEND ${FONT_C} "extern LG_Font LGF_${font};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${FONT_C} "\nconst LG_Font * LG_Fonts[] =\n{\n")
|
||||
foreach(font ${FONTS})
|
||||
file(APPEND ${FONT_C} " &LGF_${font},\n")
|
||||
endforeach()
|
||||
file(APPEND ${FONT_C} " NULL\n};\n\n")
|
||||
|
||||
add_library(fonts STATIC ${FONT_C})
|
||||
target_link_libraries(fonts ${FONTS_LINK})
|
||||
@@ -1,26 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(font_freetype LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(FONT_FREETYPE_PKGCONFIG REQUIRED
|
||||
freetype2
|
||||
fontconfig
|
||||
)
|
||||
|
||||
add_library(font_freetype STATIC
|
||||
src/freetype.c
|
||||
)
|
||||
|
||||
target_link_libraries(font_freetype
|
||||
${FONT_FREETYPE_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(font_freetype
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${FONT_FREETYPE_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,325 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "interface/font.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
static int g_initCount = 0;
|
||||
static FcConfig * g_fontConfig = NULL;
|
||||
static FT_Library g_ft;
|
||||
|
||||
struct Inst
|
||||
{
|
||||
FT_Face face;
|
||||
unsigned int height;
|
||||
};
|
||||
|
||||
static bool lgf_freetype_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
if (FT_Init_FreeType(&g_ft))
|
||||
{
|
||||
DEBUG_ERROR("FT_Init_FreeType Failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_fontConfig = FcInitLoadConfigAndFonts();
|
||||
if (!g_fontConfig)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
goto fail_init;
|
||||
}
|
||||
}
|
||||
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
goto fail_config;
|
||||
}
|
||||
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
if (!font_name)
|
||||
font_name = "FreeMono";
|
||||
|
||||
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
|
||||
if (!pat)
|
||||
{
|
||||
DEBUG_ERROR("FCNameParse failed");
|
||||
goto fail_opaque;
|
||||
}
|
||||
|
||||
FcConfigSubstitute(g_fontConfig, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * match = FcFontMatch(g_fontConfig, pat, &result);
|
||||
|
||||
if (!match)
|
||||
{
|
||||
DEBUG_ERROR("FcFontMatch Failed");
|
||||
goto fail_parse;
|
||||
}
|
||||
|
||||
if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch)
|
||||
{
|
||||
if (FT_New_Face(g_ft, (char *) file, 0, &this->face))
|
||||
{
|
||||
DEBUG_ERROR("FT_New_Face Failed");
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
if (FT_Select_Charmap(this->face, ft_encoding_unicode))
|
||||
{
|
||||
DEBUG_ERROR("FT_Select_Charmap failed");
|
||||
FT_Done_Face(this->face);
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
FT_Set_Pixel_Sizes(this->face, 0, size);
|
||||
this->height = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
++g_initCount;
|
||||
ret = true;
|
||||
|
||||
fail_match:
|
||||
FcPatternDestroy(match);
|
||||
|
||||
fail_parse:
|
||||
FcPatternDestroy(pat);
|
||||
|
||||
if (ret)
|
||||
return true;
|
||||
|
||||
fail_opaque:
|
||||
free(this);
|
||||
*opaque = NULL;
|
||||
|
||||
fail_config:
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
FcConfigDestroy(g_fontConfig);
|
||||
g_fontConfig = NULL;
|
||||
}
|
||||
|
||||
fail_init:
|
||||
if (g_initCount == 0)
|
||||
FT_Done_FreeType(g_ft);
|
||||
|
||||
fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void lgf_freetype_destroy(LG_FontObj opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (this->face)
|
||||
FT_Done_Face(this->face);
|
||||
free(this);
|
||||
|
||||
if (--g_initCount == 0)
|
||||
{
|
||||
FcConfigDestroy(g_fontConfig);
|
||||
g_fontConfig = NULL;
|
||||
|
||||
FT_Done_FreeType(g_ft);
|
||||
}
|
||||
}
|
||||
|
||||
// A very simple UTF-8 decoder that assumes the input is valid.
|
||||
static unsigned int utf8_decode(const char * str)
|
||||
{
|
||||
const unsigned char * ptr = (const unsigned char *) str;
|
||||
// Handle the 4 byte case: 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx.
|
||||
if ((*ptr & 0xf8) == 0xf0)
|
||||
return (ptr[0] & 0x07) << 18 | (ptr[1] & 0x3f) << 12 | (ptr[2] & 0x3f) << 6 | (ptr[3] & 0x3f);
|
||||
// Handle the 3 byte case: 1110 xxxx 10xx xxxx 10xx xxxx.
|
||||
else if ((*ptr & 0xf0) == 0xe0)
|
||||
return (ptr[0] & 0x0f) << 12 | (ptr[1] & 0x3f) << 6 | (ptr[2] & 0x3f);
|
||||
// Handle the 2 byte case: 110x xxxx 10xx xxxx.
|
||||
else if ((*ptr & 0xe0) == 0xc0)
|
||||
return (ptr[0] & 0x1f) << 6 | (ptr[1] & 0x3f);
|
||||
// Everything else is the 1 byte case.
|
||||
else
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
// Return the length of the current UTF-8 character. Assumes the input is valid.
|
||||
static unsigned int utf8_advance(const char * str)
|
||||
{
|
||||
const unsigned char * ptr = (const unsigned char *) str;
|
||||
// 4 byte case starts with 1111 0xxx.
|
||||
if ((*ptr & 0xf8) == 0xf0)
|
||||
return 4;
|
||||
// 3 byte case starts with 1110 xxxx.
|
||||
else if ((*ptr & 0xf0) == 0xe0)
|
||||
return 3;
|
||||
// 2 byte case starts with 110x xxxx.
|
||||
else if ((*ptr & 0xe0) == 0xc0)
|
||||
return 2;
|
||||
// Everything else is the 1 byte case.
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static LG_FontBitmap * lgf_freetype_render(LG_FontObj opaque, unsigned int fg_color, const char * text)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
int width = 0;
|
||||
int row = 0;
|
||||
int rowWidth = 0;
|
||||
int topAscend = 0;
|
||||
int bottomDescend = 0;
|
||||
|
||||
for (const char * ptr = text; *ptr; ptr += utf8_advance(ptr))
|
||||
{
|
||||
unsigned int ch = utf8_decode(ptr);
|
||||
if (ch == '\n')
|
||||
{
|
||||
if (!ptr[1])
|
||||
break;
|
||||
if (rowWidth > width)
|
||||
width = rowWidth;
|
||||
rowWidth = bottomDescend = 0;
|
||||
++row;
|
||||
}
|
||||
else if (FT_Load_Char(this->face, ch, FT_LOAD_RENDER))
|
||||
{
|
||||
DEBUG_ERROR("Failed to load character: %c", *ptr);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
FT_GlyphSlot glyph = this->face->glyph;
|
||||
rowWidth += glyph->advance.x / 64;
|
||||
|
||||
int descend = glyph->bitmap.rows - glyph->bitmap_top;
|
||||
if (descend > bottomDescend)
|
||||
bottomDescend = descend;
|
||||
if (row == 0 && glyph->bitmap_top > topAscend)
|
||||
topAscend = glyph->bitmap_top;
|
||||
}
|
||||
}
|
||||
|
||||
if (rowWidth > width)
|
||||
width = rowWidth;
|
||||
|
||||
int height = topAscend + this->height * row + bottomDescend;
|
||||
uint32_t * pixels = calloc(width * height, sizeof(uint32_t));
|
||||
|
||||
if (!pixels)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory for font pixels");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int baseline = topAscend;
|
||||
int x = 0;
|
||||
|
||||
unsigned int r = (fg_color & 0xff000000) >> 24;
|
||||
unsigned int g = (fg_color & 0x00ff0000) >> 16;
|
||||
unsigned int b = (fg_color & 0x0000ff00) >> 8;
|
||||
uint32_t color = (r << 0) | (g << 8) | (b << 16);
|
||||
|
||||
for (const char * ptr = text; *ptr; ptr += utf8_advance(ptr))
|
||||
{
|
||||
unsigned int ch = utf8_decode(ptr);
|
||||
if (ch == '\n')
|
||||
{
|
||||
baseline += this->height;
|
||||
x = 0;
|
||||
}
|
||||
else if (FT_Load_Char(this->face, ch, FT_LOAD_RENDER))
|
||||
{
|
||||
DEBUG_ERROR("Failed to load character: U+%x", ch);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
FT_GlyphSlot glyph = this->face->glyph;
|
||||
int start = baseline - glyph->bitmap_top;
|
||||
int pitch = width;
|
||||
|
||||
if (glyph->bitmap.pitch < 0)
|
||||
{
|
||||
start += glyph->bitmap.rows - 1;
|
||||
pitch = -pitch;
|
||||
}
|
||||
|
||||
for (int i = 0; i < glyph->bitmap.rows; ++i)
|
||||
for (int j = 0; j < glyph->bitmap.width; ++j)
|
||||
pixels[(start + i) * pitch + x + j + glyph->bitmap_left] = color |
|
||||
(uint32_t)glyph->bitmap.buffer[i * glyph->bitmap.pitch + j] << 24;
|
||||
|
||||
x += glyph->advance.x / 64;
|
||||
}
|
||||
}
|
||||
|
||||
LG_FontBitmap * out = malloc(sizeof(LG_FontBitmap));
|
||||
if (!out)
|
||||
{
|
||||
free(pixels);
|
||||
DEBUG_ERROR("Failed to allocate memory for font bitmap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out->width = width;
|
||||
out->height = height;
|
||||
out->bpp = 4;
|
||||
out->pixels = (uint8_t *) pixels;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void lgf_freetype_release(LG_FontObj opaque, LG_FontBitmap * font)
|
||||
{
|
||||
LG_FontBitmap * bitmap = (LG_FontBitmap *)font;
|
||||
free(bitmap->pixels);
|
||||
free(bitmap);
|
||||
}
|
||||
|
||||
struct LG_Font LGF_freetype =
|
||||
{
|
||||
.name = "freetype",
|
||||
.create = lgf_freetype_create,
|
||||
.destroy = lgf_freetype_destroy,
|
||||
.render = lgf_freetype_render,
|
||||
.release = lgf_freetype_release
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -24,8 +24,10 @@
|
||||
#include <stdbool.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include "common/ringbuffer.h"
|
||||
#include "common/types.h"
|
||||
#include "interface/displayserver.h"
|
||||
#include "interface/overlay.h"
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
@@ -41,9 +43,11 @@ bool app_inputEnabled(void);
|
||||
bool app_isCaptureMode(void);
|
||||
bool app_isCaptureOnlyMode(void);
|
||||
bool app_isFormatValid(void);
|
||||
bool app_isOverlayMode(void);
|
||||
void app_updateCursorPos(double x, double y);
|
||||
void app_updateWindowPos(int x, int y);
|
||||
void app_handleResizeEvent(int w, int h, double scale, const struct Border border);
|
||||
void app_invalidateWindow(bool full);
|
||||
|
||||
void app_handleMouseRelative(double normx, double normy,
|
||||
double rawx, double rawy);
|
||||
@@ -53,8 +57,12 @@ void app_resyncMouseBasic(void);
|
||||
|
||||
void app_handleButtonPress(int button);
|
||||
void app_handleButtonRelease(int button);
|
||||
void app_handleKeyPress(int scancode);
|
||||
void app_handleKeyRelease(int scancode);
|
||||
void app_handleWheelMotion(double motion);
|
||||
void app_handleKeyboardTyped(const char * typed);
|
||||
void app_handleKeyPress(int scancode, int charcode);
|
||||
void app_handleKeyRelease(int scancode, int charcode);
|
||||
void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super);
|
||||
void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock);
|
||||
void app_handleEnterEvent(bool entered);
|
||||
void app_handleFocusEvent(bool focused);
|
||||
void app_handleCloseEvent(void);
|
||||
@@ -78,6 +86,44 @@ void app_glSetSwapInterval(int interval);
|
||||
void app_glSwapBuffers(void);
|
||||
#endif
|
||||
|
||||
#define MAX_OVERLAY_RECTS 10
|
||||
void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params);
|
||||
void app_initOverlays(void);
|
||||
void app_setOverlay(bool enable);
|
||||
bool app_overlayNeedsRender(void);
|
||||
/**
|
||||
* render the overlay
|
||||
* returns:
|
||||
* -1 for full output damage
|
||||
* 0 for no overlay
|
||||
* >0 number of rects written into rects
|
||||
*/
|
||||
int app_renderOverlay(struct Rect * rects, int maxRects);
|
||||
|
||||
void app_freeOverlays(void);
|
||||
|
||||
/**
|
||||
* invalidate the window to update the overlay, if renderTwice is set the imgui
|
||||
* render code will run twice so that auto sized windows are calculated correctly
|
||||
*/
|
||||
void app_invalidateOverlay(bool renderTwice);
|
||||
|
||||
struct OverlayGraph;
|
||||
typedef struct OverlayGraph * GraphHandle;
|
||||
typedef const char * (*GraphFormatFn)(const char * name,
|
||||
float min, float max, float avg, float freq, float last);
|
||||
|
||||
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
|
||||
float min, float max, GraphFormatFn formatFn);
|
||||
void app_unregisterGraph(GraphHandle handle);
|
||||
void app_invalidateGraph(GraphHandle handle);
|
||||
|
||||
void app_overlayConfigRegister(const char * title,
|
||||
void (*callback)(void * udata, int * id), void * udata);
|
||||
|
||||
void app_overlayConfigRegisterTab(const char * title,
|
||||
void (*callback)(void * udata, int * id), void * udata);
|
||||
|
||||
void app_clipboardRelease(void);
|
||||
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count);
|
||||
void app_clipboardNotifySize(const LG_ClipboardData type, size_t size);
|
||||
@@ -92,18 +138,31 @@ void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
typedef struct MsgBoxHandle * MsgBoxHandle;
|
||||
MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...);
|
||||
|
||||
typedef void (*MsgBoxConfirmCallback)(bool yes, void * opaque);
|
||||
MsgBoxHandle app_confirmMsgBox(const char * caption,
|
||||
MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...);
|
||||
|
||||
void app_msgBoxClose(MsgBoxHandle handle);
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*KeybindFn)(int sc, void * opaque);
|
||||
|
||||
void app_showRecord(bool show);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param sc The scancode to register
|
||||
* @param charcode The charcode to register (used instead of sc if non zero)
|
||||
* @param callback The function to be called when the combination is pressed
|
||||
* @param opaque A pointer to be passed to the callback, may be NULL
|
||||
* @retval A handle for the binding or NULL on failure.
|
||||
* The caller is required to release the handle via `app_releaseKeybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description);
|
||||
KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback,
|
||||
void * opaque, const char * description);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
@@ -116,14 +175,20 @@ void app_releaseKeybind(KeybindHandle * handle);
|
||||
*/
|
||||
void app_releaseAllKeybinds(void);
|
||||
|
||||
/**
|
||||
* Changes whether the help message is displayed or not.
|
||||
*/
|
||||
void app_showHelp(bool show);
|
||||
bool app_guestIsLinux(void);
|
||||
bool app_guestIsWindows(void);
|
||||
bool app_guestIsOSX(void);
|
||||
bool app_guestIsBSD(void);
|
||||
bool app_guestIsOther(void);
|
||||
|
||||
/**
|
||||
* Changes whether the FPS is displayed or not.
|
||||
* Enable/disable the LG display
|
||||
*/
|
||||
void app_showFPS(bool showFPS);
|
||||
void app_stopVideo(bool stop);
|
||||
|
||||
/**
|
||||
* Enable/disable the spice display
|
||||
*/
|
||||
bool app_useSpiceDisplay(bool enable);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -23,22 +23,23 @@
|
||||
#ifdef ENABLE_EGL
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef EGLDisplay (*eglGetPlatformDisplayEXT_t)(EGLenum platform,
|
||||
void *native_display, const EGLint *attrib_list);
|
||||
typedef void (*eglSwapBuffersWithDamageKHR_t)(EGLDisplay dpy,
|
||||
EGLSurface surface, const EGLint *rects, EGLint n_rects);
|
||||
typedef void (*glEGLImageTargetTexture2DOES_t)(GLenum target,
|
||||
GLeglImageOES image);
|
||||
#include <EGL/eglext.h>
|
||||
#undef GL_KHR_debug
|
||||
#include <GLES3/gl3.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
|
||||
struct EGLDynProcs
|
||||
{
|
||||
eglGetPlatformDisplayEXT_t eglGetPlatformDisplay;
|
||||
eglGetPlatformDisplayEXT_t eglGetPlatformDisplayEXT;
|
||||
eglSwapBuffersWithDamageKHR_t eglSwapBuffersWithDamageKHR;
|
||||
eglSwapBuffersWithDamageKHR_t eglSwapBuffersWithDamageEXT;
|
||||
glEGLImageTargetTexture2DOES_t glEGLImageTargetTexture2DOES;
|
||||
PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplay;
|
||||
PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayEXT;
|
||||
PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC eglSwapBuffersWithDamageKHR;
|
||||
PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC eglSwapBuffersWithDamageEXT;
|
||||
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
|
||||
PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallback;
|
||||
PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR;
|
||||
PFNGLBUFFERSTORAGEEXTPROC glBufferStorageEXT;
|
||||
PFNEGLCREATEIMAGEPROC eglCreateImage;
|
||||
PFNEGLDESTROYIMAGEPROC eglDestroyImage;
|
||||
};
|
||||
|
||||
extern struct EGLDynProcs g_egl_dynProcs;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -18,18 +18,24 @@
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef _H_LG_GLUTIL_
|
||||
#define _H_LG_GLUTIL_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "interface/font.h"
|
||||
#include "common/types.h"
|
||||
|
||||
typedef struct EGL_FPS EGL_FPS;
|
||||
struct SwapWithDamageData
|
||||
{
|
||||
bool init;
|
||||
PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC func;
|
||||
};
|
||||
|
||||
bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_fps_free(EGL_FPS ** fps);
|
||||
void swapWithDamageInit(struct SwapWithDamageData * data, EGLDisplay display);
|
||||
void swapWithDamageDisable(struct SwapWithDamageData * data);
|
||||
void swapWithDamage(struct SwapWithDamageData * data, EGLDisplay display, EGLSurface surface,
|
||||
const struct Rect * damage, int count);
|
||||
|
||||
void egl_fps_set_display(EGL_FPS * fps, bool display);
|
||||
void egl_fps_set_font (EGL_FPS * fps, LG_Font * fontObj);
|
||||
void egl_fps_update(EGL_FPS * fps, const float avgUPS, const float avgFPS);
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY);
|
||||
#endif
|
||||
50
client/include/gl_dynprocs.h
Normal file
50
client/include/gl_dynprocs.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_GL_DYNPROCS_
|
||||
#define _H_LG_GL_DYNPROCS_
|
||||
#ifdef ENABLE_OPENGL
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
struct GLDynProcs
|
||||
{
|
||||
PFNGLGENBUFFERSPROC glGenBuffers;
|
||||
PFNGLBINDBUFFERPROC glBindBuffer;
|
||||
PFNGLBUFFERDATAPROC glBufferData;
|
||||
PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
||||
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||
PFNGLISSYNCPROC glIsSync;
|
||||
PFNGLFENCESYNCPROC glFenceSync;
|
||||
PFNGLCLIENTWAITSYNCPROC glClientWaitSync;
|
||||
PFNGLDELETESYNCPROC glDeleteSync;
|
||||
PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
|
||||
};
|
||||
|
||||
extern struct GLDynProcs g_gl_dynProcs;
|
||||
|
||||
void gl_dynProcsInit(void);
|
||||
|
||||
#else
|
||||
#define gl_dynProcsInit(...)
|
||||
#endif
|
||||
|
||||
#endif // _H_LG_GL_DYNPROCS_
|
||||
89
client/include/interface/audiodev.h
Normal file
89
client/include/interface/audiodev.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_I_AUDIODEV_
|
||||
#define _H_I_AUDIODEV_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef int (*LG_AudioPullFn)(uint8_t * dst, int frames);
|
||||
typedef void (*LG_AudioPushFn)(uint8_t * src, int frames);
|
||||
|
||||
struct LG_AudioDevOps
|
||||
{
|
||||
/* internal name of the audio for debugging */
|
||||
const char * name;
|
||||
|
||||
/* called very early to allow for option registration, optional */
|
||||
void (*earlyInit)(void);
|
||||
|
||||
/* called to initialize the audio backend */
|
||||
bool (*init)(void);
|
||||
|
||||
/* final free */
|
||||
void (*free)(void);
|
||||
|
||||
struct
|
||||
{
|
||||
/* setup the stream for playback but don't start it yet
|
||||
* Note: the pull function returns f32 samples
|
||||
*/
|
||||
void (*setup)(int channels, int sampleRate, int requestedPeriodFrames,
|
||||
int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn);
|
||||
|
||||
/* called when there is data available to start playback */
|
||||
void (*start)(void);
|
||||
|
||||
/* called when SPICE reports the audio stream has stopped */
|
||||
void (*stop)(void);
|
||||
|
||||
/* [optional] called to set the volume of the channels */
|
||||
void (*volume)(int channels, const uint16_t volume[]);
|
||||
|
||||
/* [optional] called to set muting of the output */
|
||||
void (*mute)(bool mute);
|
||||
|
||||
/* return the current total playback latency in microseconds */
|
||||
uint64_t (*latency)(void);
|
||||
}
|
||||
playback;
|
||||
|
||||
struct
|
||||
{
|
||||
/* start the record stream
|
||||
* Note: currently SPICE only supports S16 samples so always assume so
|
||||
*/
|
||||
void (*start)(int channels, int sampleRate, LG_AudioPushFn pushFn);
|
||||
|
||||
/* called when SPICE reports the audio stream has stopped */
|
||||
void (*stop)(void);
|
||||
|
||||
/* [optional] called to set the volume of the channels */
|
||||
void (*volume)(int channels, const uint16_t volume[]);
|
||||
|
||||
/* [optional] called to set muting of the input */
|
||||
void (*mute)(bool mute);
|
||||
}
|
||||
record;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <EGL/egl.h>
|
||||
#include "common/types.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
@@ -62,6 +63,24 @@ enum LG_DSWarpSupport
|
||||
LG_DS_WARP_SCREEN,
|
||||
};
|
||||
|
||||
typedef enum LG_DSPointer
|
||||
{
|
||||
LG_POINTER_NONE = 0,
|
||||
LG_POINTER_SQUARE,
|
||||
LG_POINTER_ARROW,
|
||||
LG_POINTER_INPUT,
|
||||
LG_POINTER_MOVE,
|
||||
LG_POINTER_RESIZE_NS,
|
||||
LG_POINTER_RESIZE_EW,
|
||||
LG_POINTER_RESIZE_NESW,
|
||||
LG_POINTER_RESIZE_NWSE,
|
||||
LG_POINTER_HAND,
|
||||
LG_POINTER_NOT_ALLOWED,
|
||||
}
|
||||
LG_DSPointer;
|
||||
|
||||
#define LG_POINTER_COUNT (LG_POINTER_NOT_ALLOWED + 1)
|
||||
|
||||
typedef struct LG_DSInitParams
|
||||
{
|
||||
const char * title;
|
||||
@@ -74,6 +93,10 @@ typedef struct LG_DSInitParams
|
||||
|
||||
// if true the renderer requires an OpenGL context
|
||||
bool opengl;
|
||||
|
||||
// x11 needs to know if this is in use so we can decide to setup for
|
||||
// presentation times
|
||||
bool jitRender;
|
||||
}
|
||||
LG_DSInitParams;
|
||||
|
||||
@@ -83,8 +106,12 @@ typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type,
|
||||
typedef struct LG_DSGLContext
|
||||
* LG_DSGLContext;
|
||||
|
||||
typedef struct LGEvent LGEvent;
|
||||
|
||||
struct LG_DisplayServerOps
|
||||
{
|
||||
const char * name;
|
||||
|
||||
/* called before options are parsed, useful for registering options */
|
||||
void (*setup)(void);
|
||||
|
||||
@@ -98,13 +125,13 @@ struct LG_DisplayServerOps
|
||||
bool (*init)(const LG_DSInitParams params);
|
||||
|
||||
/* called at startup after window creation, renderer and SPICE is ready */
|
||||
void (*startup)();
|
||||
void (*startup)(void);
|
||||
|
||||
/* called just before final window destruction, before final free */
|
||||
void (*shutdown)();
|
||||
void (*shutdown)(void);
|
||||
|
||||
/* final free */
|
||||
void (*free)();
|
||||
void (*free)(void);
|
||||
|
||||
/*
|
||||
* return a system specific property, returns false if unsupported or failure
|
||||
@@ -129,17 +156,30 @@ struct LG_DisplayServerOps
|
||||
void (*glSwapBuffers)(void);
|
||||
#endif
|
||||
|
||||
/* Waits for a good time to render the next frame in time for the next vblank.
|
||||
* This is optional and a display server may choose to not implement it.
|
||||
*
|
||||
* return true to force the frame to be rendered, this is used by X11 for
|
||||
* calibration */
|
||||
bool (*waitFrame)(void);
|
||||
|
||||
/* This must be called when waitFrame returns, but no frame is actually rendered. */
|
||||
void (*skipFrame)(void);
|
||||
|
||||
/* This is used to interrupt waitFrame. */
|
||||
void (*stopWaitFrame)(void);
|
||||
|
||||
/* dm specific cursor implementations */
|
||||
void (*guestPointerUpdated)(double x, double y, double localX, double localY);
|
||||
void (*showPointer)(bool show);
|
||||
void (*grabKeyboard)();
|
||||
void (*ungrabKeyboard)();
|
||||
void (*setPointer)(LG_DSPointer pointer);
|
||||
void (*grabKeyboard)(void);
|
||||
void (*ungrabKeyboard)(void);
|
||||
/* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */
|
||||
void (*grabPointer)();
|
||||
void (*ungrabPointer)();
|
||||
void (*grabPointer)(void);
|
||||
void (*ungrabPointer)(void);
|
||||
/* (un)capturePointer is used do toggle special cursor tracking in capture mode */
|
||||
void (*capturePointer)();
|
||||
void (*uncapturePointer)();
|
||||
void (*capturePointer)(void);
|
||||
void (*uncapturePointer)(void);
|
||||
|
||||
/* exiting = true if the warp is to leave the window */
|
||||
void (*warpPointer)(int x, int y, bool exiting);
|
||||
@@ -147,14 +187,17 @@ struct LG_DisplayServerOps
|
||||
/* called when the client needs to realign the pointer. This should simply
|
||||
* call the appropriate app_handleMouse* method for the platform with zero
|
||||
* deltas */
|
||||
void (*realignPointer)();
|
||||
void (*realignPointer)(void);
|
||||
|
||||
/* returns true if the position specified is actually valid */
|
||||
bool (*isValidPointerPos)(int x, int y);
|
||||
|
||||
/* called to disable/enable the screensaver */
|
||||
void (*inhibitIdle)();
|
||||
void (*uninhibitIdle)();
|
||||
void (*inhibitIdle)(void);
|
||||
void (*uninhibitIdle)(void);
|
||||
|
||||
/* called to request activation */
|
||||
void (*requestActivation)(void);
|
||||
|
||||
/* wait for the specified time without blocking UI processing/event loops */
|
||||
void (*wait)(unsigned int time);
|
||||
@@ -173,49 +216,50 @@ struct LG_DisplayServerOps
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
#define ASSERT_EGL_FN(x) assert(x);
|
||||
#define ASSERT_EGL_FN(x) DEBUG_ASSERT(x)
|
||||
#else
|
||||
#define ASSERT_EGL_FN(x)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
#define ASSERT_OPENGL_FN(x) assert(x)
|
||||
#define ASSERT_OPENGL_FN(x) DEBUG_ASSERT(x)
|
||||
#else
|
||||
#define ASSERT_OPENGL_FN(x)
|
||||
#endif
|
||||
|
||||
#define ASSERT_LG_DS_VALID(x) \
|
||||
assert((x)->setup ); \
|
||||
assert((x)->probe ); \
|
||||
assert((x)->earlyInit ); \
|
||||
assert((x)->init ); \
|
||||
assert((x)->startup ); \
|
||||
assert((x)->shutdown ); \
|
||||
assert((x)->free ); \
|
||||
assert((x)->getProp ); \
|
||||
ASSERT_EGL_FN((x)->getEGLDisplay ); \
|
||||
ASSERT_EGL_FN((x)->getEGLNativeWindow ); \
|
||||
ASSERT_EGL_FN((x)->eglSwapBuffers ); \
|
||||
DEBUG_ASSERT((x)->setup ); \
|
||||
DEBUG_ASSERT((x)->probe ); \
|
||||
DEBUG_ASSERT((x)->earlyInit); \
|
||||
DEBUG_ASSERT((x)->init ); \
|
||||
DEBUG_ASSERT((x)->startup ); \
|
||||
DEBUG_ASSERT((x)->shutdown ); \
|
||||
DEBUG_ASSERT((x)->free ); \
|
||||
DEBUG_ASSERT((x)->getProp ); \
|
||||
ASSERT_EGL_FN((x)->getEGLDisplay ); \
|
||||
ASSERT_EGL_FN((x)->getEGLNativeWindow); \
|
||||
ASSERT_EGL_FN((x)->eglSwapBuffers ); \
|
||||
ASSERT_OPENGL_FN((x)->glCreateContext ); \
|
||||
ASSERT_OPENGL_FN((x)->glDeleteContext ); \
|
||||
ASSERT_OPENGL_FN((x)->glMakeCurrent ); \
|
||||
ASSERT_OPENGL_FN((x)->glSetSwapInterval); \
|
||||
ASSERT_OPENGL_FN((x)->glSwapBuffers ); \
|
||||
assert((x)->guestPointerUpdated); \
|
||||
assert((x)->showPointer ); \
|
||||
assert((x)->grabPointer ); \
|
||||
assert((x)->ungrabPointer ); \
|
||||
assert((x)->capturePointer ); \
|
||||
assert((x)->uncapturePointer ); \
|
||||
assert((x)->warpPointer ); \
|
||||
assert((x)->realignPointer ); \
|
||||
assert((x)->isValidPointerPos ); \
|
||||
assert((x)->inhibitIdle ); \
|
||||
assert((x)->uninhibitIdle ); \
|
||||
assert((x)->wait ); \
|
||||
assert((x)->setWindowSize ); \
|
||||
assert((x)->setFullscreen ); \
|
||||
assert((x)->getFullscreen ); \
|
||||
assert((x)->minimize );
|
||||
DEBUG_ASSERT(!(x)->waitFrame == !(x)->stopWaitFrame); \
|
||||
DEBUG_ASSERT((x)->guestPointerUpdated); \
|
||||
DEBUG_ASSERT((x)->setPointer ); \
|
||||
DEBUG_ASSERT((x)->grabPointer ); \
|
||||
DEBUG_ASSERT((x)->ungrabPointer ); \
|
||||
DEBUG_ASSERT((x)->capturePointer ); \
|
||||
DEBUG_ASSERT((x)->uncapturePointer ); \
|
||||
DEBUG_ASSERT((x)->warpPointer ); \
|
||||
DEBUG_ASSERT((x)->realignPointer ); \
|
||||
DEBUG_ASSERT((x)->isValidPointerPos ); \
|
||||
DEBUG_ASSERT((x)->inhibitIdle ); \
|
||||
DEBUG_ASSERT((x)->uninhibitIdle ); \
|
||||
DEBUG_ASSERT((x)->wait ); \
|
||||
DEBUG_ASSERT((x)->setWindowSize ); \
|
||||
DEBUG_ASSERT((x)->setFullscreen ); \
|
||||
DEBUG_ASSERT((x)->getFullscreen ); \
|
||||
DEBUG_ASSERT((x)->minimize );
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
86
client/include/interface/overlay.h
Normal file
86
client/include/interface/overlay.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_I_OVERLAY_
|
||||
#define _H_I_OVERLAY_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
#define TICK_RATE 25
|
||||
|
||||
struct LG_OverlayOps
|
||||
{
|
||||
/* internal name of the overlay for debugging */
|
||||
const char * name;
|
||||
|
||||
/* called very early to allow for option registration, optional */
|
||||
void (*earlyInit)(void);
|
||||
|
||||
/* called when the overlay is registered */
|
||||
bool (*init)(void ** udata, const void * params);
|
||||
|
||||
/* final free */
|
||||
void (*free)(void * udata);
|
||||
|
||||
/* return true if realtime rendering is required when in jitRender mode
|
||||
* optional, if omitted assumes false */
|
||||
bool (*needs_render)(void * udata, bool interactive);
|
||||
|
||||
/* return true if the overlay currently requires overlay mode
|
||||
* optional, if omitted assumes false */
|
||||
bool (*needs_overlay)(void * udata);
|
||||
|
||||
/* perform the actual drawing/rendering
|
||||
*
|
||||
* `interactive` is true if the application is currently in overlay interaction
|
||||
* mode.
|
||||
*
|
||||
* `windowRects` is an array of window rects that were rendered using screen
|
||||
* coordinates. Will be `NULL` if the information is not required.
|
||||
*
|
||||
* `maxRects` is the length of `windowRects`, or 0 if `windowRects` is `NULL`
|
||||
*
|
||||
* returns the number of rects written to `windowRects`, or -1 if there is not
|
||||
* enough room left.
|
||||
*/
|
||||
int (*render)(void * udata, bool interactive, struct Rect * windowRects,
|
||||
int maxRects);
|
||||
|
||||
/* called TICK_RATE times a second by the application
|
||||
*
|
||||
* Note: This may not run in the same context as `render`!
|
||||
*
|
||||
* return true if the frame needs to be rendered
|
||||
* optional, if omitted assumes false
|
||||
*/
|
||||
bool (*tick)(void * udata, unsigned long long tickCount);
|
||||
|
||||
/* TODO: add load/save settings capabillity */
|
||||
};
|
||||
|
||||
#define ASSERT_LG_OVERLAY_VALID(x) \
|
||||
DEBUG_ASSERT((x)->name ); \
|
||||
DEBUG_ASSERT((x)->init ); \
|
||||
DEBUG_ASSERT((x)->free ); \
|
||||
DEBUG_ASSERT((x)->render);
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -27,26 +27,26 @@
|
||||
#include "common/framebuffer.h"
|
||||
|
||||
#define IS_LG_RENDERER_VALID(x) \
|
||||
((x)->get_name && \
|
||||
(x)->create && \
|
||||
(x)->initialize && \
|
||||
(x)->deinitialize && \
|
||||
(x)->on_restart && \
|
||||
(x)->on_resize && \
|
||||
(x)->on_mouse_shape && \
|
||||
(x)->on_mouse_event && \
|
||||
(x)->on_alert && \
|
||||
(x)->on_help && \
|
||||
(x)->on_show_fps && \
|
||||
(x)->render_startup && \
|
||||
(x)->render && \
|
||||
(x)->update_fps)
|
||||
((x)->getName && \
|
||||
(x)->create && \
|
||||
(x)->initialize && \
|
||||
(x)->deinitialize && \
|
||||
(x)->onRestart && \
|
||||
(x)->onResize && \
|
||||
(x)->onMouseShape && \
|
||||
(x)->onMouseEvent && \
|
||||
(x)->renderStartup && \
|
||||
(x)->render && \
|
||||
(x)->createTexture && \
|
||||
(x)->freeTexture && \
|
||||
(x)->spiceConfigure && \
|
||||
(x)->spiceDrawFill && \
|
||||
(x)->spiceDrawBitmap && \
|
||||
(x)->spiceShow)
|
||||
|
||||
typedef struct LG_RendererParams
|
||||
{
|
||||
// TTF_Font * font;
|
||||
// TTF_Font * alertFont;
|
||||
bool quickSplash;
|
||||
bool quickSplash;
|
||||
}
|
||||
LG_RendererParams;
|
||||
|
||||
@@ -71,9 +71,11 @@ LG_RendererRotate;
|
||||
|
||||
typedef struct LG_RendererFormat
|
||||
{
|
||||
FrameType type; // frame type
|
||||
unsigned int width; // image width
|
||||
unsigned int height; // image height
|
||||
FrameType type; // frame type
|
||||
unsigned int screenWidth; // actual width of the host
|
||||
unsigned int screenHeight; // actual height of the host
|
||||
unsigned int frameWidth; // width of frame transmitted
|
||||
unsigned int frameHeight; // height of frame transmitted
|
||||
unsigned int stride; // scanline width (zero if compresed)
|
||||
unsigned int pitch; // scanline bytes (or compressed size)
|
||||
unsigned int bpp; // bits per pixel (zero if compressed)
|
||||
@@ -99,49 +101,103 @@ typedef enum LG_RendererCursor
|
||||
}
|
||||
LG_RendererCursor;
|
||||
|
||||
// returns the friendly name of the renderer
|
||||
typedef const char * (* LG_RendererGetName)();
|
||||
typedef struct LG_Renderer LG_Renderer;
|
||||
|
||||
// called pre-creation to allow the renderer to register any options it might have
|
||||
typedef void (* LG_RendererSetup)();
|
||||
typedef struct LG_RendererOps
|
||||
{
|
||||
/* returns the friendly name of the renderer */
|
||||
const char * (*getName)(void);
|
||||
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params, bool * needsOpenGL);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque);
|
||||
typedef void (* LG_RendererDeInitialize )(void * opaque);
|
||||
typedef bool (* LG_RendererSupports )(void * opaque, LG_RendererSupport support);
|
||||
typedef void (* LG_RendererOnRestart )(void * opaque);
|
||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate);
|
||||
typedef bool (* LG_RendererOnMouseShape )(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
||||
typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visible , const int x, const int y);
|
||||
typedef bool (* LG_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format, bool useDMA);
|
||||
typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD);
|
||||
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
|
||||
typedef void (* LG_RendererOnHelp )(void * opaque, const char * message);
|
||||
typedef void (* LG_RendererOnShowFPS )(void * opaque, bool showFPS);
|
||||
typedef bool (* LG_RendererRenderStartup)(void * opaque);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, LG_RendererRotate rotate);
|
||||
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
|
||||
/* called pre-creation to allow the renderer to register any options it may
|
||||
* have */
|
||||
void (*setup)(void);
|
||||
|
||||
/* creates an instance of the renderer
|
||||
* Context: lg_run */
|
||||
bool (*create)(LG_Renderer ** renderer, const LG_RendererParams params,
|
||||
bool * needsOpenGL);
|
||||
|
||||
/* initializes the renderer for use
|
||||
* Context: lg_run */
|
||||
bool (*initialize)(LG_Renderer * renderer);
|
||||
|
||||
/* deinitializes & frees the renderer
|
||||
* Context: lg_run & renderThread */
|
||||
void (*deinitialize)(LG_Renderer * renderer);
|
||||
|
||||
/* returns true if the specified feature is supported
|
||||
* Context: renderThread */
|
||||
bool (*supports)(LG_Renderer * renderer, LG_RendererSupport support);
|
||||
|
||||
/* called when the renderer is to reset it's state
|
||||
* Context: lg_run & frameThread */
|
||||
void (*onRestart)(LG_Renderer * renderer);
|
||||
|
||||
/* called when the viewport has been resized
|
||||
* Context: renderThrtead */
|
||||
void (*onResize)(LG_Renderer * renderer, const int width, const int height,
|
||||
const double scale, const LG_RendererRect destRect,
|
||||
LG_RendererRotate rotate);
|
||||
|
||||
/* called when the mouse shape has changed
|
||||
* Context: cursorThread */
|
||||
bool (*onMouseShape)(LG_Renderer * renderer, const LG_RendererCursor cursor,
|
||||
const int width, const int height, const int pitch, const uint8_t * data);
|
||||
|
||||
/* called when the mouse has moved or changed visibillity
|
||||
* Context: cursorThread */
|
||||
bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, int x, int y,
|
||||
const int hx, const int hy);
|
||||
|
||||
/* called when the frame format has changed
|
||||
* Context: frameThread */
|
||||
bool (*onFrameFormat)(LG_Renderer * renderer,
|
||||
const LG_RendererFormat format);
|
||||
|
||||
/* called when there is a new frame
|
||||
* Context: frameThread */
|
||||
bool (*onFrame)(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFD,
|
||||
const FrameDamageRect * damage, int damageCount);
|
||||
|
||||
/* called when the rederer is to startup
|
||||
* Context: renderThread */
|
||||
bool (*renderStartup)(LG_Renderer * renderer, bool useDMA);
|
||||
|
||||
/* called to render the scene
|
||||
* Context: renderThread */
|
||||
bool (*render)(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
const bool newFrame, const bool invalidateWindow,
|
||||
void (*preSwap)(void * udata), void * udata);
|
||||
|
||||
/* called to create a texture from the specified 32-bit RGB image data. This
|
||||
* method is for use with Dear ImGui
|
||||
* Context: renderThread */
|
||||
void * (*createTexture)(LG_Renderer * renderer,
|
||||
int width, int height, uint8_t * data);
|
||||
|
||||
/* called to free a texture previously created by createTexture. This method
|
||||
* is for use with Dear ImGui
|
||||
* Context: renderThread */
|
||||
void (*freeTexture)(LG_Renderer * renderer, void * texture);
|
||||
|
||||
/* setup the spice display */
|
||||
void (*spiceConfigure)(LG_Renderer * renderer, int width, int height);
|
||||
|
||||
/* draw a filled rect on the spice display with the specified color */
|
||||
void (*spiceDrawFill)(LG_Renderer * renderer, int x, int y, int width,
|
||||
int height, uint32_t color);
|
||||
|
||||
/* draw an image on the spice display, data is RGBA32 */
|
||||
void (*spiceDrawBitmap)(LG_Renderer * renderer, int x, int y, int width,
|
||||
int height, int stride, uint8_t * data, bool topDown);
|
||||
|
||||
/* show the spice display */
|
||||
void (*spiceShow)(LG_Renderer * renderer, bool show);
|
||||
}
|
||||
LG_RendererOps;
|
||||
|
||||
typedef struct LG_Renderer
|
||||
{
|
||||
LG_RendererGetName get_name;
|
||||
LG_RendererSetup setup;
|
||||
|
||||
LG_RendererCreate create;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererSupports supports;
|
||||
LG_RendererOnRestart on_restart;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
LG_RendererOnFrameFormat on_frame_format;
|
||||
LG_RendererOnFrame on_frame;
|
||||
LG_RendererOnAlert on_alert;
|
||||
LG_RendererOnHelp on_help;
|
||||
LG_RendererOnShowFPS on_show_fps;
|
||||
LG_RendererRenderStartup render_startup;
|
||||
LG_RendererRender render;
|
||||
LG_RendererUpdateFPS update_fps;
|
||||
LG_RendererOps ops;
|
||||
}
|
||||
LG_Renderer;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
struct ll;
|
||||
|
||||
struct ll * ll_new();
|
||||
void ll_free (struct ll * list);
|
||||
void ll_push (struct ll * list, void * data);
|
||||
bool ll_shift (struct ll * list, void ** data);
|
||||
bool ll_peek_head(struct ll * list, void ** data);
|
||||
bool ll_peek_tail(struct ll * list, void ** data);
|
||||
unsigned int ll_count (struct ll * list);
|
||||
|
||||
void ll_reset (struct ll * list);
|
||||
bool ll_walk (struct ll * list, void ** data);
|
||||
49
client/include/overlay_utils.h
Normal file
49
client/include/overlay_utils.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_OVERLAY_UTILS_
|
||||
#define _H_LG_OVERLAY_UTILS_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
typedef struct ImVec2 ImVec2;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void * tex;
|
||||
int width;
|
||||
int height;
|
||||
}
|
||||
OverlayImage;
|
||||
|
||||
void overlayGetImGuiRect(struct Rect * rect);
|
||||
ImVec2 * overlayGetScreenSize(void);
|
||||
void overlayTextURL(const char * url, const char * text);
|
||||
void overlayTextMaybeURL(const char * text, bool wrapped);
|
||||
|
||||
// create a texture from a SVG and scale it to fit the supplied width & height
|
||||
bool overlayLoadSVG(const char * data, unsigned int size, OverlayImage * image,
|
||||
int width, int height);
|
||||
|
||||
void overlayFreeImage(OverlayImage * image);
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "common/types.h"
|
||||
#include "common/util.h"
|
||||
|
||||
// reads the specified file into a new buffer
|
||||
// the callee must free the buffer
|
||||
@@ -42,4 +43,8 @@ static inline double util_clamp(double x, double min, double max)
|
||||
return x;
|
||||
}
|
||||
|
||||
bool util_initUIFonts(void);
|
||||
void util_freeUIFonts(void);
|
||||
char * util_getUIFont(const char * fontName);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,7 +5,7 @@ set(RENDERER_H "${CMAKE_BINARY_DIR}/include/dynamic/renderers.h")
|
||||
set(RENDERER_C "${CMAKE_BINARY_DIR}/src/renderers.c")
|
||||
|
||||
file(WRITE ${RENDERER_H} "#include \"interface/renderer.h\"\n\n")
|
||||
file(APPEND ${RENDERER_H} "extern LG_Renderer * LG_Renderers[];\n\n")
|
||||
file(APPEND ${RENDERER_H} "extern LG_RendererOps * LG_Renderers[];\n\n")
|
||||
|
||||
file(WRITE ${RENDERER_C} "#include \"interface/renderer.h\"\n\n")
|
||||
file(APPEND ${RENDERER_C} "#include <stddef.h>\n\n")
|
||||
@@ -33,10 +33,10 @@ list(LENGTH RENDERERS RENDERER_COUNT)
|
||||
file(APPEND ${RENDERER_H} "#define LG_RENDERER_COUNT ${RENDERER_COUNT}\n")
|
||||
|
||||
foreach(renderer ${RENDERERS})
|
||||
file(APPEND ${RENDERER_C} "extern LG_Renderer LGR_${renderer};\n")
|
||||
file(APPEND ${RENDERER_C} "extern LG_RendererOps LGR_${renderer};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${RENDERER_C} "\nconst LG_Renderer * LG_Renderers[] =\n{\n")
|
||||
file(APPEND ${RENDERER_C} "\nconst LG_RendererOps * LG_Renderers[] =\n{\n")
|
||||
foreach(renderer ${RENDERERS})
|
||||
file(APPEND ${RENDERER_C} " &LGR_${renderer},\n")
|
||||
endforeach()
|
||||
|
||||
@@ -1,72 +1,116 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(renderer_EGL LANGUAGES C)
|
||||
project(renderer_EGL LANGUAGES C CXX)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(RENDERER_EGL_PKGCONFIG REQUIRED
|
||||
egl
|
||||
gl
|
||||
pkg_check_modules(RENDERER_EGL REQUIRED IMPORTED_TARGET
|
||||
egl
|
||||
gl
|
||||
)
|
||||
|
||||
pkg_check_modules(RENDERER_EGL_OPT_PKGCONFIG
|
||||
wayland-egl
|
||||
pkg_check_modules(RENDERER_EGL_OPT IMPORTED_TARGET
|
||||
wayland-egl
|
||||
)
|
||||
|
||||
find_program(AWK NAMES gawk mawk original-awk awk)
|
||||
|
||||
if(AWK MATCHES ".+-NOTFOUND")
|
||||
message(FATAL_ERROR "FATAL: some known version of awk couldn't be found (${AWK}).")
|
||||
else()
|
||||
message(STATUS "Using awk: ${AWK}")
|
||||
endif()
|
||||
|
||||
include(MakeObject)
|
||||
make_object(
|
||||
EGL_SHADER
|
||||
shader/desktop.vert
|
||||
shader/desktop_rgb.frag
|
||||
shader/cursor.vert
|
||||
shader/cursor_rgb.frag
|
||||
shader/cursor_mono.frag
|
||||
shader/fps.vert
|
||||
shader/fps.frag
|
||||
shader/fps_bg.frag
|
||||
shader/help.vert
|
||||
shader/help.frag
|
||||
shader/help_bg.frag
|
||||
shader/alert.vert
|
||||
shader/alert.frag
|
||||
shader/alert_bg.frag
|
||||
shader/splash_bg.vert
|
||||
shader/splash_bg.frag
|
||||
shader/splash_logo.vert
|
||||
shader/splash_logo.frag
|
||||
function(build_shaders header_dir)
|
||||
file(GLOB headers "${header_dir}/*.h")
|
||||
set(EGL_SHADER_PROCESSED)
|
||||
foreach(shader ${ARGN})
|
||||
set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${shader}")
|
||||
add_custom_command(OUTPUT "${out_f}"
|
||||
COMMAND "${AWK}" -f "${CMAKE_CURRENT_SOURCE_DIR}/glsl.include.awk"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${shader}" > "${out_f}"
|
||||
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${shader}"
|
||||
DEPENDS ${headers}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shader"
|
||||
COMMENT "Preprocessing shader ${shader}"
|
||||
VERBATIM
|
||||
)
|
||||
endforeach()
|
||||
|
||||
set(CMAKE_CURRENT_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
make_object(
|
||||
EGL_SHADER
|
||||
${ARGN}
|
||||
)
|
||||
|
||||
set(EGL_SHADER_OBJS "${EGL_SHADER_OBJS}" PARENT_SCOPE)
|
||||
set(EGL_SHADER_INCS "${EGL_SHADER_INCS}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
build_shaders(
|
||||
shader
|
||||
shader/desktop.vert
|
||||
shader/desktop_rgb.frag
|
||||
shader/cursor.vert
|
||||
shader/cursor_rgb.frag
|
||||
shader/cursor_mono.frag
|
||||
shader/damage.vert
|
||||
shader/damage.frag
|
||||
shader/basic.vert
|
||||
shader/ffx_cas.frag
|
||||
shader/ffx_fsr1_easu.frag
|
||||
shader/ffx_fsr1_rcas.frag
|
||||
shader/downscale.frag
|
||||
shader/downscale_lanczos2.frag
|
||||
shader/downscale_linear.frag
|
||||
)
|
||||
|
||||
make_defines(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shader/desktop_rgb.frag"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shader/desktop_rgb.frag"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h"
|
||||
)
|
||||
|
||||
add_library(renderer_EGL STATIC
|
||||
egl.c
|
||||
egldebug.c
|
||||
shader.c
|
||||
texture.c
|
||||
model.c
|
||||
desktop.c
|
||||
cursor.c
|
||||
fps.c
|
||||
help.c
|
||||
draw.c
|
||||
splash.c
|
||||
alert.c
|
||||
${EGL_SHADER_OBJS}
|
||||
"${EGL_SHADER_INCS}/desktop_rgb.def.h"
|
||||
egl.c
|
||||
egldebug.c
|
||||
shader.c
|
||||
texture_util.c
|
||||
texture.c
|
||||
texture_buffer.c
|
||||
texture_framebuffer.c
|
||||
texture_dmabuf.c
|
||||
model.c
|
||||
desktop.c
|
||||
desktop_rects.c
|
||||
cursor.c
|
||||
damage.c
|
||||
framebuffer.c
|
||||
postprocess.c
|
||||
ffx.c
|
||||
filter.c
|
||||
filter_ffx_cas.c
|
||||
filter_ffx_fsr1.c
|
||||
filter_downscale.c
|
||||
${EGL_SHADER_OBJS}
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h"
|
||||
${PROJECT_TOP}/repos/cimgui/imgui/backends/imgui_impl_opengl3.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(renderer_EGL PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 IMGUI_IMPL_OPENGL_ES3)
|
||||
|
||||
target_link_libraries(renderer_EGL
|
||||
${RENDERER_EGL_PKGCONFIG_LIBRARIES}
|
||||
${RENDERER_EGL_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
fonts
|
||||
PkgConfig::RENDERER_EGL
|
||||
lg_common
|
||||
|
||||
cimgui
|
||||
)
|
||||
if(RENDERER_EGL_OPT_FOUND)
|
||||
target_link_libraries(renderer_EGL
|
||||
PkgConfig::RENDERER_EGL_OPT
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(renderer_EGL
|
||||
PRIVATE
|
||||
src
|
||||
${EGL_SHADER_INCS}
|
||||
${RENDERER_EGL_PKGCONFIG_INCLUDE_DIRS}
|
||||
${RENDERER_EGL_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
PRIVATE
|
||||
src
|
||||
${EGL_SHADER_INCS}
|
||||
)
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "alert.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "alert.vert.h"
|
||||
#include "alert.frag.h"
|
||||
#include "alert_bg.frag.h"
|
||||
|
||||
struct EGL_Alert
|
||||
{
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
|
||||
EGL_Texture * texture;
|
||||
EGL_Shader * shader;
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
LG_Lock lock;
|
||||
bool update;
|
||||
LG_FontBitmap * bmp;
|
||||
|
||||
bool ready;
|
||||
float width , height ;
|
||||
float bgWidth, bgHeight;
|
||||
float r, g, b, a;
|
||||
|
||||
// uniforms
|
||||
GLint uScreen , uSize;
|
||||
GLint uScreenBG, uSizeBG, uColorBG;
|
||||
};
|
||||
|
||||
bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj)
|
||||
{
|
||||
*alert = (EGL_Alert *)malloc(sizeof(EGL_Alert));
|
||||
if (!*alert)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Alert");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*alert, 0, sizeof(EGL_Alert));
|
||||
|
||||
(*alert)->font = font;
|
||||
(*alert)->fontObj = fontObj;
|
||||
LG_LOCK_INIT((*alert)->lock);
|
||||
|
||||
if (!egl_texture_init(&(*alert)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*alert)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*alert)->shaderBG))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert bg shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!egl_shader_compile((*alert)->shader,
|
||||
b_shader_alert_vert, b_shader_alert_vert_size,
|
||||
b_shader_alert_frag, b_shader_alert_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the alert shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*alert)->shaderBG,
|
||||
b_shader_alert_vert , b_shader_alert_vert_size,
|
||||
b_shader_alert_bg_frag, b_shader_alert_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the alert shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
(*alert)->uSize = egl_shader_get_uniform_location((*alert)->shader , "size" );
|
||||
(*alert)->uScreen = egl_shader_get_uniform_location((*alert)->shader , "screen");
|
||||
(*alert)->uSizeBG = egl_shader_get_uniform_location((*alert)->shaderBG, "size" );
|
||||
(*alert)->uScreenBG = egl_shader_get_uniform_location((*alert)->shaderBG, "screen");
|
||||
(*alert)->uColorBG = egl_shader_get_uniform_location((*alert)->shaderBG, "color" );
|
||||
|
||||
if (!egl_model_init(&(*alert)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*alert)->model);
|
||||
egl_model_set_texture((*alert)->model, (*alert)->texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_alert_free(EGL_Alert ** alert)
|
||||
{
|
||||
if (!*alert)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*alert)->texture );
|
||||
egl_shader_free (&(*alert)->shader );
|
||||
egl_shader_free (&(*alert)->shaderBG);
|
||||
egl_model_free (&(*alert)->model );
|
||||
|
||||
free(*alert);
|
||||
*alert = NULL;
|
||||
}
|
||||
|
||||
void egl_alert_set_color(EGL_Alert * alert, const uint32_t color)
|
||||
{
|
||||
alert->r = (1.0f / 0xff) * ((color >> 24) & 0xFF);
|
||||
alert->g = (1.0f / 0xff) * ((color >> 16) & 0xFF);
|
||||
alert->b = (1.0f / 0xff) * ((color >> 8) & 0xFF);
|
||||
alert->a = (1.0f / 0xff) * ((color >> 0) & 0xFF);
|
||||
}
|
||||
|
||||
void egl_alert_set_text (EGL_Alert * alert, const char * str)
|
||||
{
|
||||
LG_LOCK(alert->lock);
|
||||
alert->bmp = alert->font->render(alert->fontObj, 0xffffff00, str);
|
||||
if (!alert->bmp)
|
||||
{
|
||||
alert->update = false;
|
||||
LG_UNLOCK(alert->lock);
|
||||
DEBUG_ERROR("Failed to render alert text");
|
||||
return;
|
||||
}
|
||||
|
||||
alert->update = true;
|
||||
LG_UNLOCK(alert->lock);
|
||||
}
|
||||
|
||||
void egl_alert_set_font(EGL_Alert * alert, LG_Font * fontObj)
|
||||
{
|
||||
LG_LOCK(alert->lock);
|
||||
alert->fontObj = fontObj;
|
||||
LG_UNLOCK(alert->lock);
|
||||
}
|
||||
|
||||
void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY)
|
||||
{
|
||||
if (alert->update)
|
||||
{
|
||||
LG_LOCK(alert->lock);
|
||||
egl_texture_setup(
|
||||
alert->texture,
|
||||
EGL_PF_BGRA,
|
||||
alert->bmp->width ,
|
||||
alert->bmp->height,
|
||||
alert->bmp->width * alert->bmp->bpp,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
egl_texture_update(alert->texture, alert->bmp->pixels);
|
||||
|
||||
alert->width = alert->bgWidth = alert->bmp->width;
|
||||
alert->height = alert->bgHeight = alert->bmp->height;
|
||||
|
||||
if (alert->bgWidth < 200)
|
||||
alert->bgWidth = 200;
|
||||
alert->bgHeight += 4;
|
||||
|
||||
alert->ready = true;
|
||||
|
||||
alert->font->release(alert->fontObj, alert->bmp);
|
||||
alert->update = false;
|
||||
alert->bmp = NULL;
|
||||
LG_UNLOCK(alert->lock);
|
||||
}
|
||||
|
||||
if (!alert->ready)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// render the background first
|
||||
egl_shader_use(alert->shaderBG);
|
||||
glUniform2f(alert->uScreenBG, scaleX , scaleY );
|
||||
glUniform2i(alert->uSizeBG , alert->bgWidth, alert->bgHeight);
|
||||
glUniform4f(alert->uColorBG , alert->r, alert->g, alert->b, alert->a);
|
||||
egl_model_render(alert->model);
|
||||
|
||||
// render the texture over the background
|
||||
egl_shader_use(alert->shader);
|
||||
glUniform2f(alert->uScreen, scaleX , scaleY );
|
||||
glUniform2i(alert->uSize , alert->width, alert->height);
|
||||
egl_model_render(alert->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -26,7 +26,9 @@
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -40,10 +42,21 @@ struct CursorTex
|
||||
struct EGL_Texture * texture;
|
||||
struct EGL_Shader * shader;
|
||||
GLuint uMousePos;
|
||||
GLuint uScale;
|
||||
GLuint uRotate;
|
||||
GLuint uCBMode;
|
||||
};
|
||||
|
||||
struct CursorPos
|
||||
{
|
||||
float x, y;
|
||||
};
|
||||
|
||||
struct CursorSize
|
||||
{
|
||||
float w, h;
|
||||
};
|
||||
|
||||
struct EGL_Cursor
|
||||
{
|
||||
LG_Lock lock;
|
||||
@@ -57,103 +70,110 @@ struct EGL_Cursor
|
||||
|
||||
// cursor state
|
||||
bool visible;
|
||||
float x, y, w, h;
|
||||
LG_RendererRotate rotate;
|
||||
int cbMode;
|
||||
|
||||
_Atomic(struct CursorPos) pos;
|
||||
_Atomic(struct CursorPos) hs;
|
||||
_Atomic(struct CursorSize) size;
|
||||
_Atomic(float) scale;
|
||||
|
||||
struct CursorTex norm;
|
||||
struct CursorTex mono;
|
||||
struct EGL_Model * model;
|
||||
};
|
||||
|
||||
static bool egl_cursor_tex_init(struct CursorTex * t,
|
||||
static bool cursorTexInit(struct CursorTex * t,
|
||||
const char * vertex_code , size_t vertex_size,
|
||||
const char * fragment_code, size_t fragment_size)
|
||||
{
|
||||
if (!egl_texture_init(&t->texture, NULL))
|
||||
if (!egl_textureInit(&t->texture, NULL, EGL_TEXTYPE_BUFFER))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&t->shader))
|
||||
if (!egl_shaderInit(&t->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile(t->shader,
|
||||
if (!egl_shaderCompile(t->shader,
|
||||
vertex_code, vertex_size, fragment_code, fragment_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
t->uMousePos = egl_shader_get_uniform_location(t->shader, "mouse" );
|
||||
t->uRotate = egl_shader_get_uniform_location(t->shader, "rotate");
|
||||
t->uCBMode = egl_shader_get_uniform_location(t->shader, "cbMode");
|
||||
t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" );
|
||||
t->uScale = egl_shaderGetUniform(t->shader, "scale" );
|
||||
t->uRotate = egl_shaderGetUniform(t->shader, "rotate" );
|
||||
t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void egl_cursor_tex_uniforms(EGL_Cursor * cursor, struct CursorTex * t, bool mono)
|
||||
static inline void setCursorTexUniforms(EGL_Cursor * cursor,
|
||||
struct CursorTex * t, bool mono, float x, float y,
|
||||
float w, float h, float scale)
|
||||
{
|
||||
if (mono)
|
||||
{
|
||||
glUniform4f(t->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
|
||||
glUniform1i(t->uRotate , cursor->rotate);
|
||||
glUniform1i(t->uCBMode , cursor->cbMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
glUniform4f(t->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
|
||||
glUniform1i(t->uRotate , cursor->rotate);
|
||||
glUniform1i(t->uCBMode , cursor->cbMode);
|
||||
}
|
||||
glUniform4f(t->uMousePos, x, y, w, mono ? h / 2 : h);
|
||||
glUniform1f(t->uScale , scale);
|
||||
glUniform1i(t->uRotate , cursor->rotate);
|
||||
glUniform1i(t->uCBMode , cursor->cbMode);
|
||||
}
|
||||
|
||||
static void egl_cursor_tex_free(struct CursorTex * t)
|
||||
static void cursorTexFree(struct CursorTex * t)
|
||||
{
|
||||
egl_texture_free(&t->texture);
|
||||
egl_shader_free (&t->shader );
|
||||
egl_textureFree(&t->texture);
|
||||
egl_shaderFree (&t->shader );
|
||||
};
|
||||
|
||||
bool egl_cursor_init(EGL_Cursor ** cursor)
|
||||
bool egl_cursorInit(EGL_Cursor ** cursor)
|
||||
{
|
||||
*cursor = (EGL_Cursor *)malloc(sizeof(EGL_Cursor));
|
||||
*cursor = malloc(sizeof(**cursor));
|
||||
if (!*cursor)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*cursor, 0, sizeof(EGL_Cursor));
|
||||
memset(*cursor, 0, sizeof(**cursor));
|
||||
LG_LOCK_INIT((*cursor)->lock);
|
||||
|
||||
if (!egl_cursor_tex_init(&(*cursor)->norm,
|
||||
if (!cursorTexInit(&(*cursor)->norm,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size))
|
||||
return false;
|
||||
|
||||
if (!egl_cursor_tex_init(&(*cursor)->mono,
|
||||
if (!cursorTexInit(&(*cursor)->mono,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size))
|
||||
return false;
|
||||
|
||||
if (!egl_model_init(&(*cursor)->model))
|
||||
if (!egl_modelInit(&(*cursor)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*cursor)->model);
|
||||
egl_modelSetDefault((*cursor)->model, true);
|
||||
|
||||
(*cursor)->cbMode = option_get_int("egl", "cbMode");
|
||||
|
||||
struct CursorPos pos = { .x = 0, .y = 0 };
|
||||
struct CursorPos hs = { .x = 0, .y = 0 };
|
||||
struct CursorSize size = { .w = 0, .h = 0 };
|
||||
atomic_init(&(*cursor)->pos , pos );
|
||||
atomic_init(&(*cursor)->hs , hs );
|
||||
atomic_init(&(*cursor)->size , size);
|
||||
atomic_init(&(*cursor)->scale, 1.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_cursor_free(EGL_Cursor ** cursor)
|
||||
void egl_cursorFree(EGL_Cursor ** cursor)
|
||||
{
|
||||
if (!*cursor)
|
||||
return;
|
||||
@@ -162,15 +182,15 @@ void egl_cursor_free(EGL_Cursor ** cursor)
|
||||
if ((*cursor)->data)
|
||||
free((*cursor)->data);
|
||||
|
||||
egl_cursor_tex_free(&(*cursor)->norm);
|
||||
egl_cursor_tex_free(&(*cursor)->mono);
|
||||
egl_model_free(&(*cursor)->model);
|
||||
cursorTexFree(&(*cursor)->norm);
|
||||
cursorTexFree(&(*cursor)->mono);
|
||||
egl_modelFree(&(*cursor)->model);
|
||||
|
||||
free(*cursor);
|
||||
*cursor = NULL;
|
||||
}
|
||||
|
||||
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type,
|
||||
bool egl_cursorSetShape(EGL_Cursor * cursor, const LG_RendererCursor type,
|
||||
const int width, const int height, const int stride, const uint8_t * data)
|
||||
{
|
||||
LG_LOCK(cursor->lock);
|
||||
@@ -186,7 +206,7 @@ bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type,
|
||||
if (cursor->data)
|
||||
free(cursor->data);
|
||||
|
||||
cursor->data = (uint8_t *)malloc(size);
|
||||
cursor->data = malloc(size);
|
||||
if (!cursor->data)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc buffer for cursor shape");
|
||||
@@ -203,33 +223,32 @@ bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type,
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_cursor_set_size(EGL_Cursor * cursor, const float w, const float h)
|
||||
void egl_cursorSetSize(EGL_Cursor * cursor, const float w, const float h)
|
||||
{
|
||||
cursor->w = w;
|
||||
cursor->h = h;
|
||||
struct CursorSize size = { .w = w, .h = h };
|
||||
atomic_store(&cursor->size, size);
|
||||
}
|
||||
|
||||
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y)
|
||||
void egl_cursorSetScale(EGL_Cursor * cursor, const float scale)
|
||||
{
|
||||
atomic_store(&cursor->scale, scale);
|
||||
}
|
||||
|
||||
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
|
||||
const float x, const float y, const float hx, const float hy)
|
||||
{
|
||||
cursor->visible = visible;
|
||||
cursor->x = x;
|
||||
cursor->y = y;
|
||||
struct CursorPos pos = { .x = x , .y = y };
|
||||
struct CursorPos hs = { .x = hx, .y = hy };
|
||||
atomic_store(&cursor->pos, pos);
|
||||
atomic_store(&cursor->hs , hs);
|
||||
}
|
||||
|
||||
struct CursorState egl_cursor_get_state(EGL_Cursor * cursor, int width, int height) {
|
||||
return (struct CursorState) {
|
||||
.visible = cursor->visible,
|
||||
.rect.x = (cursor->x * width + width) / 2,
|
||||
.rect.y = (-cursor->y * height + height) / 2 - cursor->h * height,
|
||||
.rect.w = cursor->w * width + 2,
|
||||
.rect.h = cursor->h * height + 2,
|
||||
};
|
||||
}
|
||||
|
||||
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
|
||||
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
LG_RendererRotate rotate, int width, int height)
|
||||
{
|
||||
if (!cursor->visible)
|
||||
return;
|
||||
return (struct CursorState) { .visible = false };
|
||||
|
||||
if (cursor->update)
|
||||
{
|
||||
@@ -241,22 +260,43 @@ void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
// fall through
|
||||
{
|
||||
uint32_t xor[cursor->height][cursor->width];
|
||||
for(int y = 0; y < cursor->height; ++y)
|
||||
for(int x = 0; x < cursor->width; ++x)
|
||||
{
|
||||
uint32_t * src = (uint32_t *)(data + (cursor->stride * y) + x * 4);
|
||||
const bool masked = (*src & 0xFF000000) != 0;
|
||||
if (masked)
|
||||
*src = xor[y][x] = *src & 0x00FFFFFF;
|
||||
else
|
||||
{
|
||||
xor[y][x] = 0xFF000000;
|
||||
*src |= 0xFF000000;
|
||||
}
|
||||
}
|
||||
|
||||
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
|
||||
cursor->width, cursor->height, sizeof(xor[0]));
|
||||
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor, true);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_texture_setup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false, false);
|
||||
egl_texture_update(cursor->norm.texture, data);
|
||||
egl_model_set_texture(cursor->model, cursor->norm.texture);
|
||||
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
|
||||
cursor->width, cursor->height, cursor->stride);
|
||||
egl_textureUpdate(cursor->norm.texture, data, true);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
uint32_t and[cursor->width * cursor->height];
|
||||
uint32_t xor[cursor->width * cursor->height];
|
||||
uint32_t and[cursor->height][cursor->width];
|
||||
uint32_t xor[cursor->height][cursor->width];
|
||||
|
||||
for(int y = 0; y < cursor->height; ++y)
|
||||
{
|
||||
for(int x = 0; x < cursor->width; ++x)
|
||||
{
|
||||
const uint8_t * srcAnd = data + (cursor->stride * y) + (x / 8);
|
||||
@@ -265,14 +305,17 @@ void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
|
||||
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
|
||||
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
|
||||
|
||||
and[y * cursor->width + x] = andMask;
|
||||
xor[y * cursor->width + x] = xorMask;
|
||||
and[y][x] = andMask;
|
||||
xor[y][x] = xorMask;
|
||||
}
|
||||
}
|
||||
|
||||
egl_texture_setup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
|
||||
egl_texture_setup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
|
||||
egl_texture_update(cursor->norm.texture, (uint8_t *)and);
|
||||
egl_texture_update(cursor->mono.texture, (uint8_t *)xor);
|
||||
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
|
||||
cursor->width, cursor->height, sizeof(and[0]));
|
||||
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
|
||||
cursor->width, cursor->height, sizeof(xor[0]));
|
||||
egl_textureUpdate(cursor->norm.texture, (uint8_t *)and, true);
|
||||
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -281,42 +324,108 @@ void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
|
||||
|
||||
cursor->rotate = rotate;
|
||||
|
||||
struct CursorPos pos = atomic_load(&cursor->pos );
|
||||
float scale = atomic_load(&cursor->scale);
|
||||
struct CursorPos hs = atomic_load(&cursor->hs );
|
||||
struct CursorSize size = atomic_load(&cursor->size );
|
||||
|
||||
pos.x -= hs.x * scale;
|
||||
pos.y -= hs.y * scale;
|
||||
size.w *= scale;
|
||||
size.h *= scale;
|
||||
|
||||
struct CursorState state = {
|
||||
.visible = true,
|
||||
};
|
||||
|
||||
switch (rotate)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
state.rect.x = (pos.x * width + width) / 2;
|
||||
state.rect.y = (-pos.y * height + height) / 2 - size.h * height;
|
||||
state.rect.w = size.w * width + 3;
|
||||
state.rect.h = size.h * height + 3;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
state.rect.x = (-pos.y * width + width) / 2 - size.h * width;
|
||||
state.rect.y = (-pos.x * height + height) / 2 - size.w * height;
|
||||
state.rect.w = size.h * width + 3;
|
||||
state.rect.h = size.w * height + 3;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_180:
|
||||
state.rect.x = (-pos.x * width + width) / 2 - size.w * width;
|
||||
state.rect.y = (pos.y * height + height) / 2;
|
||||
state.rect.w = size.w * width + 3;
|
||||
state.rect.h = size.h * height + 3;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_270:
|
||||
state.rect.x = (pos.y * width + width) / 2;
|
||||
state.rect.y = (pos.x * height + height) / 2;
|
||||
state.rect.w = size.h * width + 3;
|
||||
state.rect.h = size.w * height + 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_UNREACHABLE();
|
||||
}
|
||||
|
||||
state.rect.x = max(0, state.rect.x - 1);
|
||||
state.rect.y = max(0, state.rect.y - 1);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
egl_shader_use(cursor->norm.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->norm, true);;
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
|
||||
egl_model_set_texture(cursor->model, cursor->norm.texture);
|
||||
egl_model_render(cursor->model);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
|
||||
egl_shader_use(cursor->mono.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->mono, true);;
|
||||
egl_shaderUse(cursor->mono.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_model_set_texture(cursor->model, cursor->mono.texture);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_shader_use(cursor->norm.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->norm, false);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_model_render(cursor->model);
|
||||
egl_modelSetTexture(cursor->model, cursor->mono.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
egl_shader_use(cursor->mono.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->mono, false);
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
|
||||
egl_shaderUse(cursor->mono.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_model_render(cursor->model);
|
||||
egl_modelSetTexture(cursor->model, cursor->mono.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
break;
|
||||
}
|
||||
}
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "egl.h"
|
||||
#include "interface/renderer.h"
|
||||
|
||||
typedef struct EGL_Cursor EGL_Cursor;
|
||||
@@ -31,10 +32,10 @@ struct CursorState {
|
||||
struct Rect rect;
|
||||
};
|
||||
|
||||
bool egl_cursor_init(EGL_Cursor ** cursor);
|
||||
void egl_cursor_free(EGL_Cursor ** cursor);
|
||||
bool egl_cursorInit(EGL_Cursor ** cursor);
|
||||
void egl_cursorFree(EGL_Cursor ** cursor);
|
||||
|
||||
bool egl_cursor_set_shape(
|
||||
bool egl_cursorSetShape(
|
||||
EGL_Cursor * cursor,
|
||||
const LG_RendererCursor type,
|
||||
const int width,
|
||||
@@ -42,11 +43,12 @@ bool egl_cursor_set_shape(
|
||||
const int stride,
|
||||
const uint8_t * data);
|
||||
|
||||
void egl_cursor_set_size(EGL_Cursor * cursor, const float x, const float y);
|
||||
void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y);
|
||||
|
||||
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible,
|
||||
const float x, const float y);
|
||||
void egl_cursorSetScale(EGL_Cursor * cursor, const float scale);
|
||||
|
||||
struct CursorState egl_cursor_get_state(EGL_Cursor * cursor, int width, int height);
|
||||
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
|
||||
const float x, const float y, const float hx, const float hy);
|
||||
|
||||
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate);
|
||||
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
LG_RendererRotate rotate, int width, int height);
|
||||
|
||||
156
client/renderers/EGL/damage.c
Normal file
156
client/renderers/EGL/damage.c
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "damage.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "desktop_rects.h"
|
||||
#include "shader.h"
|
||||
#include "cimgui.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "damage.vert.h"
|
||||
#include "damage.frag.h"
|
||||
|
||||
struct EGL_Damage
|
||||
{
|
||||
EGL_Shader * shader;
|
||||
EGL_DesktopRects * mesh;
|
||||
GLfloat transform[6];
|
||||
|
||||
bool show;
|
||||
|
||||
int width , height;
|
||||
float translateX, translateY;
|
||||
float scaleX , scaleY;
|
||||
LG_RendererRotate rotate;
|
||||
|
||||
// uniforms
|
||||
GLint uTransform;
|
||||
};
|
||||
|
||||
void egl_damageConfigUI(EGL_Damage * damage)
|
||||
{
|
||||
igCheckbox("Show damage overlay", &damage->show);
|
||||
}
|
||||
|
||||
bool egl_damageInit(EGL_Damage ** damage)
|
||||
{
|
||||
*damage = malloc(sizeof(**damage));
|
||||
if (!*damage)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Damage");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*damage, 0, sizeof(EGL_Damage));
|
||||
|
||||
if (!egl_shaderInit(&(*damage)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the damage shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shaderCompile((*damage)->shader,
|
||||
b_shader_damage_vert, b_shader_damage_vert_size,
|
||||
b_shader_damage_frag, b_shader_damage_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the damage shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_desktopRectsInit(&(*damage)->mesh, KVMFR_MAX_DAMAGE_RECTS))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the mesh");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*damage)->uTransform = egl_shaderGetUniform((*damage)->shader, "transform");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_damageFree(EGL_Damage ** damage)
|
||||
{
|
||||
if (!*damage)
|
||||
return;
|
||||
|
||||
egl_desktopRectsFree(&(*damage)->mesh);
|
||||
egl_shaderFree(&(*damage)->shader);
|
||||
|
||||
free(*damage);
|
||||
*damage = NULL;
|
||||
}
|
||||
|
||||
static void update_matrix(EGL_Damage * damage)
|
||||
{
|
||||
egl_desktopRectsMatrix(damage->transform, damage->width, damage->height,
|
||||
damage->translateX, damage->translateY, damage->scaleX, damage->scaleY, damage->rotate);
|
||||
}
|
||||
|
||||
void egl_damageSetup(EGL_Damage * damage, int width, int height)
|
||||
{
|
||||
damage->width = width;
|
||||
damage->height = height;
|
||||
update_matrix(damage);
|
||||
}
|
||||
|
||||
void egl_damageResize(EGL_Damage * damage, float translateX, float translateY,
|
||||
float scaleX, float scaleY)
|
||||
{
|
||||
damage->translateX = translateX;
|
||||
damage->translateY = translateY;
|
||||
damage->scaleX = scaleX;
|
||||
damage->scaleY = scaleY;
|
||||
update_matrix(damage);
|
||||
}
|
||||
|
||||
bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate, const struct DesktopDamage * data)
|
||||
{
|
||||
if (!damage->show)
|
||||
return false;
|
||||
|
||||
if (rotate != damage->rotate)
|
||||
{
|
||||
damage->rotate = rotate;
|
||||
update_matrix(damage);
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
egl_shaderUse(damage->shader);
|
||||
glUniformMatrix3x2fv(damage->uTransform, 1, GL_FALSE, damage->transform);
|
||||
|
||||
if (data && data->count != 0)
|
||||
egl_desktopRectsUpdate(damage->mesh, (const struct DamageRects *) data,
|
||||
damage->width, damage->height);
|
||||
|
||||
egl_desktopRectsRender(damage->mesh);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
return true;
|
||||
}
|
||||
44
client/renderers/EGL/damage.h
Normal file
44
client/renderers/EGL/damage.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "common/KVMFR.h"
|
||||
#include "interface/renderer.h"
|
||||
#include "desktop_rects.h"
|
||||
|
||||
struct DesktopDamage
|
||||
{
|
||||
int count;
|
||||
FrameDamageRect rects[KVMFR_MAX_DAMAGE_RECTS];
|
||||
};
|
||||
|
||||
typedef struct EGL_Damage EGL_Damage;
|
||||
|
||||
bool egl_damageInit(EGL_Damage ** damage);
|
||||
void egl_damageFree(EGL_Damage ** damage);
|
||||
|
||||
void egl_damageConfigUI(EGL_Damage * damage);
|
||||
void egl_damageSetup(EGL_Damage * damage, int width, int height);
|
||||
void egl_damageResize(EGL_Damage * damage, float translateX, float translateY,
|
||||
float scaleX, float scaleY);
|
||||
bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate,
|
||||
const struct DesktopDamage * data);
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -22,11 +22,13 @@
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/array.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
#include "desktop_rects.h"
|
||||
#include "cimgui.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -36,31 +38,36 @@
|
||||
#include "desktop_rgb.frag.h"
|
||||
#include "desktop_rgb.def.h"
|
||||
|
||||
#include "postprocess.h"
|
||||
#include "filters.h"
|
||||
|
||||
struct DesktopShader
|
||||
{
|
||||
EGL_Shader * shader;
|
||||
GLint uDesktopPos;
|
||||
GLint uTransform;
|
||||
GLint uDesktopSize;
|
||||
GLint uRotate;
|
||||
GLint uScaleAlgo;
|
||||
GLint uNV, uNVGain;
|
||||
GLint uNVGain;
|
||||
GLint uCBMode;
|
||||
};
|
||||
|
||||
struct EGL_Desktop
|
||||
{
|
||||
EGL * egl;
|
||||
EGLDisplay * display;
|
||||
|
||||
EGL_Texture * texture;
|
||||
struct DesktopShader * shader; // the active shader
|
||||
EGL_Model * model;
|
||||
struct DesktopShader shader;
|
||||
EGL_DesktopRects * mesh;
|
||||
CountedBuffer * matrix;
|
||||
|
||||
// internals
|
||||
int width, height;
|
||||
LG_RendererRotate rotate;
|
||||
|
||||
// shader instances
|
||||
struct DesktopShader shader_generic;
|
||||
bool useSpice;
|
||||
int spiceWidth, spiceHeight;
|
||||
EGL_Texture * spiceTexture;
|
||||
|
||||
// scale algorithm
|
||||
int scaleAlgo;
|
||||
@@ -71,59 +78,65 @@ struct EGL_Desktop
|
||||
|
||||
// colorblind mode
|
||||
int cbMode;
|
||||
|
||||
bool useDMA;
|
||||
LG_RendererFormat format;
|
||||
|
||||
EGL_PostProcess * pp;
|
||||
_Atomic(bool) processFrame;
|
||||
};
|
||||
|
||||
// forwards
|
||||
void egl_desktop_toggle_nv(int key, void * opaque);
|
||||
void egl_desktop_toggle_scale_algo(int key, void * opaque);
|
||||
void toggleNV(int key, void * opaque);
|
||||
|
||||
static bool egl_init_desktop_shader(
|
||||
static bool egl_initDesktopShader(
|
||||
struct DesktopShader * shader,
|
||||
const char * vertex_code , size_t vertex_size,
|
||||
const char * fragment_code, size_t fragment_size
|
||||
)
|
||||
{
|
||||
if (!egl_shader_init(&shader->shader))
|
||||
if (!egl_shaderInit(&shader->shader))
|
||||
return false;
|
||||
|
||||
if (!egl_shader_compile(shader->shader,
|
||||
if (!egl_shaderCompile(shader->shader,
|
||||
vertex_code , vertex_size,
|
||||
fragment_code, fragment_size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
shader->uDesktopPos = egl_shader_get_uniform_location(shader->shader, "position" );
|
||||
shader->uDesktopSize = egl_shader_get_uniform_location(shader->shader, "size" );
|
||||
shader->uRotate = egl_shader_get_uniform_location(shader->shader, "rotate" );
|
||||
shader->uScaleAlgo = egl_shader_get_uniform_location(shader->shader, "scaleAlgo");
|
||||
shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" );
|
||||
shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" );
|
||||
shader->uCBMode = egl_shader_get_uniform_location(shader->shader, "cbMode" );
|
||||
shader->uTransform = egl_shaderGetUniform(shader->shader, "transform" );
|
||||
shader->uDesktopSize = egl_shaderGetUniform(shader->shader, "desktopSize");
|
||||
shader->uScaleAlgo = egl_shaderGetUniform(shader->shader, "scaleAlgo" );
|
||||
shader->uNVGain = egl_shaderGetUniform(shader->shader, "nvGain" );
|
||||
shader->uCBMode = egl_shaderGetUniform(shader->shader, "cbMode" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
|
||||
bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display,
|
||||
bool useDMA, int maxRects)
|
||||
{
|
||||
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
|
||||
if (!*desktop)
|
||||
EGL_Desktop * desktop = calloc(1, sizeof(EGL_Desktop));
|
||||
if (!desktop)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Desktop");
|
||||
return false;
|
||||
}
|
||||
*desktop_ = desktop;
|
||||
|
||||
memset(*desktop, 0, sizeof(EGL_Desktop));
|
||||
(*desktop)->display = display;
|
||||
desktop->egl = egl;
|
||||
desktop->display = display;
|
||||
|
||||
if (!egl_texture_init(&(*desktop)->texture, display))
|
||||
if (!egl_textureInit(&desktop->texture, display,
|
||||
useDMA ? EGL_TEXTYPE_DMABUF : EGL_TEXTYPE_FRAMEBUFFER))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_init_desktop_shader(
|
||||
&(*desktop)->shader_generic,
|
||||
if (!egl_initDesktopShader(
|
||||
&desktop->shader,
|
||||
b_shader_desktop_vert , b_shader_desktop_vert_size,
|
||||
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size))
|
||||
{
|
||||
@@ -131,27 +144,41 @@ bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_model_init(&(*desktop)->model))
|
||||
if (!egl_desktopRectsInit(&desktop->mesh, maxRects))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop model");
|
||||
DEBUG_ERROR("Failed to initialize the desktop mesh");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*desktop)->model);
|
||||
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
|
||||
desktop->matrix = countedBufferNew(6 * sizeof(GLfloat));
|
||||
if (!desktop->matrix)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate the desktop matrix buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
app_registerKeybind(KEY_N, egl_desktop_toggle_nv, *desktop, "Toggle night vision mode");
|
||||
app_registerKeybind(KEY_S, egl_desktop_toggle_scale_algo, *desktop, "Toggle scale algorithm");
|
||||
app_registerKeybind(0, 'N', toggleNV, desktop,
|
||||
"Toggle night vision mode");
|
||||
|
||||
(*desktop)->nvMax = option_get_int("egl", "nvGainMax");
|
||||
(*desktop)->nvGain = option_get_int("egl", "nvGain" );
|
||||
(*desktop)->cbMode = option_get_int("egl", "cbMode" );
|
||||
(*desktop)->scaleAlgo = option_get_int("egl", "scale" );
|
||||
desktop->nvMax = option_get_int("egl", "nvGainMax");
|
||||
desktop->nvGain = option_get_int("egl", "nvGain" );
|
||||
desktop->cbMode = option_get_int("egl", "cbMode" );
|
||||
desktop->scaleAlgo = option_get_int("egl", "scale" );
|
||||
desktop->useDMA = useDMA;
|
||||
|
||||
if (!egl_postProcessInit(&desktop->pp))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the post process manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_postProcessAdd(desktop->pp, &egl_filterDownscaleOps);
|
||||
egl_postProcessAdd(desktop->pp, &egl_filterFFXCASOps );
|
||||
egl_postProcessAdd(desktop->pp, &egl_filterFFXFSR1Ops );
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_desktop_toggle_nv(int key, void * opaque)
|
||||
void toggleNV(int key, void * opaque)
|
||||
{
|
||||
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
|
||||
if (desktop->nvGain++ == desktop->nvMax)
|
||||
@@ -160,24 +187,11 @@ void egl_desktop_toggle_nv(int key, void * opaque)
|
||||
if (desktop->nvGain == 0) app_alert(LG_ALERT_INFO, "NV Disabled");
|
||||
else if (desktop->nvGain == 1) app_alert(LG_ALERT_INFO, "NV Enabled");
|
||||
else app_alert(LG_ALERT_INFO, "NV Gain + %d", desktop->nvGain - 1);
|
||||
|
||||
app_invalidateWindow(true);
|
||||
}
|
||||
|
||||
static const char * egl_desktop_scale_algo_name(int algorithm)
|
||||
{
|
||||
switch (algorithm)
|
||||
{
|
||||
case EGL_SCALE_AUTO:
|
||||
return "Automatic (downscale: linear, upscale: nearest)";
|
||||
case EGL_SCALE_NEAREST:
|
||||
return "Nearest";
|
||||
case EGL_SCALE_LINEAR:
|
||||
return "Linear";
|
||||
default:
|
||||
return "(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
bool egl_desktop_scale_validate(struct Option * opt, const char ** error)
|
||||
bool egl_desktopScaleValidate(struct Option * opt, const char ** error)
|
||||
{
|
||||
if (opt->value.x_int >= 0 && opt->value.x_int < EGL_SCALE_MAX)
|
||||
return true;
|
||||
@@ -186,52 +200,84 @@ bool egl_desktop_scale_validate(struct Option * opt, const char ** error)
|
||||
return false;
|
||||
}
|
||||
|
||||
void egl_desktop_toggle_scale_algo(int key, void * opaque)
|
||||
{
|
||||
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
|
||||
if (++desktop->scaleAlgo == EGL_SCALE_MAX)
|
||||
desktop->scaleAlgo = 0;
|
||||
|
||||
app_alert(LG_ALERT_INFO, "Scale Algorithm %d: %s", desktop->scaleAlgo,
|
||||
egl_desktop_scale_algo_name(desktop->scaleAlgo));
|
||||
}
|
||||
|
||||
void egl_desktop_free(EGL_Desktop ** desktop)
|
||||
void egl_desktopFree(EGL_Desktop ** desktop)
|
||||
{
|
||||
if (!*desktop)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*desktop)->texture );
|
||||
egl_shader_free (&(*desktop)->shader_generic.shader);
|
||||
egl_model_free (&(*desktop)->model );
|
||||
egl_textureFree (&(*desktop)->texture );
|
||||
egl_textureFree (&(*desktop)->spiceTexture );
|
||||
egl_shaderFree (&(*desktop)->shader.shader);
|
||||
egl_desktopRectsFree(&(*desktop)->mesh );
|
||||
countedBufferRelease(&(*desktop)->matrix );
|
||||
|
||||
egl_postProcessFree(&(*desktop)->pp);
|
||||
|
||||
free(*desktop);
|
||||
*desktop = NULL;
|
||||
}
|
||||
|
||||
bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA)
|
||||
static const char * algorithmNames[EGL_SCALE_MAX] = {
|
||||
[EGL_SCALE_AUTO] = "Automatic (downscale: linear, upscale: nearest)",
|
||||
[EGL_SCALE_NEAREST] = "Nearest",
|
||||
[EGL_SCALE_LINEAR] = "Linear",
|
||||
};
|
||||
|
||||
void egl_desktopConfigUI(EGL_Desktop * desktop)
|
||||
{
|
||||
igText("Scale algorithm:");
|
||||
igPushItemWidth(igGetWindowWidth() - igGetStyle()->WindowPadding.x * 2);
|
||||
if (igBeginCombo("##scale", algorithmNames[desktop->scaleAlgo], 0))
|
||||
{
|
||||
for (int i = 0; i < EGL_SCALE_MAX; ++i)
|
||||
{
|
||||
bool selected = i == desktop->scaleAlgo;
|
||||
if (igSelectable_Bool(algorithmNames[i], selected, 0,
|
||||
(ImVec2) { 0.0f, 0.0f }))
|
||||
desktop->scaleAlgo = i;
|
||||
if (selected)
|
||||
igSetItemDefaultFocus();
|
||||
}
|
||||
igEndCombo();
|
||||
}
|
||||
igPopItemWidth();
|
||||
|
||||
igText("Night vision mode:");
|
||||
igSameLine(0.0f, -1.0f);
|
||||
igPushItemWidth(igGetWindowWidth() - igGetCursorPosX() - igGetStyle()->WindowPadding.x);
|
||||
|
||||
const char * format;
|
||||
switch (desktop->nvGain)
|
||||
{
|
||||
case 0: format = "off"; break;
|
||||
case 1: format = "on"; break;
|
||||
default: format = "gain: %d";
|
||||
}
|
||||
igSliderInt("##nvgain", &desktop->nvGain, 0, desktop->nvMax, format, 0);
|
||||
igPopItemWidth();
|
||||
}
|
||||
|
||||
bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
|
||||
{
|
||||
memcpy(&desktop->format, &format, sizeof(LG_RendererFormat));
|
||||
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
switch(format.type)
|
||||
{
|
||||
case FRAME_TYPE_BGRA:
|
||||
pixFmt = EGL_PF_BGRA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA:
|
||||
pixFmt = EGL_PF_RGBA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA10:
|
||||
pixFmt = EGL_PF_RGBA10;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA16F:
|
||||
pixFmt = EGL_PF_RGBA16F;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -239,17 +285,15 @@ bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bo
|
||||
return false;
|
||||
}
|
||||
|
||||
desktop->width = format.width;
|
||||
desktop->height = format.height;
|
||||
desktop->width = format.frameWidth;
|
||||
desktop->height = format.frameHeight;
|
||||
|
||||
if (!egl_texture_setup(
|
||||
if (!egl_textureSetup(
|
||||
desktop->texture,
|
||||
pixFmt,
|
||||
format.width,
|
||||
format.height,
|
||||
format.pitch,
|
||||
true, // streaming texture
|
||||
useDMA
|
||||
format.frameWidth,
|
||||
format.frameHeight,
|
||||
format.pitch
|
||||
))
|
||||
{
|
||||
DEBUG_ERROR("Failed to setup the desktop texture");
|
||||
@@ -259,48 +303,128 @@ bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bo
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd)
|
||||
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd,
|
||||
const FrameDamageRect * damageRects, int damageRectsCount)
|
||||
{
|
||||
if (dmaFd >= 0)
|
||||
if (desktop->useDMA && dmaFd >= 0)
|
||||
{
|
||||
if (!egl_texture_update_from_dma(desktop->texture, frame, dmaFd))
|
||||
if (egl_textureUpdateFromDMA(desktop->texture, frame, dmaFd))
|
||||
{
|
||||
atomic_store(&desktop->processFrame, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
DEBUG_WARN("DMA update failed, disabling DMABUF imports");
|
||||
|
||||
const char * vendor = (const char *)glGetString(GL_VENDOR);
|
||||
if (strstr(vendor, "NVIDIA"))
|
||||
{
|
||||
DEBUG_WARN("NVIDIA's DMABUF support is incomplete, please direct your complaints to NVIDIA");
|
||||
DEBUG_WARN("This is not a bug in Looking Glass");
|
||||
}
|
||||
|
||||
desktop->useDMA = false;
|
||||
|
||||
const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS);
|
||||
if (!util_hasGLExt(gl_exts, "GL_EXT_buffer_storage"))
|
||||
{
|
||||
DEBUG_ERROR("GL_EXT_buffer_storage is needed to use EGL backend");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!egl_texture_update_from_frame(desktop->texture, frame))
|
||||
}
|
||||
|
||||
egl_textureFree(&desktop->texture);
|
||||
if (!egl_textureInit(&desktop->texture, desktop->display,
|
||||
EGL_TEXTYPE_FRAMEBUFFER))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_desktopSetup(desktop, desktop->format))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (egl_textureUpdateFromFrame(desktop->texture, frame,
|
||||
damageRects, damageRectsCount))
|
||||
{
|
||||
atomic_store(&desktop->processFrame, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void egl_desktopResize(EGL_Desktop * desktop, int width, int height)
|
||||
{
|
||||
atomic_store(&desktop->processFrame, true);
|
||||
}
|
||||
|
||||
bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
unsigned int outputHeight, const float x, const float y,
|
||||
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
||||
LG_RendererRotate rotate, const struct DamageRects * rects)
|
||||
{
|
||||
EGL_Texture * tex;
|
||||
int width, height;
|
||||
|
||||
if (desktop->useSpice)
|
||||
{
|
||||
tex = desktop->spiceTexture;
|
||||
width = desktop->spiceWidth;
|
||||
height = desktop->spiceHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
tex = desktop->texture;
|
||||
width = desktop->width;
|
||||
height = desktop->height;
|
||||
}
|
||||
|
||||
if (outputWidth == 0 && outputHeight == 0)
|
||||
DEBUG_FATAL("outputWidth || outputHeight == 0");
|
||||
|
||||
enum EGL_TexStatus status;
|
||||
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
|
||||
if ((status = egl_textureProcess(tex)) != EGL_TEX_STATUS_OK)
|
||||
{
|
||||
if (status != EGL_TEX_STATUS_NOTREADY)
|
||||
DEBUG_ERROR("Failed to process the desktop texture");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
|
||||
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
||||
LG_RendererRotate rotate)
|
||||
{
|
||||
if (!desktop->shader)
|
||||
return false;
|
||||
|
||||
int scaleAlgo = EGL_SCALE_NEAREST;
|
||||
|
||||
egl_desktopRectsMatrix((float *)desktop->matrix->data,
|
||||
width, height, x, y, scaleX, scaleY, rotate);
|
||||
egl_desktopRectsUpdate(desktop->mesh, rects, width, height);
|
||||
|
||||
if (atomic_exchange(&desktop->processFrame, false) ||
|
||||
egl_postProcessConfigModified(desktop->pp))
|
||||
egl_postProcessRun(desktop->pp, tex, desktop->mesh,
|
||||
width, height, outputWidth, outputHeight);
|
||||
|
||||
unsigned int finalSizeX, finalSizeY;
|
||||
GLuint texture = egl_postProcessGetOutput(desktop->pp,
|
||||
&finalSizeX, &finalSizeY);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
egl_resetViewport(desktop->egl);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glBindSampler(0, tex->sampler);
|
||||
|
||||
if (finalSizeX > width || finalSizeY > height)
|
||||
scaleType = EGL_DESKTOP_DOWNSCALE;
|
||||
|
||||
switch (desktop->scaleAlgo)
|
||||
{
|
||||
case EGL_SCALE_AUTO:
|
||||
switch (scaleType)
|
||||
{
|
||||
case EGL_DESKTOP_NOSCALE:
|
||||
case EGL_DESKTOP_UPSCALE:
|
||||
scaleAlgo = EGL_SCALE_NEAREST;
|
||||
break;
|
||||
|
||||
case EGL_DESKTOP_NOSCALE:
|
||||
case EGL_DESKTOP_DOWNSCALE:
|
||||
scaleAlgo = EGL_SCALE_LINEAR;
|
||||
break;
|
||||
@@ -311,22 +435,96 @@ bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
|
||||
scaleAlgo = desktop->scaleAlgo;
|
||||
}
|
||||
|
||||
const struct DesktopShader * shader = desktop->shader;
|
||||
egl_shader_use(shader->shader);
|
||||
glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY);
|
||||
glUniform1i(shader->uRotate , rotate);
|
||||
glUniform1i(shader->uScaleAlgo , scaleAlgo);
|
||||
glUniform2f(shader->uDesktopSize, desktop->width, desktop->height);
|
||||
|
||||
if (desktop->nvGain)
|
||||
const struct DesktopShader * shader = &desktop->shader;
|
||||
EGL_Uniform uniforms[] =
|
||||
{
|
||||
glUniform1i(shader->uNV, 1);
|
||||
glUniform1f(shader->uNVGain, (float)desktop->nvGain);
|
||||
}
|
||||
else
|
||||
glUniform1i(shader->uNV, 0);
|
||||
{
|
||||
.type = EGL_UNIFORM_TYPE_1I,
|
||||
.location = shader->uScaleAlgo,
|
||||
.i = { scaleAlgo },
|
||||
},
|
||||
{
|
||||
.type = EGL_UNIFORM_TYPE_2F,
|
||||
.location = shader->uDesktopSize,
|
||||
.f = { width, height },
|
||||
},
|
||||
{
|
||||
.type = EGL_UNIFORM_TYPE_M3x2FV,
|
||||
.location = shader->uTransform,
|
||||
.m.transpose = GL_FALSE,
|
||||
.m.v = desktop->matrix
|
||||
},
|
||||
{
|
||||
.type = EGL_UNIFORM_TYPE_1F,
|
||||
.location = shader->uNVGain,
|
||||
.f = { (float)desktop->nvGain }
|
||||
},
|
||||
{
|
||||
.type = EGL_UNIFORM_TYPE_1I,
|
||||
.location = shader->uCBMode,
|
||||
.f = { desktop->cbMode }
|
||||
}
|
||||
};
|
||||
|
||||
glUniform1i(shader->uCBMode, desktop->cbMode);
|
||||
egl_model_render(desktop->model);
|
||||
egl_shaderSetUniforms(shader->shader, uniforms, ARRAY_LENGTH(uniforms));
|
||||
egl_shaderUse(shader->shader);
|
||||
egl_desktopRectsRender(desktop->mesh);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height)
|
||||
{
|
||||
if (!desktop->spiceTexture)
|
||||
if (!egl_textureInit(&desktop->spiceTexture, desktop->display,
|
||||
EGL_TEXTYPE_BUFFER_MAP))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the spice desktop texture");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!egl_textureSetup(
|
||||
desktop->spiceTexture,
|
||||
EGL_PF_BGRA,
|
||||
width,
|
||||
height,
|
||||
width * 4
|
||||
))
|
||||
{
|
||||
DEBUG_ERROR("Failed to setup the spice desktop texture");
|
||||
return;
|
||||
}
|
||||
|
||||
desktop->spiceWidth = width;
|
||||
desktop->spiceHeight = height;
|
||||
}
|
||||
|
||||
void egl_desktopSpiceDrawFill(EGL_Desktop * desktop, int x, int y, int width,
|
||||
int height, uint32_t color)
|
||||
{
|
||||
/* this is a fairly hacky way to do this, but since it's only for the fallback
|
||||
* spice display it's not really an issue */
|
||||
|
||||
uint32_t line[width];
|
||||
for(int x = 0; x < width; ++x)
|
||||
line[x] = color;
|
||||
|
||||
for(; y < height; ++y)
|
||||
egl_textureUpdateRect(desktop->spiceTexture,
|
||||
x, y, width, 1, sizeof(line), (uint8_t *)line, false);
|
||||
|
||||
atomic_store(&desktop->processFrame, true);
|
||||
}
|
||||
|
||||
void egl_desktopSpiceDrawBitmap(EGL_Desktop * desktop, int x, int y, int width,
|
||||
int height, int stride, uint8_t * data, bool topDown)
|
||||
{
|
||||
egl_textureUpdateRect(desktop->spiceTexture,
|
||||
x, y, width, height, stride, data, topDown);
|
||||
atomic_store(&desktop->processFrame, true);
|
||||
}
|
||||
|
||||
void egl_desktopSpiceShow(EGL_Desktop * desktop, bool show)
|
||||
{
|
||||
desktop->useSpice = show;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -22,7 +22,8 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "interface/renderer.h"
|
||||
#include "egl.h"
|
||||
#include "desktop_rects.h"
|
||||
|
||||
typedef struct EGL_Desktop EGL_Desktop;
|
||||
|
||||
@@ -34,13 +35,25 @@ enum EGL_DesktopScaleType
|
||||
};
|
||||
|
||||
struct Option;
|
||||
bool egl_desktop_scale_validate(struct Option * opt, const char ** error);
|
||||
bool egl_desktopScaleValidate(struct Option * opt, const char ** error);
|
||||
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display);
|
||||
void egl_desktop_free(EGL_Desktop ** desktop);
|
||||
bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop, EGLDisplay * display,
|
||||
bool useDMA, int maxRects);
|
||||
void egl_desktopFree(EGL_Desktop ** desktop);
|
||||
|
||||
bool egl_desktop_setup (EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA);
|
||||
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd);
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
|
||||
void egl_desktopConfigUI(EGL_Desktop * desktop);
|
||||
bool egl_desktopSetup (EGL_Desktop * desktop, const LG_RendererFormat format);
|
||||
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd,
|
||||
const FrameDamageRect * damageRects, int damageRectsCount);
|
||||
void egl_desktopResize(EGL_Desktop * desktop, int width, int height);
|
||||
bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
unsigned int outputHeight, const float x, const float y,
|
||||
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
||||
LG_RendererRotate rotate);
|
||||
LG_RendererRotate rotate, const struct DamageRects * rects);
|
||||
|
||||
void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height);
|
||||
void egl_desktopSpiceDrawFill(EGL_Desktop * desktop, int x, int y, int width,
|
||||
int height, uint32_t color);
|
||||
void egl_desktopSpiceDrawBitmap(EGL_Desktop * desktop, int x, int y, int width,
|
||||
int height, int stride, uint8_t * data, bool topDown);
|
||||
void egl_desktopSpiceShow(EGL_Desktop * desktop, bool show);
|
||||
|
||||
305
client/renderers/EGL/desktop_rects.c
Normal file
305
client/renderers/EGL/desktop_rects.c
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "desktop_rects.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <GLES3/gl3.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
struct EGL_DesktopRects
|
||||
{
|
||||
GLfloat * lastVertices;
|
||||
int lastVerticesCount;
|
||||
int lastVerticesSize;
|
||||
|
||||
GLuint buffers[2];
|
||||
GLuint vao;
|
||||
int count;
|
||||
int maxCount;
|
||||
};
|
||||
|
||||
bool egl_desktopRectsInit(EGL_DesktopRects ** rects_, int maxCount)
|
||||
{
|
||||
EGL_DesktopRects * rects = malloc(sizeof(*rects));
|
||||
if (!rects)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_DesktopRects");
|
||||
return false;
|
||||
}
|
||||
*rects_ = rects;
|
||||
memset(rects, 0, sizeof(*rects));
|
||||
|
||||
glGenVertexArrays(1, &rects->vao);
|
||||
glBindVertexArray(rects->vao);
|
||||
|
||||
glGenBuffers(2, rects->buffers);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]);
|
||||
glBufferData(GL_ARRAY_BUFFER, maxCount * 8 * sizeof(GLfloat), NULL, GL_STREAM_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
GLushort indices[maxCount * 6];
|
||||
for (int i = 0; i < maxCount; ++i)
|
||||
{
|
||||
indices[6 * i + 0] = 4 * i + 0;
|
||||
indices[6 * i + 1] = 4 * i + 1;
|
||||
indices[6 * i + 2] = 4 * i + 2;
|
||||
indices[6 * i + 3] = 4 * i + 0;
|
||||
indices[6 * i + 4] = 4 * i + 2;
|
||||
indices[6 * i + 5] = 4 * i + 3;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, rects->buffers[1]);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof indices, indices, GL_STATIC_DRAW);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
rects->count = 0;
|
||||
rects->maxCount = maxCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_desktopRectsFree(EGL_DesktopRects ** rects_)
|
||||
{
|
||||
EGL_DesktopRects * rects = *rects_;
|
||||
if (!rects)
|
||||
return;
|
||||
|
||||
glDeleteVertexArrays(1, &rects->vao);
|
||||
glDeleteBuffers(2, rects->buffers);
|
||||
free(rects->lastVertices);
|
||||
free(rects);
|
||||
*rects_ = NULL;
|
||||
}
|
||||
|
||||
inline static void rectToVertices(GLfloat * vertex, const FrameDamageRect * rect)
|
||||
{
|
||||
vertex[0] = rect->x;
|
||||
vertex[1] = rect->y;
|
||||
vertex[2] = rect->x + rect->width;
|
||||
vertex[3] = rect->y;
|
||||
vertex[4] = rect->x + rect->width;
|
||||
vertex[5] = rect->y + rect->height;
|
||||
vertex[6] = rect->x;
|
||||
vertex[7] = rect->y + rect->height;
|
||||
}
|
||||
|
||||
void egl_desktopRectsUpdate(EGL_DesktopRects * rects, const struct DamageRects * data,
|
||||
int width, int height)
|
||||
{
|
||||
if (data && data->count == 0)
|
||||
{
|
||||
rects->count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const int count = (!data || data->count < 0 ? 1 : data->count) * 8;
|
||||
GLfloat vertices[count];
|
||||
if (!data || data->count < 0)
|
||||
{
|
||||
FrameDamageRect full = {
|
||||
.x = 0, .y = 0, .width = width, .height = height,
|
||||
};
|
||||
rects->count = 1;
|
||||
rectToVertices(vertices, &full);
|
||||
}
|
||||
else
|
||||
{
|
||||
rects->count = data->count;
|
||||
DEBUG_ASSERT(rects->count <= rects->maxCount);
|
||||
|
||||
for (int i = 0; i < rects->count; ++i)
|
||||
rectToVertices(vertices + i * 8, data->rects + i);
|
||||
}
|
||||
|
||||
// check if the value actually changed and needs updating
|
||||
if (count == rects->lastVerticesCount &&
|
||||
memcmp(rects->lastVertices, vertices, sizeof(GLfloat) * count) == 0)
|
||||
return;
|
||||
|
||||
// ensure the local storage is large enough
|
||||
if (count > rects->lastVerticesSize)
|
||||
{
|
||||
if (rects->lastVertices)
|
||||
free(rects->lastVertices);
|
||||
|
||||
rects->lastVertices = malloc(sizeof(GLfloat) * count);
|
||||
if (!rects->lastVertices)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
rects->lastVerticesSize = count;
|
||||
}
|
||||
|
||||
// copy the last value for later comparison
|
||||
rects->lastVerticesCount = count;
|
||||
memcpy(rects->lastVertices, vertices, sizeof(GLfloat) * count);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, rects->count * 8 * sizeof(GLfloat), vertices);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
static void desktopToGLSpace(double matrix[6], int width, int height, double translateX,
|
||||
double translateY, double scaleX, double scaleY, LG_RendererRotate rotate)
|
||||
{
|
||||
switch (rotate)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
matrix[0] = 2.0 * scaleX / width;
|
||||
matrix[1] = 0.0;
|
||||
matrix[2] = 0.0;
|
||||
matrix[3] = -2.0 * scaleY / height;
|
||||
matrix[4] = translateX - scaleX;
|
||||
matrix[5] = translateY + scaleY;
|
||||
return;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
matrix[0] = 0.0;
|
||||
matrix[1] = -2.0 * scaleY / width;
|
||||
matrix[2] = -2.0 * scaleX / height;
|
||||
matrix[3] = 0.0;
|
||||
matrix[4] = translateX + scaleX;
|
||||
matrix[5] = translateY + scaleY;
|
||||
return;
|
||||
|
||||
case LG_ROTATE_180:
|
||||
matrix[0] = -2.0 * scaleX / width;
|
||||
matrix[1] = 0.0;
|
||||
matrix[2] = 0.0;
|
||||
matrix[3] = 2.0 * scaleY / height;
|
||||
matrix[4] = translateX + scaleX;
|
||||
matrix[5] = translateY - scaleY;
|
||||
return;
|
||||
|
||||
case LG_ROTATE_270:
|
||||
matrix[0] = 0.0;
|
||||
matrix[1] = 2.0 * scaleY / width;
|
||||
matrix[2] = 2.0 * scaleX / height;
|
||||
matrix[3] = 0.0;
|
||||
matrix[4] = translateX - scaleX;
|
||||
matrix[5] = translateY - scaleY;
|
||||
}
|
||||
}
|
||||
|
||||
void egl_desktopRectsMatrix(float matrix[6], int width, int height, float translateX,
|
||||
float translateY, float scaleX, float scaleY, LG_RendererRotate rotate)
|
||||
{
|
||||
double temp[6];
|
||||
desktopToGLSpace(temp, width, height, translateX, translateY, scaleX, scaleY, rotate);
|
||||
for (int i = 0; i < 6; ++i)
|
||||
matrix[i] = temp[i];
|
||||
}
|
||||
|
||||
void egl_desktopToScreenMatrix(double matrix[6], int frameWidth, int frameHeight,
|
||||
double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate,
|
||||
double windowWidth, double windowHeight)
|
||||
{
|
||||
desktopToGLSpace(matrix, frameWidth, frameHeight, translateX, translateY, scaleX, scaleY, rotate);
|
||||
|
||||
double hw = windowWidth / 2;
|
||||
double hh = windowHeight / 2;
|
||||
matrix[0] *= hw;
|
||||
matrix[1] *= hh;
|
||||
matrix[2] *= hw;
|
||||
matrix[3] *= hh;
|
||||
matrix[4] = matrix[4] * hw + hw;
|
||||
matrix[5] = matrix[5] * hh + hh;
|
||||
}
|
||||
|
||||
inline static void matrixMultiply(const double matrix[6], double * nx, double * ny, double x, double y)
|
||||
{
|
||||
*nx = matrix[0] * x + matrix[2] * y + matrix[4];
|
||||
*ny = matrix[1] * x + matrix[3] * y + matrix[5];
|
||||
}
|
||||
|
||||
struct Rect egl_desktopToScreen(const double matrix[6], const struct FrameDamageRect * rect)
|
||||
{
|
||||
double x1, y1, x2, y2;
|
||||
matrixMultiply(matrix, &x1, &y1, rect->x, rect->y);
|
||||
matrixMultiply(matrix, &x2, &y2, rect->x + rect->width, rect->y + rect->height);
|
||||
|
||||
int x3 = min(x1, x2);
|
||||
int y3 = min(y1, y2);
|
||||
return (struct Rect) {
|
||||
.x = x3,
|
||||
.y = y3,
|
||||
.w = ceil(max(x1, x2)) - x3,
|
||||
.h = ceil(max(y1, y2)) - y3,
|
||||
};
|
||||
}
|
||||
|
||||
void egl_screenToDesktopMatrix(double matrix[6], int frameWidth, int frameHeight,
|
||||
double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate,
|
||||
double windowWidth, double windowHeight)
|
||||
{
|
||||
double inverted[6] = {0};
|
||||
egl_desktopToScreenMatrix(inverted, frameWidth, frameHeight, translateX, translateY,
|
||||
scaleX, scaleY, rotate, windowWidth, windowHeight);
|
||||
|
||||
double det = inverted[0] * inverted[3] - inverted[1] * inverted[2];
|
||||
matrix[0] = inverted[3] / det;
|
||||
matrix[1] = -inverted[1] / det;
|
||||
matrix[2] = -inverted[2] / det;
|
||||
matrix[3] = inverted[0] / det;
|
||||
matrix[4] = (inverted[2] * inverted[5] - inverted[3] * inverted[4]) / det;
|
||||
matrix[5] = (inverted[1] * inverted[4] - inverted[0] * inverted[5]) / det;
|
||||
}
|
||||
|
||||
bool egl_screenToDesktop(struct FrameDamageRect * output, const double matrix[6],
|
||||
const struct Rect * rect, int width, int height)
|
||||
{
|
||||
double x1, y1, x2, y2;
|
||||
matrixMultiply(matrix, &x1, &y1, rect->x - 1, rect->y - 1);
|
||||
matrixMultiply(matrix, &x2, &y2, rect->x + rect->w + 1, rect->y + rect->h + 1);
|
||||
|
||||
int x3 = min(x1, x2);
|
||||
int y3 = min(y1, y2);
|
||||
int x4 = ceil(max(x1, x2));
|
||||
int y4 = ceil(max(y1, y2));
|
||||
|
||||
if (x4 < 0 || y4 < 0 || x3 >= width || y3 >= height)
|
||||
return false;
|
||||
|
||||
output->x = max(x3, 0);
|
||||
output->y = max(y3, 0);
|
||||
output->width = min(width, x4) - output->x;
|
||||
output->height = min(height, y4) - output->y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_desktopRectsRender(EGL_DesktopRects * rects)
|
||||
{
|
||||
if (!rects->count)
|
||||
return;
|
||||
|
||||
glBindVertexArray(rects->vao);
|
||||
glDrawElements(GL_TRIANGLES, 6 * rects->count, GL_UNSIGNED_SHORT, NULL);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
53
client/renderers/EGL/desktop_rects.h
Normal file
53
client/renderers/EGL/desktop_rects.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "common/types.h"
|
||||
#include "interface/renderer.h"
|
||||
|
||||
struct DamageRects
|
||||
{
|
||||
int count;
|
||||
FrameDamageRect rects[];
|
||||
};
|
||||
|
||||
typedef struct EGL_DesktopRects EGL_DesktopRects;
|
||||
|
||||
bool egl_desktopRectsInit(EGL_DesktopRects ** rects, int maxCount);
|
||||
void egl_desktopRectsFree(EGL_DesktopRects ** rects);
|
||||
|
||||
void egl_desktopRectsMatrix(float matrix[6], int width, int height, float translateX,
|
||||
float translateY, float scaleX, float scaleY, LG_RendererRotate rotate);
|
||||
void egl_desktopToScreenMatrix(double matrix[6], int frameWidth, int frameHeight,
|
||||
double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate,
|
||||
double windowWidth, double windowHeight);
|
||||
struct Rect egl_desktopToScreen(const double matrix[6], const struct FrameDamageRect * rect);
|
||||
|
||||
void egl_screenToDesktopMatrix(double matrix[6], int frameWidth, int frameHeight,
|
||||
double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate,
|
||||
double windowWidth, double windowHeight);
|
||||
bool egl_screenToDesktop(struct FrameDamageRect * output, const double matrix[6],
|
||||
const struct Rect * rect, int width, int height);
|
||||
|
||||
void egl_desktopRectsUpdate(EGL_DesktopRects * rects, const struct DamageRects * data,
|
||||
int width, int height);
|
||||
void egl_desktopRectsRender(EGL_DesktopRects * rects);
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "draw.h"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
void egl_draw_torus(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer)
|
||||
{
|
||||
GLfloat * v = (GLfloat *)malloc(sizeof(GLfloat) * (pts + 1) * 6);
|
||||
GLfloat * dst = v;
|
||||
|
||||
for(unsigned int i = 0; i <= pts; ++i)
|
||||
{
|
||||
const float angle = (i / (float)pts) * M_PI * 2.0f;
|
||||
const float c = cos(angle);
|
||||
const float s = sin(angle);
|
||||
*dst = x + (inner * c); ++dst;
|
||||
*dst = y + (inner * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
*dst = x + (outer * c); ++dst;
|
||||
*dst = y + (outer * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
}
|
||||
|
||||
egl_model_add_verticies(model, v, NULL, (pts + 1) * 2);
|
||||
free(v);
|
||||
}
|
||||
|
||||
void egl_draw_torus_arc(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer, float s, float e)
|
||||
{
|
||||
GLfloat * v = (GLfloat *)malloc(sizeof(GLfloat) * (pts + 1) * 6);
|
||||
GLfloat * dst = v;
|
||||
|
||||
for(unsigned int i = 0; i <= pts; ++i)
|
||||
{
|
||||
const float angle = s + ((i / (float)pts) * e);
|
||||
const float c = cos(angle);
|
||||
const float s = sin(angle);
|
||||
*dst = x + (inner * c); ++dst;
|
||||
*dst = y + (inner * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
*dst = x + (outer * c); ++dst;
|
||||
*dst = y + (outer * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
}
|
||||
|
||||
egl_model_add_verticies(model, v, NULL, (pts + 1) * 2);
|
||||
free(v);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -20,7 +20,5 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "model.h"
|
||||
|
||||
void egl_draw_torus (EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer);
|
||||
void egl_draw_torus_arc(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer, float s, float e);
|
||||
typedef struct Inst EGL;
|
||||
void egl_resetViewport(EGL * egl);
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "egldebug.h"
|
||||
#include <GL/gl.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
const char * egl_getErrorStr(void)
|
||||
@@ -44,3 +44,17 @@ const char * egl_getErrorStr(void)
|
||||
default : return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
const char * gl_getErrorStr(void)
|
||||
{
|
||||
switch (glGetError())
|
||||
{
|
||||
case GL_NO_ERROR : return "GL_NO_ERROR";
|
||||
case GL_INVALID_ENUM : return "GL_INVALID_ENUM";
|
||||
case GL_INVALID_VALUE : return "GL_INVALID_VALUE";
|
||||
case GL_INVALID_OPERATION : return "GL_INVALID_OPERATION";
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
case GL_OUT_OF_MEMORY : return "GL_OUT_OF_MEMORY";
|
||||
default : return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -21,9 +21,16 @@
|
||||
#include "common/debug.h"
|
||||
|
||||
const char * egl_getErrorStr(void);
|
||||
const char * gl_getErrorStr(void);
|
||||
|
||||
#define DEBUG_EGL_WARN(fmt, ...) \
|
||||
DEBUG_WARN(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr())
|
||||
|
||||
#define DEBUG_EGL_ERROR(fmt, ...) \
|
||||
DEBUG_ERROR(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr())
|
||||
|
||||
#define DEBUG_GL_WARN(fmt, ...) \
|
||||
DEBUG_WARN(fmt " (%s)", ##__VA_ARGS__, gl_getErrorStr())
|
||||
|
||||
#define DEBUG_GL_ERROR(fmt, ...) \
|
||||
DEBUG_ERROR(fmt " (%s)", ##__VA_ARGS__, gl_getErrorStr())
|
||||
|
||||
74
client/renderers/EGL/egltypes.h
Normal file
74
client/renderers/EGL/egltypes.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum EGL_TexType
|
||||
{
|
||||
EGL_TEXTYPE_BUFFER,
|
||||
EGL_TEXTYPE_BUFFER_MAP,
|
||||
EGL_TEXTYPE_BUFFER_STREAM,
|
||||
EGL_TEXTYPE_FRAMEBUFFER,
|
||||
EGL_TEXTYPE_DMABUF
|
||||
}
|
||||
EGL_TexType;
|
||||
|
||||
typedef enum EGL_PixelFormat
|
||||
{
|
||||
EGL_PF_RGBA,
|
||||
EGL_PF_BGRA,
|
||||
EGL_PF_RGBA10,
|
||||
EGL_PF_RGBA16F
|
||||
}
|
||||
EGL_PixelFormat;
|
||||
|
||||
typedef enum EGL_TexStatus
|
||||
{
|
||||
EGL_TEX_STATUS_NOTREADY,
|
||||
EGL_TEX_STATUS_OK,
|
||||
EGL_TEX_STATUS_ERROR
|
||||
}
|
||||
EGL_TexStatus;
|
||||
|
||||
typedef struct EGL_TexSetup
|
||||
{
|
||||
/* the pixel format of the texture */
|
||||
EGL_PixelFormat pixFmt;
|
||||
|
||||
/* the width of the texture in pixels */
|
||||
size_t width;
|
||||
|
||||
/* the height of the texture in pixels */
|
||||
size_t height;
|
||||
|
||||
/* the stide of the texture in bytes */
|
||||
size_t stride;
|
||||
}
|
||||
EGL_TexSetup;
|
||||
|
||||
typedef enum EGL_FilterType
|
||||
{
|
||||
EGL_FILTER_TYPE_EFFECT,
|
||||
EGL_FILTER_TYPE_UPSCALE,
|
||||
EGL_FILTER_TYPE_DOWNSCALE
|
||||
}
|
||||
EGL_FilterType;
|
||||
49
client/renderers/EGL/ffx.c
Normal file
49
client/renderers/EGL/ffx.c
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "ffx.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#define A_CPU
|
||||
#define A_RESTRICT
|
||||
#define A_STATIC inline static
|
||||
#include "shader/ffx_a.h"
|
||||
#include "shader/ffx_cas.h"
|
||||
#include "shader/ffx_fsr1.h"
|
||||
|
||||
void ffxCasConst(uint32_t consts[8], float sharpness, float inputX, float inputY,
|
||||
float outputX, float outputY)
|
||||
{
|
||||
CasSetup(consts + 0, consts + 4, sharpness, inputX, inputY, outputX, outputY);
|
||||
}
|
||||
|
||||
void ffxFsrEasuConst(uint32_t consts[16], float viewportX, float viewportY,
|
||||
float inputX, float inputY, float outputX, float outputY)
|
||||
{
|
||||
FsrEasuCon(consts + 0, consts + 4, consts + 8, consts + 12, viewportX, viewportY,
|
||||
inputX, inputY, outputX, outputY);
|
||||
}
|
||||
|
||||
void ffxFsrRcasConst(uint32_t consts[4], float sharpness)
|
||||
{
|
||||
FsrRcasCon(consts, sharpness);
|
||||
}
|
||||
28
client/renderers/EGL/ffx.h
Normal file
28
client/renderers/EGL/ffx.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
void ffxCasConst(uint32_t consts[8], float sharpness, float inputX, float inputY,
|
||||
float outputX, float outputY);
|
||||
void ffxFsrEasuConst(uint32_t consts[16], float viewportX, float viewportY,
|
||||
float inputX, float inputY, float outputX, float outputY);
|
||||
void ffxFsrRcasConst(uint32_t consts[4], float sharpness);
|
||||
30
client/renderers/EGL/filter.c
Normal file
30
client/renderers/EGL/filter.c
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "filter.h"
|
||||
|
||||
void egl_filterRectsRender(EGL_Shader * shader, EGL_FilterRects * rects)
|
||||
{
|
||||
glUniformMatrix3x2fv(egl_shaderGetUniform(shader, "transform"),
|
||||
1, GL_FALSE, rects->matrix);
|
||||
glUniform2f(egl_shaderGetUniform(shader, "desktopSize"),
|
||||
rects->width, rects->height);
|
||||
egl_desktopRectsRender(rects->rects);
|
||||
}
|
||||
171
client/renderers/EGL/filter.h
Normal file
171
client/renderers/EGL/filter.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util.h"
|
||||
#include "shader.h"
|
||||
#include "egltypes.h"
|
||||
#include "desktop_rects.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct EGL_FilterRects
|
||||
{
|
||||
EGL_DesktopRects * rects;
|
||||
GLfloat * matrix;
|
||||
int width, height;
|
||||
}
|
||||
EGL_FilterRects;
|
||||
|
||||
typedef struct EGL_Filter EGL_Filter;
|
||||
|
||||
typedef struct EGL_FilterOps
|
||||
{
|
||||
/* the identifier of this filter */
|
||||
const char * id;
|
||||
|
||||
/* the friendly name of this filter */
|
||||
const char * name;
|
||||
|
||||
/* the type of this filter */
|
||||
EGL_FilterType type;
|
||||
|
||||
/* early initialization for registration of options */
|
||||
void (*earlyInit)(void);
|
||||
|
||||
/* initialize the filter */
|
||||
bool (*init)(EGL_Filter ** filter);
|
||||
|
||||
/* free the filter */
|
||||
void (*free)(EGL_Filter * filter);
|
||||
|
||||
/* render any imgui config
|
||||
* Returns true if a redraw is required */
|
||||
bool (*imguiConfig)(EGL_Filter * filter);
|
||||
|
||||
/* writes filter state to options */
|
||||
void (*saveState)(EGL_Filter * filter);
|
||||
|
||||
/* reads filter state from options */
|
||||
void (*loadState)(EGL_Filter * filter);
|
||||
|
||||
/* set the input format of the filter */
|
||||
bool (*setup)(EGL_Filter * filter, enum EGL_PixelFormat pixFmt,
|
||||
unsigned int width, unsigned int height);
|
||||
|
||||
/* set the output resolution hint for the filter
|
||||
* this is optional and only a hint */
|
||||
void (*setOutputResHint)(EGL_Filter * filter,
|
||||
unsigned int x, unsigned int y);
|
||||
|
||||
/* returns the output resolution of the filter */
|
||||
void (*getOutputRes)(EGL_Filter * filter,
|
||||
unsigned int *x, unsigned int *y);
|
||||
|
||||
/* prepare the shader for use
|
||||
* A filter can return false to bypass it */
|
||||
bool (*prepare)(EGL_Filter * filter);
|
||||
|
||||
/* runs the filter on the provided texture
|
||||
* returns the processed texture as the output */
|
||||
GLuint (*run)(EGL_Filter * filter, EGL_FilterRects * rects,
|
||||
GLuint texture);
|
||||
|
||||
/* called when the filter output is no loger needed so it can release memory
|
||||
* this is optional */
|
||||
void (*release)(EGL_Filter * filter);
|
||||
}
|
||||
EGL_FilterOps;
|
||||
|
||||
typedef struct EGL_Filter
|
||||
{
|
||||
EGL_FilterOps ops;
|
||||
}
|
||||
EGL_Filter;
|
||||
|
||||
static inline bool egl_filterInit(const EGL_FilterOps * ops, EGL_Filter ** filter)
|
||||
{
|
||||
if (!ops->init(filter))
|
||||
return false;
|
||||
|
||||
memcpy(&(*filter)->ops, ops, sizeof(*ops));
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void egl_filterFree(EGL_Filter ** filter)
|
||||
{
|
||||
(*filter)->ops.free(*filter);
|
||||
*filter = NULL;
|
||||
}
|
||||
|
||||
static inline bool egl_filterImguiConfig(EGL_Filter * filter)
|
||||
{
|
||||
return filter->ops.imguiConfig(filter);
|
||||
}
|
||||
|
||||
static inline void egl_filterSaveState(EGL_Filter * filter)
|
||||
{
|
||||
filter->ops.saveState(filter);
|
||||
}
|
||||
|
||||
static inline void egl_filterLoadState(EGL_Filter * filter)
|
||||
{
|
||||
filter->ops.loadState(filter);
|
||||
}
|
||||
|
||||
static inline bool egl_filterSetup(EGL_Filter * filter,
|
||||
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height)
|
||||
{
|
||||
return filter->ops.setup(filter, pixFmt, width, height);
|
||||
}
|
||||
|
||||
static inline void egl_filterSetOutputResHint(EGL_Filter * filter,
|
||||
unsigned int x, unsigned int y)
|
||||
{
|
||||
if (filter->ops.setOutputResHint)
|
||||
filter->ops.setOutputResHint(filter, x, y);
|
||||
}
|
||||
|
||||
static inline void egl_filterGetOutputRes(EGL_Filter * filter,
|
||||
unsigned int *x, unsigned int *y)
|
||||
{
|
||||
return filter->ops.getOutputRes(filter, x, y);
|
||||
}
|
||||
|
||||
static inline bool egl_filterPrepare(EGL_Filter * filter)
|
||||
{
|
||||
return filter->ops.prepare(filter);
|
||||
}
|
||||
|
||||
static inline GLuint egl_filterRun(EGL_Filter * filter,
|
||||
EGL_FilterRects * rects, GLuint texture)
|
||||
{
|
||||
return filter->ops.run(filter, rects, texture);
|
||||
}
|
||||
|
||||
static inline void egl_filterRelease(EGL_Filter * filter)
|
||||
{
|
||||
if (filter->ops.release)
|
||||
filter->ops.release(filter);
|
||||
}
|
||||
|
||||
void egl_filterRectsRender(EGL_Shader * shader, EGL_FilterRects * rects);
|
||||
442
client/renderers/EGL/filter_downscale.c
Normal file
442
client/renderers/EGL/filter_downscale.c
Normal file
@@ -0,0 +1,442 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "filter.h"
|
||||
#include "framebuffer.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "cimgui.h"
|
||||
|
||||
#include "basic.vert.h"
|
||||
#include "downscale.frag.h"
|
||||
#include "downscale_lanczos2.frag.h"
|
||||
#include "downscale_linear.frag.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
DOWNSCALE_NEAREST = 0,
|
||||
DOWNSCALE_LINEAR,
|
||||
DOWNSCALE_LANCZOS2,
|
||||
}
|
||||
DownscaleFilter;
|
||||
|
||||
#define DOWNSCALE_COUNT (DOWNSCALE_LANCZOS2 + 1)
|
||||
|
||||
const char *filterNames[DOWNSCALE_COUNT] = {
|
||||
"Nearest pixel",
|
||||
"Linear",
|
||||
"Lanczos",
|
||||
};
|
||||
|
||||
typedef struct EGL_FilterDownscale
|
||||
{
|
||||
EGL_Filter base;
|
||||
|
||||
bool enable;
|
||||
EGL_Shader * nearest;
|
||||
EGL_Uniform uNearest;
|
||||
EGL_Shader * linear;
|
||||
EGL_Shader * lanczos2;
|
||||
|
||||
DownscaleFilter filter;
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
unsigned int width, height;
|
||||
float pixelSize;
|
||||
float vOffset, hOffset;
|
||||
bool prepared;
|
||||
|
||||
EGL_Framebuffer * fb;
|
||||
GLuint sampler[2];
|
||||
}
|
||||
EGL_FilterDownscale;
|
||||
|
||||
static void egl_filterDownscaleEarlyInit(void)
|
||||
{
|
||||
static struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "downscale",
|
||||
.description = "Enable downscaling",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "downscalePixelSize",
|
||||
.description = "Downscale filter pixel size",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_FLOAT,
|
||||
.value.x_float = 2.0f
|
||||
},
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "downscaleHOffset",
|
||||
.description = "Downscale filter horizontal offset",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_FLOAT,
|
||||
.value.x_float = 0.0f
|
||||
},
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "downscaleVOffset",
|
||||
.description = "Downscale filter vertical offset",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_FLOAT,
|
||||
.value.x_float = 0.0f
|
||||
},
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "downscaleFilter",
|
||||
.description = "Downscale filter type",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 0
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
static void egl_filterDownscaleSaveState(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
|
||||
|
||||
option_set_bool ("eglFilter", "downscale", this->enable);
|
||||
option_set_float("eglFilter", "downscalePixelSize", this->pixelSize);
|
||||
option_set_float("eglFilter", "downscaleHOffset", this->vOffset);
|
||||
option_set_float("eglFilter", "downscaleVOffset", this->hOffset);
|
||||
option_set_int ("eglFilter", "downscaleFilter", this->filter);
|
||||
}
|
||||
|
||||
static void egl_filterDownscaleLoadState(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
|
||||
|
||||
this->enable = option_get_bool ("eglFilter", "downscale");
|
||||
this->pixelSize = option_get_float("eglFilter", "downscalePixelSize");
|
||||
this->vOffset = option_get_float("eglFilter", "downscaleHOffset");
|
||||
this->hOffset = option_get_float("eglFilter", "downscaleVOffset");
|
||||
this->filter = option_get_int ("eglFilter", "downscaleFilter");
|
||||
|
||||
if (this->filter < 0 || this->filter >= DOWNSCALE_COUNT)
|
||||
this->filter = 0;
|
||||
|
||||
this->prepared = false;
|
||||
}
|
||||
|
||||
static bool egl_filterDownscaleInit(EGL_Filter ** filter)
|
||||
{
|
||||
EGL_FilterDownscale * this = calloc(1, sizeof(*this));
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate ram");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shaderInit(&this->nearest))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the shader");
|
||||
goto error_this;
|
||||
}
|
||||
|
||||
if (!egl_shaderCompile(this->nearest,
|
||||
b_shader_basic_vert , b_shader_basic_vert_size,
|
||||
b_shader_downscale_frag, b_shader_downscale_frag_size)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the shader");
|
||||
goto error_shader;
|
||||
}
|
||||
|
||||
if (!egl_shaderInit(&this->linear))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the shader");
|
||||
goto error_this;
|
||||
}
|
||||
|
||||
if (!egl_shaderCompile(this->linear,
|
||||
b_shader_basic_vert, b_shader_basic_vert_size,
|
||||
b_shader_downscale_linear_frag, b_shader_downscale_linear_frag_size)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the shader");
|
||||
goto error_shader;
|
||||
}
|
||||
|
||||
if (!egl_shaderInit(&this->lanczos2))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the shader");
|
||||
goto error_this;
|
||||
}
|
||||
|
||||
if (!egl_shaderCompile(this->lanczos2,
|
||||
b_shader_basic_vert, b_shader_basic_vert_size,
|
||||
b_shader_downscale_lanczos2_frag, b_shader_downscale_lanczos2_frag_size)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the shader");
|
||||
goto error_shader;
|
||||
}
|
||||
|
||||
this->uNearest.type = EGL_UNIFORM_TYPE_3F;
|
||||
this->uNearest.location =
|
||||
egl_shaderGetUniform(this->nearest, "uConfig");
|
||||
|
||||
if (!egl_framebufferInit(&this->fb))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the framebuffer");
|
||||
goto error_shader;
|
||||
}
|
||||
|
||||
glGenSamplers(ARRAY_LENGTH(this->sampler), this->sampler);
|
||||
glSamplerParameteri(this->sampler[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(this->sampler[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(this->sampler[0], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(this->sampler[0], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(this->sampler[1], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(this->sampler[1], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(this->sampler[1], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(this->sampler[1], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||
|
||||
egl_filterDownscaleLoadState(&this->base);
|
||||
|
||||
*filter = &this->base;
|
||||
return true;
|
||||
|
||||
error_shader:
|
||||
egl_shaderFree(&this->nearest);
|
||||
egl_shaderFree(&this->linear);
|
||||
egl_shaderFree(&this->lanczos2);
|
||||
|
||||
error_this:
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void egl_filterDownscaleFree(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
|
||||
|
||||
egl_shaderFree(&this->nearest);
|
||||
egl_shaderFree(&this->linear);
|
||||
egl_shaderFree(&this->lanczos2);
|
||||
egl_framebufferFree(&this->fb);
|
||||
glDeleteSamplers(ARRAY_LENGTH(this->sampler), this->sampler);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static bool egl_filterDownscaleImguiConfig(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
|
||||
|
||||
bool redraw = false;
|
||||
bool enable = this->enable;
|
||||
|
||||
igCheckbox("Enable", &enable);
|
||||
if (enable != this->enable)
|
||||
{
|
||||
this->enable = enable;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (igBeginCombo("Filter", filterNames[this->filter], 0))
|
||||
{
|
||||
for (int i = 0; i < DOWNSCALE_COUNT; ++i)
|
||||
{
|
||||
bool selected = i == this->filter;
|
||||
if (igSelectable_Bool(filterNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
redraw = true;
|
||||
this->filter = i;
|
||||
}
|
||||
if (selected)
|
||||
igSetItemDefaultFocus();
|
||||
}
|
||||
igEndCombo();
|
||||
}
|
||||
|
||||
float pixelSize = this->pixelSize;
|
||||
igInputFloat("Pixel size", &pixelSize, 0.1f, 1.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal);
|
||||
pixelSize = util_clamp(pixelSize, 1.0f, 10.0f);
|
||||
igSliderFloat("##pixelsize", &pixelSize, 1.0f, 10.0f, "%.2f",
|
||||
ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_NoInput);
|
||||
|
||||
igText("Resolution: %dx%d", this->width, this->height);
|
||||
|
||||
if (pixelSize != this->pixelSize)
|
||||
{
|
||||
this->pixelSize = pixelSize;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
switch (this->filter)
|
||||
{
|
||||
case DOWNSCALE_NEAREST:
|
||||
{
|
||||
float vOffset = this->vOffset;
|
||||
igSliderFloat("V-Offset", &vOffset, -2, 2, NULL, 0);
|
||||
if (vOffset != this->vOffset)
|
||||
{
|
||||
this->vOffset = vOffset;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
float hOffset = this->hOffset;
|
||||
igSliderFloat("H-Offset", &hOffset, -2, 2, NULL, 0);
|
||||
if (hOffset != this->hOffset)
|
||||
{
|
||||
this->hOffset = hOffset;
|
||||
redraw = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (redraw)
|
||||
this->prepared = false;
|
||||
|
||||
return redraw;
|
||||
}
|
||||
|
||||
static bool egl_filterDownscaleSetup(EGL_Filter * filter,
|
||||
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height)
|
||||
{
|
||||
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
|
||||
|
||||
width = (float)width / this->pixelSize;
|
||||
height = (float)height / this->pixelSize;
|
||||
|
||||
if (!this->enable)
|
||||
return false;
|
||||
|
||||
if (this->prepared &&
|
||||
pixFmt == this->pixFmt &&
|
||||
this->width == width &&
|
||||
this->height == height)
|
||||
return this->pixelSize > 1.0f;
|
||||
|
||||
if (!egl_framebufferSetup(this->fb, pixFmt, width, height))
|
||||
return false;
|
||||
|
||||
this->pixFmt = pixFmt;
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->prepared = false;
|
||||
|
||||
return this->pixelSize > 1.0f;
|
||||
}
|
||||
|
||||
static void egl_filterDownscaleGetOutputRes(EGL_Filter * filter,
|
||||
unsigned int *width, unsigned int *height)
|
||||
{
|
||||
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
|
||||
*width = this->width;
|
||||
*height = this->height;
|
||||
}
|
||||
|
||||
static bool egl_filterDownscalePrepare(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
|
||||
|
||||
if (this->prepared)
|
||||
return true;
|
||||
|
||||
switch (this->filter)
|
||||
{
|
||||
case DOWNSCALE_NEAREST:
|
||||
this->uNearest.f[0] = this->pixelSize;
|
||||
this->uNearest.f[1] = this->vOffset;
|
||||
this->uNearest.f[2] = this->hOffset;
|
||||
egl_shaderSetUniforms(this->nearest, &this->uNearest, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static GLuint egl_filterDownscaleRun(EGL_Filter * filter,
|
||||
EGL_FilterRects * rects, GLuint texture)
|
||||
{
|
||||
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
|
||||
|
||||
egl_framebufferBind(this->fb);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
EGL_Shader * shader;
|
||||
|
||||
switch (this->filter)
|
||||
{
|
||||
case DOWNSCALE_NEAREST:
|
||||
glBindSampler(0, this->sampler[0]);
|
||||
shader = this->nearest;
|
||||
break;
|
||||
|
||||
case DOWNSCALE_LINEAR:
|
||||
glBindSampler(0, this->sampler[1]);
|
||||
shader = this->linear;
|
||||
break;
|
||||
|
||||
case DOWNSCALE_LANCZOS2:
|
||||
glBindSampler(0, this->sampler[0]);
|
||||
shader = this->lanczos2;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_UNREACHABLE();
|
||||
}
|
||||
|
||||
egl_shaderUse(shader);
|
||||
egl_filterRectsRender(shader, rects);
|
||||
|
||||
return egl_framebufferGetTexture(this->fb);
|
||||
}
|
||||
|
||||
EGL_FilterOps egl_filterDownscaleOps =
|
||||
{
|
||||
.id = "downscale",
|
||||
.name = "Downscaler",
|
||||
.type = EGL_FILTER_TYPE_DOWNSCALE,
|
||||
.earlyInit = egl_filterDownscaleEarlyInit,
|
||||
.init = egl_filterDownscaleInit,
|
||||
.free = egl_filterDownscaleFree,
|
||||
.imguiConfig = egl_filterDownscaleImguiConfig,
|
||||
.saveState = egl_filterDownscaleSaveState,
|
||||
.loadState = egl_filterDownscaleLoadState,
|
||||
.setup = egl_filterDownscaleSetup,
|
||||
.getOutputRes = egl_filterDownscaleGetOutputRes,
|
||||
.prepare = egl_filterDownscalePrepare,
|
||||
.run = egl_filterDownscaleRun
|
||||
};
|
||||
297
client/renderers/EGL/filter_ffx_cas.c
Normal file
297
client/renderers/EGL/filter_ffx_cas.c
Normal file
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "filter.h"
|
||||
#include "framebuffer.h"
|
||||
|
||||
#include "common/countedbuffer.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "cimgui.h"
|
||||
#include "ffx.h"
|
||||
|
||||
#include "basic.vert.h"
|
||||
#include "ffx_cas.frag.h"
|
||||
|
||||
typedef struct EGL_FilterFFXCAS
|
||||
{
|
||||
EGL_Filter base;
|
||||
|
||||
EGL_Shader * shader;
|
||||
bool enable;
|
||||
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
unsigned int width, height;
|
||||
float sharpness;
|
||||
CountedBuffer * consts;
|
||||
bool prepared;
|
||||
|
||||
EGL_Framebuffer * fb;
|
||||
GLuint sampler;
|
||||
}
|
||||
EGL_FilterFFXCAS;
|
||||
|
||||
static void egl_filterFFXCASEarlyInit(void)
|
||||
{
|
||||
static struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "ffxCAS",
|
||||
.description = "AMD FidelityFX CAS",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "ffxCASSharpness",
|
||||
.description = "AMD FidelityFX CAS Sharpness",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_FLOAT,
|
||||
.value.x_float = 0.0f
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
static void casUpdateConsts(EGL_FilterFFXCAS * this)
|
||||
{
|
||||
ffxCasConst((uint32_t *) this->consts->data, this->sharpness,
|
||||
this->width, this->height,
|
||||
this->width, this->height);
|
||||
}
|
||||
|
||||
static void egl_filterFFXCASSaveState(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
|
||||
|
||||
option_set_bool ("eglFilter", "ffxCAS", this->enable);
|
||||
option_set_float("eglFilter", "ffxCASSharpness", this->sharpness);
|
||||
}
|
||||
|
||||
static void egl_filterFFXCASLoadState(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
|
||||
|
||||
this->enable = option_get_bool ("eglFilter", "ffxCAS");
|
||||
this->sharpness = option_get_float("eglFilter", "ffxCASSharpness");
|
||||
}
|
||||
|
||||
static bool egl_filterFFXCASInit(EGL_Filter ** filter)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = calloc(1, sizeof(*this));
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate ram");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shaderInit(&this->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the shader");
|
||||
goto error_this;
|
||||
}
|
||||
|
||||
if (!egl_shaderCompile(this->shader,
|
||||
b_shader_basic_vert , b_shader_basic_vert_size,
|
||||
b_shader_ffx_cas_frag, b_shader_ffx_cas_frag_size)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the shader");
|
||||
goto error_shader;
|
||||
}
|
||||
|
||||
this->consts = countedBufferNew(8 * sizeof(GLuint));
|
||||
if (!this->consts)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate consts buffer");
|
||||
goto error_shader;
|
||||
}
|
||||
|
||||
egl_shaderSetUniforms(this->shader, &(EGL_Uniform) {
|
||||
.type = EGL_UNIFORM_TYPE_4UIV,
|
||||
.location = egl_shaderGetUniform(this->shader, "uConsts"),
|
||||
.v = this->consts,
|
||||
}, 1);
|
||||
|
||||
egl_filterFFXCASLoadState(&this->base);
|
||||
|
||||
if (!egl_framebufferInit(&this->fb))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the framebuffer");
|
||||
goto error_consts;
|
||||
}
|
||||
|
||||
glGenSamplers(1, &this->sampler);
|
||||
glSamplerParameteri(this->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(this->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||
|
||||
*filter = &this->base;
|
||||
return true;
|
||||
|
||||
error_consts:
|
||||
countedBufferRelease(&this->consts);
|
||||
|
||||
error_shader:
|
||||
egl_shaderFree(&this->shader);
|
||||
|
||||
error_this:
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void egl_filterFFXCASFree(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
|
||||
|
||||
egl_shaderFree(&this->shader);
|
||||
countedBufferRelease(&this->consts);
|
||||
egl_framebufferFree(&this->fb);
|
||||
glDeleteSamplers(1, &this->sampler);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static bool egl_filterFFXCASImguiConfig(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
|
||||
|
||||
bool redraw = false;
|
||||
bool cas = this->enable;
|
||||
float casSharpness = this->sharpness;
|
||||
|
||||
igCheckbox("Enabled", &cas);
|
||||
if (cas != this->enable)
|
||||
{
|
||||
this->enable = cas;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
igText("Sharpness:");
|
||||
igSameLine(0.0f, -1.0f);
|
||||
igPushItemWidth(igGetWindowWidth() - igGetCursorPosX() -
|
||||
igGetStyle()->WindowPadding.x);
|
||||
|
||||
igSliderFloat("##casSharpness", &casSharpness, 0.0f, 1.0f, NULL, 0);
|
||||
casSharpness = util_clamp(casSharpness, 0.0f, 1.0f);
|
||||
if (igIsItemHovered(ImGuiHoveredFlags_None))
|
||||
igSetTooltip("Ctrl+Click to enter a value");
|
||||
igPopItemWidth();
|
||||
|
||||
if (casSharpness != this->sharpness)
|
||||
{
|
||||
// enable CAS if the sharpness was changed
|
||||
if (!cas)
|
||||
{
|
||||
cas = true;
|
||||
this->enable = true;
|
||||
}
|
||||
|
||||
this->sharpness = casSharpness;
|
||||
casUpdateConsts(this);
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (redraw)
|
||||
this->prepared = false;
|
||||
|
||||
return redraw;
|
||||
}
|
||||
|
||||
static bool egl_filterFFXCASSetup(EGL_Filter * filter,
|
||||
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
|
||||
|
||||
if (!this->enable)
|
||||
return false;
|
||||
|
||||
if (pixFmt == this->pixFmt && this->width == width && this->height == height)
|
||||
return true;
|
||||
|
||||
if (!egl_framebufferSetup(this->fb, pixFmt, width, height))
|
||||
return false;
|
||||
|
||||
this->pixFmt = pixFmt;
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->prepared = false;
|
||||
casUpdateConsts(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_filterFFXCASGetOutputRes(EGL_Filter * filter,
|
||||
unsigned int *width, unsigned int *height)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
|
||||
*width = this->width;
|
||||
*height = this->height;
|
||||
}
|
||||
|
||||
static bool egl_filterFFXCASPrepare(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
|
||||
|
||||
if (this->prepared)
|
||||
return true;
|
||||
|
||||
this->prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static GLuint egl_filterFFXCASRun(EGL_Filter * filter,
|
||||
EGL_FilterRects * rects, GLuint texture)
|
||||
{
|
||||
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
|
||||
|
||||
egl_framebufferBind(this->fb);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glBindSampler(0, this->sampler);
|
||||
|
||||
egl_shaderUse(this->shader);
|
||||
egl_filterRectsRender(this->shader, rects);
|
||||
|
||||
return egl_framebufferGetTexture(this->fb);
|
||||
}
|
||||
|
||||
EGL_FilterOps egl_filterFFXCASOps =
|
||||
{
|
||||
.id = "ffxCAS",
|
||||
.name = "AMD FidelityFX CAS",
|
||||
.type = EGL_FILTER_TYPE_EFFECT,
|
||||
.earlyInit = egl_filterFFXCASEarlyInit,
|
||||
.init = egl_filterFFXCASInit,
|
||||
.free = egl_filterFFXCASFree,
|
||||
.imguiConfig = egl_filterFFXCASImguiConfig,
|
||||
.saveState = egl_filterFFXCASSaveState,
|
||||
.loadState = egl_filterFFXCASLoadState,
|
||||
.setup = egl_filterFFXCASSetup,
|
||||
.getOutputRes = egl_filterFFXCASGetOutputRes,
|
||||
.prepare = egl_filterFFXCASPrepare,
|
||||
.run = egl_filterFFXCASRun
|
||||
};
|
||||
440
client/renderers/EGL/filter_ffx_fsr1.c
Normal file
440
client/renderers/EGL/filter_ffx_fsr1.c
Normal file
@@ -0,0 +1,440 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "filter.h"
|
||||
#include "framebuffer.h"
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/countedbuffer.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "cimgui.h"
|
||||
#include "ffx.h"
|
||||
|
||||
#include "basic.vert.h"
|
||||
#include "ffx_fsr1_easu.frag.h"
|
||||
#include "ffx_fsr1_rcas.frag.h"
|
||||
|
||||
typedef struct EGL_FilterFFXFSR1
|
||||
{
|
||||
EGL_Filter base;
|
||||
|
||||
EGL_Shader * easu, * rcas;
|
||||
bool enable, active;
|
||||
float sharpness;
|
||||
CountedBuffer * consts;
|
||||
EGL_Uniform easuUniform[2], rcasUniform;
|
||||
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
unsigned int width, height;
|
||||
unsigned int inWidth, inHeight;
|
||||
bool sizeChanged;
|
||||
bool prepared;
|
||||
|
||||
EGL_Framebuffer * easuFb, * rcasFb;
|
||||
GLuint sampler;
|
||||
}
|
||||
EGL_FilterFFXFSR1;
|
||||
|
||||
static void egl_filterFFXFSR1EarlyInit(void)
|
||||
{
|
||||
static struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "ffxFSR",
|
||||
.description = "AMD FidelityFX FSR",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "ffxFSRSharpness",
|
||||
.description = "AMD FidelityFX FSR Sharpness",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_FLOAT,
|
||||
.value.x_float = 1.0f
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
static void rcasUpdateUniform(EGL_FilterFFXFSR1 * this)
|
||||
{
|
||||
ffxFsrRcasConst(this->rcasUniform.ui, 2.0f - this->sharpness * 2.0f);
|
||||
}
|
||||
|
||||
static void egl_filterFFXFSR1SaveState(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
|
||||
option_set_bool ("eglFilter", "ffxFSR", this->enable);
|
||||
option_set_float("eglFilter", "ffxFSRSharpness", this->sharpness);
|
||||
}
|
||||
|
||||
static void egl_filterFFXFSR1LoadState(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
|
||||
this->enable = option_get_bool ("eglFilter", "ffxFSR");
|
||||
this->sharpness = option_get_float("eglFilter", "ffxFSRSharpness");
|
||||
}
|
||||
|
||||
static bool egl_filterFFXFSR1Init(EGL_Filter ** filter)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = calloc(1, sizeof(*this));
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate ram");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shaderInit(&this->easu))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the Easu shader");
|
||||
goto error_this;
|
||||
}
|
||||
|
||||
if (!egl_shaderInit(&this->rcas))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the Rcas shader");
|
||||
goto error_esau;
|
||||
}
|
||||
|
||||
if (!egl_shaderCompile(this->easu,
|
||||
b_shader_basic_vert , b_shader_basic_vert_size,
|
||||
b_shader_ffx_fsr1_easu_frag, b_shader_ffx_fsr1_easu_frag_size)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the Easu shader");
|
||||
goto error_rcas;
|
||||
}
|
||||
|
||||
if (!egl_shaderCompile(this->rcas,
|
||||
b_shader_basic_vert , b_shader_basic_vert_size,
|
||||
b_shader_ffx_fsr1_rcas_frag, b_shader_ffx_fsr1_rcas_frag_size)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the Rcas shader");
|
||||
goto error_rcas;
|
||||
}
|
||||
|
||||
this->consts = countedBufferNew(16 * sizeof(GLuint));
|
||||
if (!this->consts)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate consts buffer");
|
||||
goto error_rcas;
|
||||
}
|
||||
|
||||
egl_filterFFXFSR1LoadState(&this->base);
|
||||
|
||||
this->easuUniform[0].type = EGL_UNIFORM_TYPE_4UIV;
|
||||
this->easuUniform[0].location =
|
||||
egl_shaderGetUniform(this->easu, "uConsts");
|
||||
this->easuUniform[0].v = this->consts;
|
||||
this->easuUniform[1].type = EGL_UNIFORM_TYPE_2F;
|
||||
this->easuUniform[1].location =
|
||||
egl_shaderGetUniform(this->easu, "uOutRes");
|
||||
|
||||
this->rcasUniform.type = EGL_UNIFORM_TYPE_4UI;
|
||||
this->rcasUniform.location = egl_shaderGetUniform(this->rcas, "uConsts");
|
||||
rcasUpdateUniform(this);
|
||||
|
||||
if (!egl_framebufferInit(&this->easuFb))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the Easu framebuffer");
|
||||
goto error_consts;
|
||||
}
|
||||
|
||||
if (!egl_framebufferInit(&this->rcasFb))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the Rcas framebuffer");
|
||||
goto error_easuFb;
|
||||
}
|
||||
|
||||
glGenSamplers(1, &this->sampler);
|
||||
glSamplerParameteri(this->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(this->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||
|
||||
*filter = &this->base;
|
||||
return true;
|
||||
|
||||
error_easuFb:
|
||||
egl_framebufferFree(&this->rcasFb);
|
||||
|
||||
error_consts:
|
||||
countedBufferRelease(&this->consts);
|
||||
|
||||
error_rcas:
|
||||
egl_shaderFree(&this->rcas);
|
||||
|
||||
error_esau:
|
||||
egl_shaderFree(&this->easu);
|
||||
|
||||
error_this:
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void egl_filterFFXFSR1Free(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
|
||||
egl_shaderFree(&this->easu);
|
||||
egl_shaderFree(&this->rcas);
|
||||
countedBufferRelease(&this->consts);
|
||||
egl_framebufferFree(&this->easuFb);
|
||||
egl_framebufferFree(&this->rcasFb);
|
||||
glDeleteSamplers(1, &this->sampler);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static bool egl_filterFFXFSR1ImguiConfig(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
|
||||
bool redraw = false;
|
||||
bool enable = this->enable;
|
||||
float sharpness = this->sharpness;
|
||||
|
||||
igCheckbox("Enabled", &enable);
|
||||
if (enable != this->enable)
|
||||
{
|
||||
this->enable = enable;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (this->active)
|
||||
{
|
||||
double dimScale = (double) this->width / this->inWidth;
|
||||
const char * name;
|
||||
if (dimScale < 1.29)
|
||||
name = "better than Ultra Quality";
|
||||
else if (dimScale < 1.31)
|
||||
name = "Ultra Quality";
|
||||
else if (dimScale < 1.4)
|
||||
name = "slightly worse than Ultra Quality";
|
||||
else if (dimScale < 1.49)
|
||||
name = "slightly better than Quality";
|
||||
else if (dimScale < 1.51)
|
||||
name = "Quality";
|
||||
else if (dimScale < 1.6)
|
||||
name = "slightly worse than Quality";
|
||||
else if (dimScale < 1.69)
|
||||
name = "slightly better than Balanced";
|
||||
else if (dimScale < 1.71)
|
||||
name = "Balanced";
|
||||
else if (dimScale < 1.85)
|
||||
name = "slightly worse than Balanced";
|
||||
else if (dimScale < 1.99)
|
||||
name = "slightly better than Performance";
|
||||
else if (dimScale < 2.01)
|
||||
name = "Performance";
|
||||
else
|
||||
name = "worse than Performance";
|
||||
igText("Equivalent quality mode: %s%s", name, this->enable ? "" : ", inactive");
|
||||
}
|
||||
else
|
||||
igText("Equivalent quality mode: not upscaling, inactive");
|
||||
|
||||
if (igIsItemHovered(ImGuiHoveredFlags_None))
|
||||
{
|
||||
igBeginTooltip();
|
||||
igText(
|
||||
"Equivalent quality mode is decided by the resolution in the guest VM or the output\n"
|
||||
"of the previous filter in the chain.\n\n"
|
||||
"Here are the input resolutions needed for each quality mode at current window size:\n"
|
||||
);
|
||||
|
||||
if (igBeginTable("Resolutions", 2, 0, (ImVec2) { 0.0f, 0.0f }, 0.0f))
|
||||
{
|
||||
igTableNextColumn();
|
||||
igText("Ultra Quality");
|
||||
igTableNextColumn();
|
||||
igText("%.0fx%.0f", this->width / 1.3, this->height / 1.3);
|
||||
igTableNextColumn();
|
||||
igText("Quality");
|
||||
igTableNextColumn();
|
||||
igText("%.0fx%.0f", this->width / 1.5, this->height / 1.5);
|
||||
igTableNextColumn();
|
||||
igText("Balanced");
|
||||
igTableNextColumn();
|
||||
igText("%.0fx%.0f", this->width / 1.7, this->height / 1.7);
|
||||
igTableNextColumn();
|
||||
igText("Performance");
|
||||
igTableNextColumn();
|
||||
igText("%.0fx%.0f", this->width / 2.0, this->height / 2.0);
|
||||
igEndTable();
|
||||
}
|
||||
igEndTooltip();
|
||||
}
|
||||
|
||||
igText("Sharpness:");
|
||||
igSameLine(0.0f, -1.0f);
|
||||
igPushItemWidth(igGetWindowWidth() - igGetCursorPosX() -
|
||||
igGetStyle()->WindowPadding.x);
|
||||
igSliderFloat("##fsr1Sharpness", &sharpness, 0.0f, 1.0f, NULL, 0);
|
||||
sharpness = util_clamp(sharpness, 0.0f, 1.0f);
|
||||
if (igIsItemHovered(ImGuiHoveredFlags_None))
|
||||
igSetTooltip("Ctrl+Click to enter a value");
|
||||
igPopItemWidth();
|
||||
|
||||
if (sharpness != this->sharpness)
|
||||
{
|
||||
// enable FSR1 if the sharpness was changed
|
||||
if (!enable)
|
||||
{
|
||||
enable = true;
|
||||
this->enable = true;
|
||||
}
|
||||
|
||||
this->sharpness = sharpness;
|
||||
rcasUpdateUniform(this);
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (redraw)
|
||||
this->prepared = false;
|
||||
|
||||
return redraw;
|
||||
}
|
||||
|
||||
static void egl_filterFFXFSR1SetOutputResHint(EGL_Filter * filter,
|
||||
unsigned int width, unsigned int height)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
if (this->width == width && this->height == height)
|
||||
return;
|
||||
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->sizeChanged = true;
|
||||
this->prepared = false;
|
||||
}
|
||||
|
||||
static bool egl_filterFFXFSR1Setup(EGL_Filter * filter,
|
||||
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
|
||||
if (!this->enable)
|
||||
return false;
|
||||
|
||||
this->active = this->width > width && this->height > height;
|
||||
if (!this->active)
|
||||
return false;
|
||||
|
||||
if (pixFmt == this->pixFmt && !this->sizeChanged &&
|
||||
width == this->inWidth && height == this->inHeight)
|
||||
return true;
|
||||
|
||||
if (!egl_framebufferSetup(this->easuFb, pixFmt, this->width, this->height))
|
||||
return false;
|
||||
|
||||
if (!egl_framebufferSetup(this->rcasFb, pixFmt, this->width, this->height))
|
||||
return false;
|
||||
|
||||
this->inWidth = width;
|
||||
this->inHeight = height;
|
||||
this->sizeChanged = false;
|
||||
this->pixFmt = pixFmt;
|
||||
this->prepared = false;
|
||||
|
||||
this->easuUniform[1].f[0] = this->width;
|
||||
this->easuUniform[1].f[1] = this->height;
|
||||
ffxFsrEasuConst((uint32_t *)this->consts->data, this->inWidth, this->inHeight,
|
||||
this->inWidth, this->inHeight, this->width, this->height);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_filterFFXFSR1GetOutputRes(EGL_Filter * filter,
|
||||
unsigned int *width, unsigned int *height)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
*width = this->width;
|
||||
*height = this->height;
|
||||
}
|
||||
|
||||
static bool egl_filterFFXFSR1Prepare(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
|
||||
if (!this->active)
|
||||
return false;
|
||||
|
||||
if (this->prepared)
|
||||
return true;
|
||||
|
||||
egl_shaderSetUniforms(this->easu, this->easuUniform, ARRAY_LENGTH(this->easuUniform));
|
||||
egl_shaderSetUniforms(this->rcas, &this->rcasUniform, 1);
|
||||
this->prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static GLuint egl_filterFFXFSR1Run(EGL_Filter * filter,
|
||||
EGL_FilterRects * rects, GLuint texture)
|
||||
{
|
||||
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
|
||||
|
||||
// pass 1, Easu
|
||||
egl_framebufferBind(this->easuFb);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glBindSampler(0, this->sampler);
|
||||
egl_shaderUse(this->easu);
|
||||
egl_filterRectsRender(this->easu, rects);
|
||||
texture = egl_framebufferGetTexture(this->easuFb);
|
||||
|
||||
// pass 2, Rcas
|
||||
egl_framebufferBind(this->rcasFb);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glBindSampler(0, this->sampler);
|
||||
egl_shaderUse(this->rcas);
|
||||
egl_filterRectsRender(this->rcas, rects);
|
||||
texture = egl_framebufferGetTexture(this->rcasFb);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
EGL_FilterOps egl_filterFFXFSR1Ops =
|
||||
{
|
||||
.id = "ffxFSR1",
|
||||
.name = "AMD FidelityFX FSR",
|
||||
.type = EGL_FILTER_TYPE_UPSCALE,
|
||||
.earlyInit = egl_filterFFXFSR1EarlyInit,
|
||||
.init = egl_filterFFXFSR1Init,
|
||||
.free = egl_filterFFXFSR1Free,
|
||||
.imguiConfig = egl_filterFFXFSR1ImguiConfig,
|
||||
.saveState = egl_filterFFXFSR1SaveState,
|
||||
.loadState = egl_filterFFXFSR1LoadState,
|
||||
.setup = egl_filterFFXFSR1Setup,
|
||||
.setOutputResHint = egl_filterFFXFSR1SetOutputResHint,
|
||||
.getOutputRes = egl_filterFFXFSR1GetOutputRes,
|
||||
.prepare = egl_filterFFXFSR1Prepare,
|
||||
.run = egl_filterFFXFSR1Run
|
||||
};
|
||||
25
client/renderers/EGL/filters.h
Normal file
25
client/renderers/EGL/filters.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
extern EGL_FilterOps egl_filterDownscaleOps;
|
||||
extern EGL_FilterOps egl_filterFFXCASOps;
|
||||
extern EGL_FilterOps egl_filterFFXFSR1Ops;
|
||||
@@ -1,211 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "fps.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "fps.vert.h"
|
||||
#include "fps.frag.h"
|
||||
#include "fps_bg.frag.h"
|
||||
|
||||
struct EGL_FPS
|
||||
{
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
|
||||
EGL_Texture * texture;
|
||||
EGL_Shader * shader;
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
bool display;
|
||||
bool ready;
|
||||
int iwidth, iheight;
|
||||
float width, height;
|
||||
|
||||
// uniforms
|
||||
GLint uScreen , uSize;
|
||||
GLint uScreenBG, uSizeBG;
|
||||
};
|
||||
|
||||
bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj)
|
||||
{
|
||||
*fps = (EGL_FPS *)malloc(sizeof(EGL_FPS));
|
||||
if (!*fps)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_FPS");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*fps, 0, sizeof(EGL_FPS));
|
||||
|
||||
(*fps)->font = font;
|
||||
(*fps)->fontObj = fontObj;
|
||||
|
||||
if (!egl_texture_init(&(*fps)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*fps)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*fps)->shaderBG))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps bg shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!egl_shader_compile((*fps)->shader,
|
||||
b_shader_fps_vert, b_shader_fps_vert_size,
|
||||
b_shader_fps_frag, b_shader_fps_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the fps shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*fps)->shaderBG,
|
||||
b_shader_fps_vert , b_shader_fps_vert_size,
|
||||
b_shader_fps_bg_frag, b_shader_fps_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the fps shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
(*fps)->uSize = egl_shader_get_uniform_location((*fps)->shader , "size" );
|
||||
(*fps)->uScreen = egl_shader_get_uniform_location((*fps)->shader , "screen");
|
||||
(*fps)->uSizeBG = egl_shader_get_uniform_location((*fps)->shaderBG, "size" );
|
||||
(*fps)->uScreenBG = egl_shader_get_uniform_location((*fps)->shaderBG, "screen");
|
||||
|
||||
if (!egl_model_init(&(*fps)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*fps)->model);
|
||||
egl_model_set_texture((*fps)->model, (*fps)->texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_fps_free(EGL_FPS ** fps)
|
||||
{
|
||||
if (!*fps)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*fps)->texture );
|
||||
egl_shader_free (&(*fps)->shader );
|
||||
egl_shader_free (&(*fps)->shaderBG);
|
||||
egl_model_free (&(*fps)->model );
|
||||
|
||||
free(*fps);
|
||||
*fps = NULL;
|
||||
}
|
||||
|
||||
void egl_fps_set_display(EGL_FPS * fps, bool display)
|
||||
{
|
||||
fps->display = display;
|
||||
}
|
||||
|
||||
void egl_fps_set_font(EGL_FPS * fps, LG_Font * fontObj)
|
||||
{
|
||||
fps->fontObj = fontObj;
|
||||
}
|
||||
|
||||
void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
{
|
||||
if (!fps->display)
|
||||
return;
|
||||
|
||||
char str[128];
|
||||
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
|
||||
|
||||
LG_FontBitmap * bmp = fps->font->render(fps->fontObj, 0xffffff00, str);
|
||||
if (!bmp)
|
||||
{
|
||||
DEBUG_ERROR("Failed to render fps text");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fps->iwidth != bmp->width || fps->iheight != bmp->height)
|
||||
{
|
||||
fps->iwidth = bmp->width;
|
||||
fps->iheight = bmp->height;
|
||||
fps->width = (float)bmp->width;
|
||||
fps->height = (float)bmp->height;
|
||||
|
||||
egl_texture_setup(
|
||||
fps->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
egl_texture_update
|
||||
(
|
||||
fps->texture,
|
||||
bmp->pixels
|
||||
);
|
||||
|
||||
fps->ready = true;
|
||||
fps->font->release(fps->fontObj, bmp);
|
||||
}
|
||||
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
|
||||
{
|
||||
if (!fps->display || !fps->ready)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// render the background first
|
||||
egl_shader_use(fps->shaderBG);
|
||||
glUniform2f(fps->uScreenBG, scaleX , scaleY );
|
||||
glUniform2f(fps->uSizeBG , fps->width, fps->height);
|
||||
egl_model_render(fps->model);
|
||||
|
||||
// render the texture over the background
|
||||
egl_shader_use(fps->shader);
|
||||
glUniform2f(fps->uScreen, scaleX , scaleY );
|
||||
glUniform2f(fps->uSize , fps->width, fps->height);
|
||||
egl_model_render(fps->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
108
client/renderers/EGL/framebuffer.c
Normal file
108
client/renderers/EGL/framebuffer.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "texture.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
struct EGL_Framebuffer
|
||||
{
|
||||
GLuint fbo;
|
||||
EGL_Texture * tex;
|
||||
};
|
||||
|
||||
bool egl_framebufferInit(EGL_Framebuffer ** fb)
|
||||
{
|
||||
EGL_Framebuffer * this = calloc(1, sizeof(*this));
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate ram");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_textureInit(&this->tex, NULL, EGL_TEXTYPE_BUFFER))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
glGenFramebuffers(1, &this->fbo);
|
||||
|
||||
*fb = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_framebufferFree(EGL_Framebuffer ** fb)
|
||||
{
|
||||
EGL_Framebuffer * this = *fb;
|
||||
|
||||
egl_textureFree(&this->tex);
|
||||
free(this);
|
||||
*fb = NULL;
|
||||
}
|
||||
|
||||
bool egl_framebufferSetup(EGL_Framebuffer * this, enum EGL_PixelFormat pixFmt,
|
||||
unsigned int width, unsigned int height)
|
||||
{
|
||||
if (!egl_textureSetup(this->tex, pixFmt, width, height, 0))
|
||||
{
|
||||
DEBUG_ERROR("Failed to setup the texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
GLuint tex;
|
||||
egl_textureGet(this->tex, &tex, NULL, NULL);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this->fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D, tex, 0);
|
||||
glDrawBuffers(1, &(GLenum){GL_COLOR_ATTACHMENT0});
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
DEBUG_ERROR("Failed to setup the framebuffer: 0x%x", status);
|
||||
return false;
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_framebufferBind(EGL_Framebuffer * this)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this->fbo);
|
||||
glViewport(0, 0, this->tex->format.width, this->tex->format.height);
|
||||
}
|
||||
|
||||
GLuint egl_framebufferGetTexture(EGL_Framebuffer * this)
|
||||
{
|
||||
GLuint output;
|
||||
egl_textureGet(this->tex, &output, NULL, NULL);
|
||||
return output;
|
||||
}
|
||||
35
client/renderers/EGL/framebuffer.h
Normal file
35
client/renderers/EGL/framebuffer.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "texture.h"
|
||||
|
||||
typedef struct EGL_Framebuffer EGL_Framebuffer;
|
||||
|
||||
bool egl_framebufferInit(EGL_Framebuffer ** fb);
|
||||
void egl_framebufferFree(EGL_Framebuffer ** fb);
|
||||
|
||||
bool egl_framebufferSetup(EGL_Framebuffer * this, enum EGL_PixelFormat pixFmt,
|
||||
unsigned int width, unsigned int height);
|
||||
|
||||
void egl_framebufferBind(EGL_Framebuffer * this);
|
||||
|
||||
GLuint egl_framebufferGetTexture(EGL_Framebuffer * this);
|
||||
13
client/renderers/EGL/glsl.include.awk
Normal file
13
client/renderers/EGL/glsl.include.awk
Normal file
@@ -0,0 +1,13 @@
|
||||
BEGIN { FS="\"" }
|
||||
|
||||
function process(line, second) {
|
||||
if (line ~ /^#include[ \t]*".+"[ \t\r]*$/) {
|
||||
while (getline < second) {
|
||||
process($0, $2)
|
||||
}
|
||||
} else {
|
||||
print line
|
||||
}
|
||||
}
|
||||
|
||||
{ process($0, $2) }
|
||||
@@ -1,215 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "help.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "help.vert.h"
|
||||
#include "help.frag.h"
|
||||
#include "help_bg.frag.h"
|
||||
|
||||
struct EGL_Help
|
||||
{
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
|
||||
EGL_Texture * texture;
|
||||
EGL_Shader * shader;
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
_Atomic(LG_FontBitmap *) bmp;
|
||||
|
||||
bool shouldRender;
|
||||
int iwidth, iheight;
|
||||
float width, height;
|
||||
|
||||
// uniforms
|
||||
GLint uScreen , uSize;
|
||||
GLint uScreenBG, uSizeBG;
|
||||
};
|
||||
|
||||
bool egl_help_init(EGL_Help ** help, const LG_Font * font, LG_FontObj fontObj)
|
||||
{
|
||||
*help = (EGL_Help *)malloc(sizeof(EGL_Help));
|
||||
if (!*help)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Help");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*help, 0, sizeof(EGL_Help));
|
||||
|
||||
(*help)->font = font;
|
||||
(*help)->fontObj = fontObj;
|
||||
|
||||
if (!egl_texture_init(&(*help)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*help)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*help)->shaderBG))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help bg shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!egl_shader_compile((*help)->shader,
|
||||
b_shader_help_vert, b_shader_help_vert_size,
|
||||
b_shader_help_frag, b_shader_help_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*help)->shaderBG,
|
||||
b_shader_help_vert , b_shader_help_vert_size,
|
||||
b_shader_help_bg_frag, b_shader_help_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*help)->uSize = egl_shader_get_uniform_location((*help)->shader , "size" );
|
||||
(*help)->uScreen = egl_shader_get_uniform_location((*help)->shader , "screen");
|
||||
(*help)->uSizeBG = egl_shader_get_uniform_location((*help)->shaderBG, "size" );
|
||||
(*help)->uScreenBG = egl_shader_get_uniform_location((*help)->shaderBG, "screen");
|
||||
|
||||
if (!egl_model_init(&(*help)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*help)->model);
|
||||
egl_model_set_texture((*help)->model, (*help)->texture);
|
||||
|
||||
atomic_init(&(*help)->bmp, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_help_free(EGL_Help ** help)
|
||||
{
|
||||
if (!*help)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*help)->texture );
|
||||
egl_shader_free (&(*help)->shader );
|
||||
egl_shader_free (&(*help)->shaderBG);
|
||||
egl_model_free (&(*help)->model );
|
||||
|
||||
free(*help);
|
||||
*help = NULL;
|
||||
}
|
||||
|
||||
void egl_help_set_text(EGL_Help * help, const char * help_text)
|
||||
{
|
||||
LG_FontBitmap * bmp = NULL;
|
||||
if (help_text)
|
||||
{
|
||||
bmp = help->font->render(help->fontObj, 0xffffff00, help_text);
|
||||
if (!bmp)
|
||||
DEBUG_ERROR("Failed to render help text");
|
||||
} else
|
||||
help->shouldRender = false;
|
||||
|
||||
bmp = atomic_exchange(&help->bmp, bmp);
|
||||
if (bmp)
|
||||
{
|
||||
help->font->release(help->fontObj, bmp);
|
||||
}
|
||||
}
|
||||
|
||||
void egl_help_set_font(EGL_Help * help, LG_FontObj fontObj)
|
||||
{
|
||||
help->fontObj = fontObj;
|
||||
}
|
||||
|
||||
void egl_help_render(EGL_Help * help, const float scaleX, const float scaleY)
|
||||
{
|
||||
LG_FontBitmap * bmp = atomic_exchange(&help->bmp, NULL);
|
||||
if (bmp)
|
||||
{
|
||||
if (help->iwidth != bmp->width || help->iheight != bmp->height)
|
||||
{
|
||||
help->iwidth = bmp->width;
|
||||
help->iheight = bmp->height;
|
||||
help->width = (float)bmp->width;
|
||||
help->height = (float)bmp->height;
|
||||
|
||||
egl_texture_setup(
|
||||
help->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
egl_texture_update
|
||||
(
|
||||
help->texture,
|
||||
bmp->pixels
|
||||
);
|
||||
|
||||
help->shouldRender = true;
|
||||
help->font->release(help->fontObj, bmp);
|
||||
}
|
||||
|
||||
if (!help->shouldRender)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// render the background first
|
||||
egl_shader_use(help->shaderBG);
|
||||
glUniform2f(help->uScreenBG, scaleX , scaleY );
|
||||
glUniform2f(help->uSizeBG , help->width, help->height);
|
||||
egl_model_render(help->model);
|
||||
|
||||
// render the texture over the background
|
||||
egl_shader_use(help->shader);
|
||||
glUniform2f(help->uScreen, scaleX , scaleY );
|
||||
glUniform2f(help->uSize , help->width, help->height);
|
||||
egl_model_render(help->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -23,12 +23,11 @@
|
||||
#include "texture.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "ll.h"
|
||||
#include "common/ll.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
struct EGL_Model
|
||||
{
|
||||
@@ -37,8 +36,8 @@ struct EGL_Model
|
||||
size_t vertexCount;
|
||||
bool finish;
|
||||
|
||||
bool hasBuffer;
|
||||
GLuint buffer;
|
||||
GLuint vao;
|
||||
|
||||
EGL_Shader * shader;
|
||||
EGL_Texture * texture;
|
||||
@@ -53,23 +52,23 @@ struct FloatList
|
||||
|
||||
void update_uniform_bindings(EGL_Model * model);
|
||||
|
||||
bool egl_model_init(EGL_Model ** model)
|
||||
bool egl_modelInit(EGL_Model ** model)
|
||||
{
|
||||
*model = (EGL_Model *)malloc(sizeof(EGL_Model));
|
||||
*model = malloc(sizeof(**model));
|
||||
if (!*model)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Model");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*model, 0, sizeof(EGL_Model));
|
||||
memset(*model, 0, sizeof(**model));
|
||||
|
||||
(*model)->verticies = ll_new();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_model_free(EGL_Model ** model)
|
||||
void egl_modelFree(EGL_Model ** model)
|
||||
{
|
||||
if (!*model)
|
||||
return;
|
||||
@@ -83,14 +82,17 @@ void egl_model_free(EGL_Model ** model)
|
||||
}
|
||||
ll_free((*model)->verticies);
|
||||
|
||||
if ((*model)->hasBuffer)
|
||||
if ((*model)->buffer)
|
||||
glDeleteBuffers(1, &(*model)->buffer);
|
||||
|
||||
if ((*model)->vao)
|
||||
glDeleteVertexArrays(1, &(*model)->vao);
|
||||
|
||||
free(*model);
|
||||
*model = NULL;
|
||||
}
|
||||
|
||||
void egl_model_set_default(EGL_Model * model)
|
||||
void egl_modelSetDefault(EGL_Model * model, bool flipped)
|
||||
{
|
||||
static const GLfloat square[] =
|
||||
{
|
||||
@@ -100,7 +102,15 @@ void egl_model_set_default(EGL_Model * model)
|
||||
1.0f, 1.0f, 0.0f
|
||||
};
|
||||
|
||||
static const GLfloat uvs[] =
|
||||
static const GLfloat uvsNormal[] =
|
||||
{
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f
|
||||
};
|
||||
|
||||
static const GLfloat uvsFlipped[] =
|
||||
{
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
@@ -108,16 +118,37 @@ void egl_model_set_default(EGL_Model * model)
|
||||
1.0f, 0.0f
|
||||
};
|
||||
|
||||
egl_model_add_verticies(model, square, uvs, 4);
|
||||
egl_modelAddVerts(model, square, flipped ? uvsFlipped : uvsNormal, 4);
|
||||
}
|
||||
|
||||
void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count)
|
||||
void egl_modelAddVerts(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count)
|
||||
{
|
||||
struct FloatList * fl = (struct FloatList *)malloc(sizeof(struct FloatList));
|
||||
struct FloatList * fl = malloc(sizeof(*fl));
|
||||
if (!fl)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
fl->count = count;
|
||||
fl->v = (GLfloat *)malloc(sizeof(GLfloat) * count * 3);
|
||||
fl->u = (GLfloat *)malloc(sizeof(GLfloat) * count * 2);
|
||||
|
||||
fl->v = malloc(sizeof(GLfloat) * count * 3);
|
||||
if (!fl->v)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
free(fl);
|
||||
return;
|
||||
}
|
||||
|
||||
fl->u = malloc(sizeof(GLfloat) * count * 2);
|
||||
if (!fl->u)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
free(fl->v);
|
||||
free(fl);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3);
|
||||
|
||||
if (uvs)
|
||||
@@ -130,16 +161,21 @@ void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const
|
||||
model->vertexCount += count;
|
||||
}
|
||||
|
||||
void egl_model_render(EGL_Model * model)
|
||||
void egl_modelRender(EGL_Model * model)
|
||||
{
|
||||
if (!model->vertexCount)
|
||||
return;
|
||||
|
||||
if (model->rebuild)
|
||||
{
|
||||
if (model->hasBuffer)
|
||||
if (model->buffer)
|
||||
glDeleteBuffers(1, &model->buffer);
|
||||
|
||||
if (!model->vao)
|
||||
glGenVertexArrays(1, &model->vao);
|
||||
|
||||
glBindVertexArray(model->vao);
|
||||
|
||||
/* create a buffer large enough */
|
||||
glGenBuffers(1, &model->buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, model->buffer);
|
||||
@@ -149,59 +185,64 @@ void egl_model_render(EGL_Model * model)
|
||||
|
||||
/* buffer the verticies */
|
||||
struct FloatList * fl;
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
ll_lock(model->verticies);
|
||||
ll_forEachNL(model->verticies, item, fl)
|
||||
{
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 3, fl->v);
|
||||
offset += sizeof(GLfloat) * fl->count * 3;
|
||||
}
|
||||
|
||||
/* buffer the uvs */
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
ll_forEachNL(model->verticies, item, fl)
|
||||
{
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 2, fl->u);
|
||||
offset += sizeof(GLfloat) * fl->count * 2;
|
||||
}
|
||||
ll_unlock(model->verticies);
|
||||
|
||||
/* set up vertex arrays in the VAO */
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat) * model->vertexCount * 3));
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
model->rebuild = false;
|
||||
}
|
||||
|
||||
/* bind the model buffer and setup the pointers */
|
||||
glBindBuffer(GL_ARRAY_BUFFER, model->buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat) * model->vertexCount * 3));
|
||||
glBindVertexArray(model->vao);
|
||||
|
||||
if (model->shader)
|
||||
egl_shader_use(model->shader);
|
||||
egl_shaderUse(model->shader);
|
||||
|
||||
if (model->texture)
|
||||
egl_texture_bind(model->texture);
|
||||
egl_textureBind(model->texture);
|
||||
|
||||
/* draw the arrays */
|
||||
GLint offset = 0;
|
||||
struct FloatList * fl;
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
ll_lock(model->verticies);
|
||||
ll_forEachNL(model->verticies, item, fl)
|
||||
{
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, offset, fl->count);
|
||||
offset += fl->count;
|
||||
}
|
||||
ll_unlock(model->verticies);
|
||||
|
||||
/* unbind and cleanup */
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void egl_model_set_shader(EGL_Model * model, EGL_Shader * shader)
|
||||
void egl_modelSetShader(EGL_Model * model, EGL_Shader * shader)
|
||||
{
|
||||
model->shader = shader;
|
||||
update_uniform_bindings(model);
|
||||
}
|
||||
|
||||
void egl_model_set_texture(EGL_Model * model, EGL_Texture * texture)
|
||||
void egl_modelSetTexture(EGL_Model * model, EGL_Texture * texture)
|
||||
{
|
||||
model->texture = texture;
|
||||
update_uniform_bindings(model);
|
||||
@@ -212,6 +253,5 @@ void update_uniform_bindings(EGL_Model * model)
|
||||
if (!model->shader || !model->texture)
|
||||
return;
|
||||
|
||||
const int count = egl_texture_count(model->texture);
|
||||
egl_shader_associate_textures(model->shader, count);
|
||||
egl_shaderAssocTextures(model->shader, 1);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -24,16 +24,17 @@
|
||||
#include "shader.h"
|
||||
#include "texture.h"
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <GLES3/gl3.h>
|
||||
|
||||
typedef struct EGL_Model EGL_Model;
|
||||
typedef struct EGL_Texture EGL_Texture;
|
||||
|
||||
bool egl_model_init(EGL_Model ** model);
|
||||
void egl_model_free(EGL_Model ** model);
|
||||
bool egl_modelInit(EGL_Model ** model);
|
||||
void egl_modelFree(EGL_Model ** model);
|
||||
|
||||
void egl_model_set_default (EGL_Model * model);
|
||||
void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count);
|
||||
void egl_model_set_shader (EGL_Model * model, EGL_Shader * shader);
|
||||
void egl_model_set_texture (EGL_Model * model, EGL_Texture * texture);
|
||||
void egl_modelSetDefault (EGL_Model * model, bool flipped);
|
||||
void egl_modelAddVerts(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count);
|
||||
void egl_modelSetShader (EGL_Model * model, EGL_Shader * shader);
|
||||
void egl_modelSetTexture (EGL_Model * model, EGL_Texture * texture);
|
||||
|
||||
void egl_model_render(EGL_Model * model);
|
||||
void egl_modelRender(EGL_Model * model);
|
||||
|
||||
668
client/renderers/EGL/postprocess.c
Normal file
668
client/renderers/EGL/postprocess.c
Normal file
@@ -0,0 +1,668 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "postprocess.h"
|
||||
#include "filters.h"
|
||||
#include "app.h"
|
||||
#include "cimgui.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/array.h"
|
||||
#include "common/option.h"
|
||||
#include "common/paths.h"
|
||||
#include "common/stringlist.h"
|
||||
#include "common/stringutils.h"
|
||||
#include "common/vector.h"
|
||||
|
||||
static const EGL_FilterOps * EGL_Filters[] =
|
||||
{
|
||||
&egl_filterDownscaleOps,
|
||||
&egl_filterFFXFSR1Ops,
|
||||
&egl_filterFFXCASOps
|
||||
};
|
||||
|
||||
struct EGL_PostProcess
|
||||
{
|
||||
Vector filters;
|
||||
GLuint output;
|
||||
unsigned int outputX, outputY;
|
||||
_Atomic(bool) modified;
|
||||
|
||||
EGL_DesktopRects * rects;
|
||||
|
||||
StringList presets;
|
||||
char * presetDir;
|
||||
int activePreset;
|
||||
char presetEdit[128];
|
||||
char * presetError;
|
||||
};
|
||||
|
||||
void egl_postProcessEarlyInit(void)
|
||||
{
|
||||
static struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "eglFilter",
|
||||
.name = "order",
|
||||
.description = "The order of filters to use",
|
||||
.preset = true,
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = ""
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "preset",
|
||||
.description = "The initial filter preset to load",
|
||||
.type = OPTION_TYPE_STRING
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
option_register(options);
|
||||
|
||||
for (int i = 0; i < ARRAY_LENGTH(EGL_Filters); ++i)
|
||||
EGL_Filters[i]->earlyInit();
|
||||
}
|
||||
|
||||
static void loadPreset(struct EGL_PostProcess * this, const char * name);
|
||||
|
||||
static void loadPresetList(struct EGL_PostProcess * this)
|
||||
{
|
||||
DIR * dir = NULL;
|
||||
|
||||
alloc_sprintf(&this->presetDir, "%s/presets", lgConfigDir());
|
||||
if (!this->presetDir)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory for presets");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mkdir(this->presetDir, S_IRWXU) < 0 && errno != EEXIST)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create presets directory: %s", this->presetDir);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dir = opendir(this->presetDir);
|
||||
if (!dir)
|
||||
{
|
||||
DEBUG_ERROR("Failed to open presets directory: %s", this->presetDir);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
this->presets = stringlist_new(true);
|
||||
if (!this->presets)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory for preset list");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
struct dirent * entry;
|
||||
const char * preset = option_get_string("egl", "preset");
|
||||
this->activePreset = -1;
|
||||
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
if (entry->d_type != DT_REG)
|
||||
continue;
|
||||
|
||||
DEBUG_INFO("Found preset: %s", entry->d_name);
|
||||
char * name = strdup(entry->d_name);
|
||||
if (!name)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
goto fail;
|
||||
}
|
||||
stringlist_push(this->presets, name);
|
||||
|
||||
if (preset && strcmp(preset, name) == 0)
|
||||
this->activePreset = stringlist_count(this->presets) - 1;
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
|
||||
if (preset)
|
||||
{
|
||||
if (this->activePreset > -1)
|
||||
loadPreset(this, preset);
|
||||
else
|
||||
DEBUG_WARN("egl:preset '%s' does not exist", preset);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
free(this->presetDir);
|
||||
this->presetDir = NULL;
|
||||
if (dir)
|
||||
closedir(dir);
|
||||
if (this->presets)
|
||||
stringlist_free(&this->presets);
|
||||
}
|
||||
|
||||
static void presetError(struct EGL_PostProcess * this, char * message)
|
||||
{
|
||||
free(this->presetError);
|
||||
this->presetError = message;
|
||||
}
|
||||
|
||||
static bool savePreset(struct EGL_PostProcess * this, const char * name)
|
||||
{
|
||||
EGL_Filter * filter;
|
||||
vector_forEach(filter, &this->filters)
|
||||
egl_filterSaveState(filter);
|
||||
|
||||
size_t orderLen = 0;
|
||||
vector_forEach(filter, &this->filters)
|
||||
orderLen += strlen(filter->ops.id) + 1;
|
||||
|
||||
char order[orderLen];
|
||||
char * p = order;
|
||||
vector_forEach(filter, &this->filters)
|
||||
{
|
||||
strcpy(p, filter->ops.id);
|
||||
p += strlen(filter->ops.id);
|
||||
*p++ = ';';
|
||||
}
|
||||
if (p > order)
|
||||
p[-1] = '\0';
|
||||
option_set_string("eglFilter", "order", order);
|
||||
|
||||
char * path;
|
||||
alloc_sprintf(&path, "%s/%s", this->presetDir, name);
|
||||
if (!path)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE * file = fopen(path, "w");
|
||||
if (!file)
|
||||
{
|
||||
const char * strError = strerror(errno);
|
||||
DEBUG_ERROR("Failed to open preset \"%s\" for writing: %s", name, strError);
|
||||
free(path);
|
||||
|
||||
char * error;
|
||||
alloc_sprintf(&error, "Failed to save preset: %s\nError: %s", name, strError);
|
||||
if (error)
|
||||
presetError(this, error);
|
||||
return false;
|
||||
}
|
||||
free(path);
|
||||
|
||||
DEBUG_INFO("Saving preset: %s", name);
|
||||
option_dump_preset(file);
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int stringListIndex(StringList list, const char * str)
|
||||
{
|
||||
unsigned int count = stringlist_count(list);
|
||||
for (unsigned int i = 0; i < count; ++i)
|
||||
if (strcmp(stringlist_at(list, i), str) == 0)
|
||||
return i;
|
||||
return INT_MAX;
|
||||
}
|
||||
|
||||
static int compareFilterOrder(const void * a_, const void * b_, void * opaque)
|
||||
{
|
||||
const EGL_Filter * a = *(const EGL_Filter **)a_;
|
||||
const EGL_Filter * b = *(const EGL_Filter **)b_;
|
||||
StringList order = opaque;
|
||||
|
||||
return stringListIndex(order, a->ops.id) - stringListIndex(order, b->ops.id);
|
||||
}
|
||||
|
||||
static void reorderFilters(struct EGL_PostProcess * this)
|
||||
{
|
||||
StringList order = stringlist_new(false);
|
||||
if (!order)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
return;
|
||||
}
|
||||
|
||||
char * orderStr = strdup(option_get_string("eglFilter", "order"));
|
||||
if (!orderStr)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
stringlist_free(&order);
|
||||
return;
|
||||
}
|
||||
|
||||
char * p = orderStr;
|
||||
while (*p)
|
||||
{
|
||||
stringlist_push(order, p);
|
||||
char * end = strchr(p, ';');
|
||||
if (!end)
|
||||
break;
|
||||
*end = '\0';
|
||||
p = end + 1;
|
||||
}
|
||||
|
||||
qsort_r(vector_data(&this->filters), vector_size(&this->filters),
|
||||
sizeof(EGL_Filter *), compareFilterOrder, order);
|
||||
|
||||
stringlist_free(&order);
|
||||
free(orderStr);
|
||||
}
|
||||
|
||||
static void loadPreset(struct EGL_PostProcess * this, const char * name)
|
||||
{
|
||||
char * path;
|
||||
alloc_sprintf(&path, "%s/%s", this->presetDir, name);
|
||||
if (!path)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!option_load(path))
|
||||
{
|
||||
DEBUG_ERROR("Failed to load preset: %s", name);
|
||||
free(path);
|
||||
|
||||
char * error;
|
||||
alloc_sprintf(&error, "Failed to load preset: %s", name);
|
||||
if (error)
|
||||
presetError(this, error);
|
||||
return;
|
||||
}
|
||||
free(path);
|
||||
|
||||
DEBUG_INFO("Loading preset: %s", name);
|
||||
EGL_Filter * filter;
|
||||
vector_forEach(filter, &this->filters)
|
||||
egl_filterLoadState(filter);
|
||||
reorderFilters(this);
|
||||
}
|
||||
|
||||
static void savePresetAs(struct EGL_PostProcess * this)
|
||||
{
|
||||
if (!savePreset(this, this->presetEdit))
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < stringlist_count(this->presets); ++i)
|
||||
{
|
||||
DEBUG_INFO("Saw preset: %s", stringlist_at(this->presets, i));
|
||||
if (strcmp(stringlist_at(this->presets, i), this->presetEdit) == 0)
|
||||
{
|
||||
this->activePreset = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->activePreset = stringlist_push(this->presets, strdup(this->presetEdit));
|
||||
}
|
||||
|
||||
static void deletePreset(struct EGL_PostProcess * this)
|
||||
{
|
||||
char * path;
|
||||
alloc_sprintf(&path, "%s/%s", this->presetDir,
|
||||
stringlist_at(this->presets, this->activePreset));
|
||||
if (!path)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
return;
|
||||
}
|
||||
|
||||
unlink(path);
|
||||
free(path);
|
||||
stringlist_remove(this->presets, this->activePreset);
|
||||
if (this->activePreset >= stringlist_count(this->presets))
|
||||
this->activePreset = stringlist_count(this->presets) - 1;
|
||||
}
|
||||
|
||||
static bool presetsUI(struct EGL_PostProcess * this)
|
||||
{
|
||||
if (!this->presets)
|
||||
return false;
|
||||
|
||||
bool redraw = false;
|
||||
const char * active = "<none>";
|
||||
|
||||
if (this->activePreset >= 0)
|
||||
active = stringlist_at(this->presets, this->activePreset);
|
||||
|
||||
if (igBeginCombo("Preset name", active, 0))
|
||||
{
|
||||
for (unsigned i = 0; i < stringlist_count(this->presets); ++i)
|
||||
{
|
||||
bool selected = i == this->activePreset;
|
||||
if (igSelectable_Bool(stringlist_at(this->presets, i), selected, 0,
|
||||
(ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
this->activePreset = i;
|
||||
redraw = true;
|
||||
loadPreset(this, stringlist_at(this->presets, this->activePreset));
|
||||
}
|
||||
if (selected)
|
||||
igSetItemDefaultFocus();
|
||||
}
|
||||
igEndCombo();
|
||||
}
|
||||
if (igIsItemHovered(ImGuiHoveredFlags_None))
|
||||
igSetTooltip("Selecting a preset will load it");
|
||||
|
||||
if (igButton("Save preset", (ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
if (this->activePreset >= 0)
|
||||
savePreset(this, stringlist_at(this->presets, this->activePreset));
|
||||
else
|
||||
presetError(this, strdup("You must select a preset to save."));
|
||||
}
|
||||
|
||||
if (igIsItemHovered(ImGuiHoveredFlags_None) && this->activePreset >= 0)
|
||||
igSetTooltip("This will overwrite the preset named: %s",
|
||||
stringlist_at(this->presets, this->activePreset));
|
||||
|
||||
igSameLine(0.0f, -1.0f);
|
||||
|
||||
if (igButton("Save preset as...", (ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
this->presetEdit[0] = '\0';
|
||||
igOpenPopup_Str("Save preset as...", ImGuiPopupFlags_None);
|
||||
}
|
||||
|
||||
igSameLine(0.0f, -1.0f);
|
||||
|
||||
if (igButton("Delete preset", (ImVec2) { 0.0f, 0.0f }) && this->activePreset >= 0)
|
||||
deletePreset(this);
|
||||
|
||||
if (igBeginPopupModal("Save preset as...", NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
igText("Enter a name for the new preset:");
|
||||
|
||||
if (!igIsAnyItemActive())
|
||||
igSetKeyboardFocusHere(0);
|
||||
|
||||
if (igInputText("##name", this->presetEdit, sizeof(this->presetEdit),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue, NULL, NULL))
|
||||
{
|
||||
savePresetAs(this);
|
||||
igCloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (igButton("Save", (ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
savePresetAs(this);
|
||||
igCloseCurrentPopup();
|
||||
}
|
||||
|
||||
igSameLine(0.0f, -1.0f);
|
||||
if (igButton("Cancel", (ImVec2) { 0.0f, 0.0f }))
|
||||
igCloseCurrentPopup();
|
||||
|
||||
igEndPopup();
|
||||
}
|
||||
|
||||
if (this->presetError)
|
||||
igOpenPopup_Str("Preset error", ImGuiPopupFlags_None);
|
||||
|
||||
if (igBeginPopupModal("Preset error", NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
igText("%s", this->presetError);
|
||||
|
||||
if (!igIsAnyItemActive())
|
||||
igSetKeyboardFocusHere(0);
|
||||
|
||||
if (igButton("OK", (ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
free(this->presetError);
|
||||
this->presetError = NULL;
|
||||
igCloseCurrentPopup();
|
||||
}
|
||||
|
||||
igEndPopup();
|
||||
}
|
||||
|
||||
return redraw;
|
||||
}
|
||||
|
||||
static void drawDropTarget(void)
|
||||
{
|
||||
igPushStyleColor_Vec4(ImGuiCol_Separator, (ImVec4) { 1.0f, 1.0f, 0.0f, 1.0f });
|
||||
igSeparator();
|
||||
igPopStyleColor(1);
|
||||
}
|
||||
|
||||
static void configUI(void * opaque, int * id)
|
||||
{
|
||||
struct EGL_PostProcess * this = opaque;
|
||||
|
||||
bool redraw = false;
|
||||
redraw |= presetsUI(this);
|
||||
igSeparator();
|
||||
|
||||
static size_t mouseIdx = -1;
|
||||
static bool moving = false;
|
||||
static size_t moveIdx = 0;
|
||||
bool doMove = false;
|
||||
|
||||
ImVec2 window, pos;
|
||||
igGetWindowPos(&window);
|
||||
igGetMousePos(&pos);
|
||||
|
||||
EGL_Filter ** filters = vector_data(&this->filters);
|
||||
size_t count = vector_size(&this->filters);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
EGL_Filter * filter = filters[i];
|
||||
|
||||
if (moving && mouseIdx < moveIdx && i == mouseIdx)
|
||||
drawDropTarget();
|
||||
|
||||
igPushID_Ptr(filter);
|
||||
bool draw = igCollapsingHeader_BoolPtr(filter->ops.name, NULL, 0);
|
||||
if (igIsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
|
||||
mouseIdx = i;
|
||||
|
||||
bool active = igIsItemActive();
|
||||
if (draw)
|
||||
redraw |= egl_filterImguiConfig(filter);
|
||||
igPopID();
|
||||
|
||||
if (moving)
|
||||
{
|
||||
if (!igIsMouseDragging(ImGuiMouseButton_Left, -1.0f))
|
||||
{
|
||||
moving = false;
|
||||
doMove = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (active && igIsMouseDragging(ImGuiMouseButton_Left, -1.0f))
|
||||
{
|
||||
moveIdx = mouseIdx;
|
||||
moving = true;
|
||||
}
|
||||
|
||||
if (moving && mouseIdx > moveIdx && i == mouseIdx)
|
||||
drawDropTarget();
|
||||
}
|
||||
|
||||
if (moving)
|
||||
{
|
||||
igSetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
igSetTooltip(filters[moveIdx]->ops.name);
|
||||
}
|
||||
|
||||
if (doMove)
|
||||
{
|
||||
EGL_Filter * tmp = filters[moveIdx];
|
||||
if (mouseIdx > moveIdx) // moving down
|
||||
memmove(filters + moveIdx, filters + moveIdx + 1, (mouseIdx - moveIdx) * sizeof(EGL_Filter *));
|
||||
else // moving up
|
||||
memmove(filters + mouseIdx + 1, filters + mouseIdx, (moveIdx - mouseIdx) * sizeof(EGL_Filter *));
|
||||
filters[mouseIdx] = tmp;
|
||||
}
|
||||
|
||||
if (redraw)
|
||||
{
|
||||
atomic_store(&this->modified, true);
|
||||
app_invalidateWindow(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool egl_postProcessInit(EGL_PostProcess ** pp)
|
||||
{
|
||||
EGL_PostProcess * this = calloc(1, sizeof(*this));
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vector_create(&this->filters, sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate the filter list");
|
||||
goto error_this;
|
||||
}
|
||||
|
||||
if (!egl_desktopRectsInit(&this->rects, 1))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop rects");
|
||||
goto error_filters;
|
||||
}
|
||||
|
||||
loadPresetList(this);
|
||||
reorderFilters(this);
|
||||
app_overlayConfigRegisterTab("EGL Filters", configUI, this);
|
||||
|
||||
*pp = this;
|
||||
return true;
|
||||
|
||||
error_filters:
|
||||
vector_destroy(&this->filters);
|
||||
|
||||
error_this:
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
void egl_postProcessFree(EGL_PostProcess ** pp)
|
||||
{
|
||||
if (!*pp)
|
||||
return;
|
||||
|
||||
EGL_PostProcess * this = *pp;
|
||||
|
||||
EGL_Filter ** filter;
|
||||
vector_forEachRef(filter, &this->filters)
|
||||
egl_filterFree(filter);
|
||||
vector_destroy(&this->filters);
|
||||
|
||||
free(this->presetDir);
|
||||
if (this->presets)
|
||||
stringlist_free(&this->presets);
|
||||
|
||||
egl_desktopRectsFree(&this->rects);
|
||||
free(this->presetError);
|
||||
free(this);
|
||||
*pp = NULL;
|
||||
}
|
||||
|
||||
bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops)
|
||||
{
|
||||
EGL_Filter * filter;
|
||||
if (!egl_filterInit(ops, &filter))
|
||||
return false;
|
||||
|
||||
vector_push(&this->filters, &filter);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_postProcessConfigModified(EGL_PostProcess * this)
|
||||
{
|
||||
return atomic_load(&this->modified);
|
||||
}
|
||||
|
||||
bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
|
||||
EGL_DesktopRects * rects, int desktopWidth, int desktopHeight,
|
||||
unsigned int targetX, unsigned int targetY)
|
||||
{
|
||||
if (targetX == 0 && targetY == 0)
|
||||
DEBUG_FATAL("targetX || targetY == 0");
|
||||
|
||||
EGL_Filter * lastFilter = NULL;
|
||||
unsigned int sizeX, sizeY;
|
||||
|
||||
GLuint texture;
|
||||
if (egl_textureGet(tex, &texture, &sizeX, &sizeY) != EGL_TEX_STATUS_OK)
|
||||
return false;
|
||||
|
||||
if (atomic_exchange(&this->modified, false))
|
||||
{
|
||||
rects = this->rects;
|
||||
egl_desktopRectsUpdate(rects, NULL, desktopWidth, desktopHeight);
|
||||
}
|
||||
|
||||
GLfloat matrix[6];
|
||||
egl_desktopRectsMatrix(matrix, desktopWidth, desktopHeight, 0.0f, 0.0f,
|
||||
1.0f, 1.0f, LG_ROTATE_0);
|
||||
|
||||
EGL_FilterRects filterRects = {
|
||||
.rects = rects,
|
||||
.matrix = matrix,
|
||||
.width = desktopWidth,
|
||||
.height = desktopHeight,
|
||||
};
|
||||
|
||||
EGL_Filter * filter;
|
||||
vector_forEach(filter, &this->filters)
|
||||
{
|
||||
egl_filterSetOutputResHint(filter, targetX, targetY);
|
||||
|
||||
if (!egl_filterSetup(filter, tex->format.pixFmt, sizeX, sizeY) ||
|
||||
!egl_filterPrepare(filter))
|
||||
continue;
|
||||
|
||||
texture = egl_filterRun(filter, &filterRects, texture);
|
||||
egl_filterGetOutputRes(filter, &sizeX, &sizeY);
|
||||
|
||||
if (lastFilter)
|
||||
egl_filterRelease(lastFilter);
|
||||
|
||||
lastFilter = filter;
|
||||
}
|
||||
|
||||
this->output = texture;
|
||||
this->outputX = sizeX;
|
||||
this->outputY = sizeY;
|
||||
return true;
|
||||
}
|
||||
|
||||
GLuint egl_postProcessGetOutput(EGL_PostProcess * this,
|
||||
unsigned int * outputX, unsigned int * outputY)
|
||||
{
|
||||
*outputX = this->outputX;
|
||||
*outputY = this->outputY;
|
||||
return this->output;
|
||||
}
|
||||
47
client/renderers/EGL/postprocess.h
Normal file
47
client/renderers/EGL/postprocess.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "desktop_rects.h"
|
||||
#include "filter.h"
|
||||
#include "texture.h"
|
||||
|
||||
typedef struct EGL_PostProcess EGL_PostProcess;
|
||||
|
||||
void egl_postProcessEarlyInit(void);
|
||||
|
||||
bool egl_postProcessInit(EGL_PostProcess ** pp);
|
||||
void egl_postProcessFree(EGL_PostProcess ** pp);
|
||||
|
||||
/* create and add a filter to this processor */
|
||||
bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops);
|
||||
|
||||
/* returns true if the configuration was modified since the last run */
|
||||
bool egl_postProcessConfigModified(EGL_PostProcess * this);
|
||||
|
||||
/* apply the filters to the supplied texture
|
||||
* targetX/Y is the final target output dimension hint if scalers are present */
|
||||
bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
|
||||
EGL_DesktopRects * rects, int desktopWidth, int desktopHeight,
|
||||
unsigned int targetX, unsigned int targetY);
|
||||
|
||||
GLuint egl_postProcessGetOutput(EGL_PostProcess * this,
|
||||
unsigned int * outputX, unsigned int * outputY);
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -30,34 +30,41 @@ struct EGL_Shader
|
||||
{
|
||||
bool hasShader;
|
||||
GLuint shader;
|
||||
|
||||
EGL_Uniform * uniforms;
|
||||
int uniformCount;
|
||||
int uniformUsed;
|
||||
};
|
||||
|
||||
bool egl_shader_init(EGL_Shader ** this)
|
||||
bool egl_shaderInit(EGL_Shader ** this)
|
||||
{
|
||||
*this = (EGL_Shader *)malloc(sizeof(EGL_Shader));
|
||||
*this = calloc(1, sizeof(EGL_Shader));
|
||||
if (!*this)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*this, 0, sizeof(EGL_Shader));
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_shader_free(EGL_Shader ** this)
|
||||
void egl_shaderFree(EGL_Shader ** shader)
|
||||
{
|
||||
if (!*this)
|
||||
EGL_Shader * this = *shader;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
if ((*this)->hasShader)
|
||||
glDeleteProgram((*this)->shader);
|
||||
if (this->hasShader)
|
||||
glDeleteProgram(this->shader);
|
||||
|
||||
free(*this);
|
||||
*this = NULL;
|
||||
egl_shaderFreeUniforms(this);
|
||||
free(this->uniforms);
|
||||
|
||||
free(this);
|
||||
*shader = NULL;
|
||||
}
|
||||
|
||||
bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * fragment_file)
|
||||
bool egl_shaderLoad(EGL_Shader * this, const char * vertex_file, const char * fragment_file)
|
||||
{
|
||||
char * vertex_code, * fragment_code;
|
||||
size_t vertex_size, fragment_size;
|
||||
@@ -79,13 +86,14 @@ bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * f
|
||||
|
||||
DEBUG_INFO("Loaded fragment shader: %s", fragment_file);
|
||||
|
||||
bool ret = egl_shader_compile(this, vertex_code, vertex_size, fragment_code, fragment_size);
|
||||
bool ret = egl_shaderCompile(this, vertex_code, vertex_size, fragment_code, fragment_size);
|
||||
free(vertex_code);
|
||||
free(fragment_code);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool egl_shader_compile(EGL_Shader * this, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size)
|
||||
bool egl_shaderCompile(EGL_Shader * this, const char * vertex_code,
|
||||
size_t vertex_size, const char * fragment_code, size_t fragment_size)
|
||||
{
|
||||
if (this->hasShader)
|
||||
{
|
||||
@@ -111,10 +119,15 @@ bool egl_shader_compile(EGL_Shader * this, const char * vertex_code, size_t vert
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
if (!log)
|
||||
DEBUG_ERROR("out of memory");
|
||||
else
|
||||
{
|
||||
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
@@ -137,10 +150,15 @@ bool egl_shader_compile(EGL_Shader * this, const char * vertex_code, size_t vert
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
if (!log)
|
||||
DEBUG_ERROR("out of memory");
|
||||
else
|
||||
{
|
||||
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
glDeleteShader(fragmentShader);
|
||||
@@ -186,15 +204,286 @@ bool egl_shader_compile(EGL_Shader * this, const char * vertex_code, size_t vert
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_shader_use(EGL_Shader * this)
|
||||
void egl_shaderSetUniforms(EGL_Shader * this, EGL_Uniform * uniforms, int count)
|
||||
{
|
||||
egl_shaderFreeUniforms(this);
|
||||
if (count > this->uniformCount)
|
||||
{
|
||||
free(this->uniforms);
|
||||
this->uniforms = malloc(sizeof(*this->uniforms) * count);
|
||||
if (!this->uniforms)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
this->uniformCount = count;
|
||||
}
|
||||
|
||||
this->uniformUsed = count;
|
||||
memcpy(this->uniforms, uniforms, sizeof(*this->uniforms) * count);
|
||||
|
||||
for(int i = 0; i < this->uniformUsed; ++i)
|
||||
{
|
||||
switch(this->uniforms[i].type)
|
||||
{
|
||||
case EGL_UNIFORM_TYPE_1FV:
|
||||
case EGL_UNIFORM_TYPE_2FV:
|
||||
case EGL_UNIFORM_TYPE_3FV:
|
||||
case EGL_UNIFORM_TYPE_4FV:
|
||||
case EGL_UNIFORM_TYPE_1IV:
|
||||
case EGL_UNIFORM_TYPE_2IV:
|
||||
case EGL_UNIFORM_TYPE_3IV:
|
||||
case EGL_UNIFORM_TYPE_4IV:
|
||||
case EGL_UNIFORM_TYPE_1UIV:
|
||||
case EGL_UNIFORM_TYPE_2UIV:
|
||||
case EGL_UNIFORM_TYPE_3UIV:
|
||||
case EGL_UNIFORM_TYPE_4UIV:
|
||||
countedBufferAddRef(this->uniforms[i].v);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M2FV:
|
||||
case EGL_UNIFORM_TYPE_M3FV:
|
||||
case EGL_UNIFORM_TYPE_M4FV:
|
||||
case EGL_UNIFORM_TYPE_M2x3FV:
|
||||
case EGL_UNIFORM_TYPE_M3x2FV:
|
||||
case EGL_UNIFORM_TYPE_M2x4FV:
|
||||
case EGL_UNIFORM_TYPE_M4x2FV:
|
||||
case EGL_UNIFORM_TYPE_M3x4FV:
|
||||
case EGL_UNIFORM_TYPE_M4x3FV:
|
||||
countedBufferAddRef(this->uniforms[i].m.v);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void egl_shaderFreeUniforms(EGL_Shader * this)
|
||||
{
|
||||
for(int i = 0; i < this->uniformUsed; ++i)
|
||||
{
|
||||
switch(this->uniforms[i].type)
|
||||
{
|
||||
case EGL_UNIFORM_TYPE_1FV:
|
||||
case EGL_UNIFORM_TYPE_2FV:
|
||||
case EGL_UNIFORM_TYPE_3FV:
|
||||
case EGL_UNIFORM_TYPE_4FV:
|
||||
case EGL_UNIFORM_TYPE_1IV:
|
||||
case EGL_UNIFORM_TYPE_2IV:
|
||||
case EGL_UNIFORM_TYPE_3IV:
|
||||
case EGL_UNIFORM_TYPE_4IV:
|
||||
case EGL_UNIFORM_TYPE_1UIV:
|
||||
case EGL_UNIFORM_TYPE_2UIV:
|
||||
case EGL_UNIFORM_TYPE_3UIV:
|
||||
case EGL_UNIFORM_TYPE_4UIV:
|
||||
countedBufferRelease(&this->uniforms[i].v);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M2FV:
|
||||
case EGL_UNIFORM_TYPE_M3FV:
|
||||
case EGL_UNIFORM_TYPE_M4FV:
|
||||
case EGL_UNIFORM_TYPE_M2x3FV:
|
||||
case EGL_UNIFORM_TYPE_M3x2FV:
|
||||
case EGL_UNIFORM_TYPE_M2x4FV:
|
||||
case EGL_UNIFORM_TYPE_M4x2FV:
|
||||
case EGL_UNIFORM_TYPE_M3x4FV:
|
||||
case EGL_UNIFORM_TYPE_M4x3FV:
|
||||
countedBufferRelease(&this->uniforms[i].m.v);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->uniformUsed = 0;
|
||||
}
|
||||
|
||||
void egl_shaderUse(EGL_Shader * this)
|
||||
{
|
||||
if (this->hasShader)
|
||||
glUseProgram(this->shader);
|
||||
else
|
||||
DEBUG_ERROR("Shader program has not been compiled");
|
||||
|
||||
for(int i = 0; i < this->uniformUsed; ++i)
|
||||
{
|
||||
EGL_Uniform * uniform = &this->uniforms[i];
|
||||
switch(uniform->type)
|
||||
{
|
||||
case EGL_UNIFORM_TYPE_1F:
|
||||
glUniform1f(uniform->location, uniform->f[0]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_2F:
|
||||
glUniform2f(uniform->location, uniform->f[0], uniform->f[1]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_3F:
|
||||
glUniform3f(uniform->location, uniform->f[0], uniform->f[1],
|
||||
uniform->f[2]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_4F:
|
||||
glUniform4f(uniform->location, uniform->f[0], uniform->f[1],
|
||||
uniform->f[2], uniform->f[3]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_1I:
|
||||
glUniform1i(uniform->location, uniform->i[0]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_2I:
|
||||
glUniform2i(uniform->location, uniform->i[0], uniform->i[1]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_3I:
|
||||
glUniform3i(uniform->location, uniform->i[0], uniform->i[1],
|
||||
uniform->i[2]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_4I:
|
||||
glUniform4i(uniform->location, uniform->i[0], uniform->i[1],
|
||||
uniform->i[2], uniform->i[3]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_1UI:
|
||||
glUniform1ui(uniform->location, uniform->ui[0]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_2UI:
|
||||
glUniform2ui(uniform->location, uniform->ui[0], uniform->ui[1]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_3UI:
|
||||
glUniform3ui(uniform->location, uniform->ui[0], uniform->ui[1],
|
||||
uniform->ui[2]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_4UI:
|
||||
glUniform4ui(uniform->location, uniform->ui[0], uniform->ui[1],
|
||||
uniform->ui[2], uniform->ui[3]);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_1FV:
|
||||
glUniform1fv(uniform->location, uniform->v->size / sizeof(GLfloat),
|
||||
(const GLfloat *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_2FV:
|
||||
glUniform2fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 2,
|
||||
(const GLfloat *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_3FV:
|
||||
glUniform3fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 3,
|
||||
(const GLfloat *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_4FV:
|
||||
glUniform4fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 4,
|
||||
(const GLfloat *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_1IV:
|
||||
glUniform1iv(uniform->location, uniform->v->size / sizeof(GLint),
|
||||
(const GLint *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_2IV:
|
||||
glUniform2iv(uniform->location, uniform->v->size / sizeof(GLint) / 2,
|
||||
(const GLint *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_3IV:
|
||||
glUniform3iv(uniform->location, uniform->v->size / sizeof(GLint) / 3,
|
||||
(const GLint *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_4IV:
|
||||
glUniform4iv(uniform->location, uniform->v->size / sizeof(GLint) / 4,
|
||||
(const GLint *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_1UIV:
|
||||
glUniform1uiv(uniform->location, uniform->v->size / sizeof(GLuint),
|
||||
(const GLuint *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_2UIV:
|
||||
glUniform2uiv(uniform->location, uniform->v->size / sizeof(GLuint) / 2,
|
||||
(const GLuint *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_3UIV:
|
||||
glUniform3uiv(uniform->location, uniform->v->size / sizeof(GLuint) / 3,
|
||||
(const GLuint *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_4UIV:
|
||||
glUniform4uiv(uniform->location, uniform->v->size / sizeof(GLuint) / 4,
|
||||
(const GLuint *)uniform->v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M2FV:
|
||||
glUniformMatrix2fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 2,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M3FV:
|
||||
glUniformMatrix3fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 3,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M4FV:
|
||||
glUniformMatrix4fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 4,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M2x3FV:
|
||||
glUniformMatrix2x3fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 6,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M3x2FV:
|
||||
glUniformMatrix3x2fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 6,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M2x4FV:
|
||||
glUniformMatrix2x4fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 8,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M4x2FV:
|
||||
glUniformMatrix4x2fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 8,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M3x4FV:
|
||||
glUniformMatrix3x4fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 12,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
|
||||
case EGL_UNIFORM_TYPE_M4x3FV:
|
||||
glUniformMatrix4x3fv(uniform->location,
|
||||
uniform->v->size / sizeof(GLfloat) / 12,
|
||||
uniform->m.transpose, (const GLfloat *)uniform->m.v->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void egl_shader_associate_textures(EGL_Shader * this, const int count)
|
||||
void egl_shaderAssocTextures(EGL_Shader * this, const int count)
|
||||
{
|
||||
char name[] = "sampler1";
|
||||
glUseProgram(this->shader);
|
||||
@@ -212,7 +501,7 @@ void egl_shader_associate_textures(EGL_Shader * this, const int count)
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
GLint egl_shader_get_uniform_location(EGL_Shader * this, const char * name)
|
||||
GLint egl_shaderGetUniform(EGL_Shader * this, const char * name)
|
||||
{
|
||||
if (!this->shader)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -23,16 +23,91 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <GLES3/gl3.h>
|
||||
|
||||
#include "common/countedbuffer.h"
|
||||
|
||||
typedef struct EGL_Shader EGL_Shader;
|
||||
|
||||
bool egl_shader_init(EGL_Shader ** shader);
|
||||
void egl_shader_free(EGL_Shader ** shader);
|
||||
enum EGL_UniformType
|
||||
{
|
||||
EGL_UNIFORM_TYPE_1F,
|
||||
EGL_UNIFORM_TYPE_2F,
|
||||
EGL_UNIFORM_TYPE_3F,
|
||||
EGL_UNIFORM_TYPE_4F,
|
||||
EGL_UNIFORM_TYPE_1I,
|
||||
EGL_UNIFORM_TYPE_2I,
|
||||
EGL_UNIFORM_TYPE_3I,
|
||||
EGL_UNIFORM_TYPE_4I,
|
||||
EGL_UNIFORM_TYPE_1UI,
|
||||
EGL_UNIFORM_TYPE_2UI,
|
||||
EGL_UNIFORM_TYPE_3UI,
|
||||
EGL_UNIFORM_TYPE_4UI,
|
||||
|
||||
bool egl_shader_load (EGL_Shader * model, const char * vertex_file, const char * fragment_file);
|
||||
bool egl_shader_compile(EGL_Shader * model, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size);
|
||||
void egl_shader_use (EGL_Shader * shader);
|
||||
// vectors
|
||||
EGL_UNIFORM_TYPE_1FV,
|
||||
EGL_UNIFORM_TYPE_2FV,
|
||||
EGL_UNIFORM_TYPE_3FV,
|
||||
EGL_UNIFORM_TYPE_4FV,
|
||||
EGL_UNIFORM_TYPE_1IV,
|
||||
EGL_UNIFORM_TYPE_2IV,
|
||||
EGL_UNIFORM_TYPE_3IV,
|
||||
EGL_UNIFORM_TYPE_4IV,
|
||||
EGL_UNIFORM_TYPE_1UIV,
|
||||
EGL_UNIFORM_TYPE_2UIV,
|
||||
EGL_UNIFORM_TYPE_3UIV,
|
||||
EGL_UNIFORM_TYPE_4UIV,
|
||||
|
||||
void egl_shader_associate_textures(EGL_Shader * shader, const int count);
|
||||
GLint egl_shader_get_uniform_location(EGL_Shader * shader, const char * name);
|
||||
// matrices
|
||||
EGL_UNIFORM_TYPE_M2FV,
|
||||
EGL_UNIFORM_TYPE_M3FV,
|
||||
EGL_UNIFORM_TYPE_M4FV,
|
||||
EGL_UNIFORM_TYPE_M2x3FV,
|
||||
EGL_UNIFORM_TYPE_M3x2FV,
|
||||
EGL_UNIFORM_TYPE_M2x4FV,
|
||||
EGL_UNIFORM_TYPE_M4x2FV,
|
||||
EGL_UNIFORM_TYPE_M3x4FV,
|
||||
EGL_UNIFORM_TYPE_M4x3FV
|
||||
};
|
||||
|
||||
typedef struct EGL_Uniform
|
||||
{
|
||||
enum EGL_UniformType type;
|
||||
GLint location;
|
||||
|
||||
union
|
||||
{
|
||||
GLfloat f [4];
|
||||
GLint i [4];
|
||||
GLuint ui[4];
|
||||
|
||||
CountedBuffer * v;
|
||||
|
||||
struct
|
||||
{
|
||||
CountedBuffer * v;
|
||||
GLboolean transpose;
|
||||
}
|
||||
m;
|
||||
};
|
||||
}
|
||||
EGL_Uniform;
|
||||
|
||||
bool egl_shaderInit(EGL_Shader ** shader);
|
||||
void egl_shaderFree(EGL_Shader ** shader);
|
||||
|
||||
bool egl_shaderLoad(EGL_Shader * model, const char * vertex_file,
|
||||
const char * fragment_file);
|
||||
|
||||
bool egl_shaderCompile(EGL_Shader * model, const char * vertex_code,
|
||||
size_t vertex_size, const char * fragment_code, size_t fragment_size);
|
||||
|
||||
void egl_shaderSetUniforms(EGL_Shader * shader, EGL_Uniform * uniforms,
|
||||
int count);
|
||||
void egl_shaderFreeUniforms(EGL_Shader * shader);
|
||||
|
||||
void egl_shaderUse(EGL_Shader * shader);
|
||||
|
||||
void egl_shaderAssocTextures(EGL_Shader * shader, const int count);
|
||||
|
||||
GLint egl_shaderGetUniform(EGL_Shader * shader, const char * name);
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
in highp vec2 sz;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texelFetch(sampler1, ivec2(uv * sz), 0);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec2 screen;
|
||||
uniform ivec2 size;
|
||||
uniform vec4 color;
|
||||
|
||||
out highp vec2 uv;
|
||||
out highp vec2 sz;
|
||||
out highp vec4 c;
|
||||
|
||||
void main()
|
||||
{
|
||||
sz = vec2(size) + 0.5;
|
||||
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
gl_Position.xy *= screen.xy * sz;
|
||||
|
||||
uv = vertexUV;
|
||||
c = color;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec4 c;
|
||||
out highp vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = c;
|
||||
}
|
||||
15
client/renderers/EGL/shader/basic.vert
Normal file
15
client/renderers/EGL/shader/basic.vert
Normal file
@@ -0,0 +1,15 @@
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec2 vertex;
|
||||
out vec2 fragCoord;
|
||||
|
||||
uniform vec2 desktopSize;
|
||||
uniform mat3x2 transform;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 pos = transform * vec3(vertex, 1.0);
|
||||
gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);
|
||||
fragCoord = vertex / desktopSize;
|
||||
}
|
||||
37
client/renderers/EGL/shader/color_blind.h
Normal file
37
client/renderers/EGL/shader/color_blind.h
Normal file
@@ -0,0 +1,37 @@
|
||||
vec4 cbTransform(vec4 color, int cbMode)
|
||||
{
|
||||
float L = (17.8824000 * color.r) + (43.516100 * color.g) + (4.11935 * color.b);
|
||||
float M = (03.4556500 * color.r) + (27.155400 * color.g) + (3.86714 * color.b);
|
||||
float S = (00.0299566 * color.r) + (00.184309 * color.g) + (1.46709 * color.b);
|
||||
float l, m, s;
|
||||
|
||||
if (cbMode == 1) // Protanope
|
||||
{
|
||||
l = 0.0f * L + 2.02344f * M + -2.52581f * S;
|
||||
m = 0.0f * L + 1.0f * M + 0.0f * S;
|
||||
s = 0.0f * L + 0.0f * M + 1.0f * S;
|
||||
}
|
||||
else if (cbMode == 2) // Deuteranope
|
||||
{
|
||||
l = 1.000000 * L + 0.0f * M + 0.00000 * S;
|
||||
m = 0.494207 * L + 0.0f * M + 1.24827 * S;
|
||||
s = 0.000000 * L + 0.0f * M + 1.00000 * S;
|
||||
}
|
||||
else if (cbMode == 3) // Tritanope
|
||||
{
|
||||
l = 1.000000 * L + 0.000000 * M + 0.0 * S;
|
||||
m = 0.000000 * L + 1.000000 * M + 0.0 * S;
|
||||
s = -0.395913 * L + 0.801109 * M + 0.0 * S;
|
||||
}
|
||||
|
||||
vec4 error;
|
||||
error.r = ( 0.080944447900 * l) + (-0.13050440900 * m) + ( 0.116721066 * s);
|
||||
error.g = (-0.010248533500 * l) + ( 0.05401932660 * m) + (-0.113614708 * s);
|
||||
error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + ( 0.693511405 * s);
|
||||
error.a = 0.0;
|
||||
|
||||
error = color - error;
|
||||
color.g += (error.r * 0.7) + (error.g * 1.0);
|
||||
color.b += (error.r * 0.7) + (error.b * 1.0);
|
||||
return color;
|
||||
}
|
||||
32
client/renderers/EGL/shader/compat.h
Normal file
32
client/renderers/EGL/shader/compat.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#if __VERSION__ == 300
|
||||
vec4 textureGather(sampler2D tex, vec2 uv, int comp)
|
||||
{
|
||||
vec4 c0 = textureOffset(tex, uv, ivec2(0,1));
|
||||
vec4 c1 = textureOffset(tex, uv, ivec2(1,1));
|
||||
vec4 c2 = textureOffset(tex, uv, ivec2(1,0));
|
||||
vec4 c3 = textureOffset(tex, uv, ivec2(0,0));
|
||||
return vec4(c0[comp], c1[comp], c2[comp],c3[comp]);
|
||||
}
|
||||
#elif __VERSION__ < 300
|
||||
vec4 textureGather(sampler2D tex, vec2 uv, int comp)
|
||||
{
|
||||
vec4 c3 = texture2D(tex, uv);
|
||||
return vec4(c3[comp], c3[comp], c3[comp],c3[comp]);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __VERSION__ < 310
|
||||
uint bitfieldExtract(uint val, int off, int size)
|
||||
{
|
||||
uint mask = uint((1 << size) - 1);
|
||||
return uint(val >> off) & mask;
|
||||
}
|
||||
|
||||
uint bitfieldInsert(uint a, uint b, int c, int d)
|
||||
{
|
||||
uint mask = ~(0xffffffffu << d) << c;
|
||||
mask = ~mask;
|
||||
a &= mask;
|
||||
return a | (b << c);
|
||||
}
|
||||
#endif
|
||||
@@ -1,12 +1,13 @@
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec4 mouse;
|
||||
uniform lowp int rotate;
|
||||
uniform int rotate;
|
||||
|
||||
out highp vec2 uv;
|
||||
out vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
@@ -38,6 +39,6 @@ void main()
|
||||
gl_Position.y = muv.x;
|
||||
}
|
||||
|
||||
gl_Position.w = 1.0;
|
||||
gl_Position.zw = vec2(0.0, 1.0);
|
||||
uv = vertexUV;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
uniform float scale;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp vec4 tmp = texture(sampler1, uv);
|
||||
vec4 tmp;
|
||||
if (scale > 1.0)
|
||||
{
|
||||
vec2 ts = vec2(textureSize(sampler1, 0));
|
||||
vec2 px = (uv - (0.5 / ts)) * ts;
|
||||
if (px.x < 0.0 || px.y < 0.0)
|
||||
discard;
|
||||
|
||||
tmp = texelFetch(sampler1, ivec2(px), 0);
|
||||
}
|
||||
else
|
||||
tmp = texture(sampler1, uv);
|
||||
|
||||
if (tmp.rgb == vec3(0.0, 0.0, 0.0))
|
||||
discard;
|
||||
|
||||
color = tmp;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user