mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-02-18 00:29:48 +00:00
Compare commits
818 Commits
a9
...
Release/B2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76710ef201 | ||
|
|
e20c8a5cc7 | ||
|
|
4f4d2dbf42 | ||
|
|
8692e9af80 | ||
|
|
7d2b39058c | ||
|
|
6927dbecd2 | ||
|
|
f9b6dcc986 | ||
|
|
5c912e3c27 | ||
|
|
7e362050f7 | ||
|
|
10fbdeb294 | ||
|
|
72d70e8322 | ||
|
|
c66a339bbc | ||
|
|
1c7961daeb | ||
|
|
cdc3384883 | ||
|
|
969effedde | ||
|
|
dc4d1d49fa | ||
|
|
4e1f947a09 | ||
|
|
15d1a74291 | ||
|
|
7dba6b9b08 | ||
|
|
a5ad531004 | ||
|
|
c119b3dcca | ||
|
|
e2f2437ef4 | ||
|
|
b2980fea63 | ||
|
|
2b518690b8 | ||
|
|
92aca75792 | ||
|
|
64fdb8b7bb | ||
|
|
431ae3fc55 | ||
|
|
380b5df9f9 | ||
|
|
c7330167cf | ||
|
|
ca02e1aba9 | ||
|
|
ca4b1f5592 | ||
|
|
0cf1e27709 | ||
|
|
045932ce77 | ||
|
|
bf5481446b | ||
|
|
e3f97e384b | ||
|
|
76e119f8ad | ||
|
|
bfb12c74fb | ||
|
|
fa50b7824c | ||
|
|
da8b2d0cec | ||
|
|
74649ddb96 | ||
|
|
4619ddef5d | ||
|
|
ea74ee6e25 | ||
|
|
ecd73aa670 | ||
|
|
10d9678b3d | ||
|
|
e08d3afdbc | ||
|
|
9a6b598438 | ||
|
|
d9a80b16f0 | ||
|
|
90d0cd873d | ||
|
|
82e0b7b6ab | ||
|
|
2e1b0f2550 | ||
|
|
3302d353cf | ||
|
|
1899d9f1da | ||
|
|
fb9b772db0 | ||
|
|
302b988524 | ||
|
|
19c2fe9b5e | ||
|
|
88d25ee98c | ||
|
|
0f2ecdf5f1 | ||
|
|
3511fb8d59 | ||
|
|
1d6d640b6e | ||
|
|
977d7b277d | ||
|
|
be7820303f | ||
|
|
43503222c7 | ||
|
|
85b8c12abf | ||
|
|
7af053497e | ||
|
|
9e3a42cb62 | ||
|
|
aa32c5ffad | ||
|
|
62d1bd1ea2 | ||
|
|
2329e993ee | ||
|
|
da655b86c3 | ||
|
|
c5ff8bd4ce | ||
|
|
06aee158de | ||
|
|
bd42445ea7 | ||
|
|
ede96fa486 | ||
|
|
67dec216d2 | ||
|
|
fcbdf7ba4f | ||
|
|
e8c949c1e7 | ||
|
|
28c93ef5ac | ||
|
|
d7921c5d5f | ||
|
|
6d296f2b44 | ||
|
|
553e2830bb | ||
|
|
667ab981ba | ||
|
|
bc7871f630 | ||
|
|
d579705b10 | ||
|
|
94d383a8c1 | ||
|
|
08062e3fc3 | ||
|
|
4441427943 | ||
|
|
f5da432d38 | ||
|
|
60f665a65c | ||
|
|
9b6174793a | ||
|
|
dedab38b99 | ||
|
|
4580b18b04 | ||
|
|
88dad36449 | ||
|
|
075c82b32c | ||
|
|
ae2ffd0a28 | ||
|
|
26eea64689 | ||
|
|
c9ff1e1949 | ||
|
|
e31f38eadc | ||
|
|
756b57400b | ||
|
|
01bfd2e090 | ||
|
|
dc3e89e65c | ||
|
|
240d0ff263 | ||
|
|
3b47a4113f | ||
|
|
a6d6a49f82 | ||
|
|
f8ff3faf78 | ||
|
|
d899c26617 | ||
|
|
73ba325072 | ||
|
|
aff19e13c7 | ||
|
|
007122df43 | ||
|
|
06f8911ee1 | ||
|
|
f96f0fecda | ||
|
|
21987cb423 | ||
|
|
18cc8d7cab | ||
|
|
fc0dbd8782 | ||
|
|
b7ca3d7e37 | ||
|
|
c4bf992c0c | ||
|
|
dcce288a98 | ||
|
|
cfd8126e5d | ||
|
|
7a96642498 | ||
|
|
8d5a42c233 | ||
|
|
00a41be413 | ||
|
|
fdb9a9cca8 | ||
|
|
e7f088ef52 | ||
|
|
243efcd51a | ||
|
|
e3cbdd18a0 | ||
|
|
b9cdaf8e19 | ||
|
|
4758caa772 | ||
|
|
4058522f68 | ||
|
|
80437c564d | ||
|
|
503fc7c312 | ||
|
|
f6691a90c0 | ||
|
|
ead09ed110 | ||
|
|
ac1ecd2e7b | ||
|
|
3538e7f6f4 | ||
|
|
75bc038144 | ||
|
|
7018a3e737 | ||
|
|
d3836d4548 | ||
|
|
dbd7db7787 | ||
|
|
1222fd40b7 | ||
|
|
b5f4c639fd | ||
|
|
cddeeff3fc | ||
|
|
94a35a6558 | ||
|
|
b953b2b807 | ||
|
|
367a73d033 | ||
|
|
1ac13658e1 | ||
|
|
2440272307 | ||
|
|
582ed6b5d1 | ||
|
|
e2adbaa5c1 | ||
|
|
4acf800ace | ||
|
|
7cc305c2f5 | ||
|
|
95f5962186 | ||
|
|
f4c2996a3a | ||
|
|
10c4037694 | ||
|
|
52be6deccf | ||
|
|
0d736efc88 | ||
|
|
9cc21c2a62 | ||
|
|
0b7f422d5d | ||
|
|
0ca760fad6 | ||
|
|
3feed7ba07 | ||
|
|
57f1f2d1fe | ||
|
|
b0f9f15a60 | ||
|
|
dc4d820666 | ||
|
|
e30b54ddb2 | ||
|
|
939bb07603 | ||
|
|
cc2c49644d | ||
|
|
29f221d547 | ||
|
|
2e32ceb6e0 | ||
|
|
2cbc9b6426 | ||
|
|
3f3a8f898d | ||
|
|
6e62ea5364 | ||
|
|
5d39b6160a | ||
|
|
a9e8187f28 | ||
|
|
228f5bfdff | ||
|
|
29e5f193f0 | ||
|
|
8f8ebab712 | ||
|
|
418149c9a6 | ||
|
|
e30e5da75a | ||
|
|
fc6681306e | ||
|
|
60acc3ef44 | ||
|
|
9958e557b7 | ||
|
|
8dbc1daaf4 | ||
|
|
5a23d048bd | ||
|
|
b658ea6459 | ||
|
|
dc91a0d807 | ||
|
|
c1fd6552d2 | ||
|
|
6b2e78acdf | ||
|
|
7b11ab04c6 | ||
|
|
bced5f95ff | ||
|
|
9d7f773b9c | ||
|
|
fea0a98b9e | ||
|
|
8745858bcf | ||
|
|
2885c73a9a | ||
|
|
893b23f3cd | ||
|
|
d860d6b891 | ||
|
|
dcc9625803 | ||
|
|
b7e4426002 | ||
|
|
b4cf8f76c8 | ||
|
|
687eddcc63 | ||
|
|
9d6d137b50 | ||
|
|
a75b95694b | ||
|
|
c7aa8871e4 | ||
|
|
f9d919bdbb | ||
|
|
4d0f019ad5 | ||
|
|
e6154e685f | ||
|
|
2c59b5f557 | ||
|
|
4746c89227 | ||
|
|
278d851c7c | ||
|
|
406e22a681 | ||
|
|
17e05c6fd5 | ||
|
|
9846762991 | ||
|
|
17df1ebc6b | ||
|
|
ad8a8b52be | ||
|
|
0d29527758 | ||
|
|
7a96c9fe24 | ||
|
|
c71e5c63ca | ||
|
|
f82a164d75 | ||
|
|
5d4e9b1ead | ||
|
|
788f885759 | ||
|
|
6aeafc6651 | ||
|
|
1aadf91901 | ||
|
|
7de030bb69 | ||
|
|
b5d91ccc21 | ||
|
|
0eafa7de5d | ||
|
|
e554635e48 | ||
|
|
5e915dd1ff | ||
|
|
13f55011c0 | ||
|
|
05dc713dac | ||
|
|
80f3c7934a | ||
|
|
1341bf8fbd | ||
|
|
5b163063c3 | ||
|
|
c2a15ad89d | ||
|
|
c92312a6c6 | ||
|
|
3253e7fd10 | ||
|
|
e5178793b3 | ||
|
|
bec4f83778 | ||
|
|
22f04a926f | ||
|
|
76fa390e3d | ||
|
|
1ef406bbaf | ||
|
|
0aa8711796 | ||
|
|
bea7c94cae | ||
|
|
e7239c53fd | ||
|
|
6f551c770c | ||
|
|
2d755a45e0 | ||
|
|
7a98a886b6 | ||
|
|
b0fb7177bb | ||
|
|
73e8bc41cd | ||
|
|
0b8f1a18b2 | ||
|
|
8caa220ad5 | ||
|
|
b8203bec53 | ||
|
|
5db4c32035 | ||
|
|
9282ed19b2 | ||
|
|
45ee79014d | ||
|
|
0dc0e6490c | ||
|
|
127113a59b | ||
|
|
49bf115c84 | ||
|
|
2196516e2b | ||
|
|
899dbff7e9 | ||
|
|
4345d94d68 | ||
|
|
074af5d16c | ||
|
|
89d6ea0b5d | ||
|
|
c5baf212c8 | ||
|
|
ba31c78412 | ||
|
|
1c1d2a0568 | ||
|
|
0c6ff6822d | ||
|
|
491ffc3576 | ||
|
|
da5ebee3f7 | ||
|
|
6530ca62da | ||
|
|
0bd19cfd38 | ||
|
|
8ada29e25f | ||
|
|
3b5c1bd09c | ||
|
|
c82a5e0523 | ||
|
|
9c5f9906fa | ||
|
|
db2f5b85a9 | ||
|
|
547598c61c | ||
|
|
711fbc549a | ||
|
|
f85c017184 | ||
|
|
85d46ed2b0 | ||
|
|
2d9f578719 | ||
|
|
e75f3a7278 | ||
|
|
26fa5c8860 | ||
|
|
ed5140568a | ||
|
|
70110b4a5a | ||
|
|
a6f23f00b4 | ||
|
|
30e3a43311 | ||
|
|
dce6aaefea | ||
|
|
4843a278ff | ||
|
|
fe7d611fb9 | ||
|
|
0e7e918e2c | ||
|
|
7d6e061ade | ||
|
|
66891aa536 | ||
|
|
1d7a2ccf82 | ||
|
|
e1bfb1234b | ||
|
|
9377fdfc37 | ||
|
|
5f1d17ba1f | ||
|
|
4c0ca1c8e7 | ||
|
|
8ef1aee35c | ||
|
|
4168cc8d78 | ||
|
|
bca54ab1f6 | ||
|
|
6d2c464436 | ||
|
|
e93bd7a3bf | ||
|
|
da94075e7b | ||
|
|
69522495de | ||
|
|
fce88fc72c | ||
|
|
163a2e5d0a | ||
|
|
b979752989 | ||
|
|
8ad2d5f949 | ||
|
|
745ba66119 | ||
|
|
4cf6dec592 | ||
|
|
d7fa0aeff9 | ||
|
|
2def6346e6 | ||
|
|
607539a2af | ||
|
|
6d24dd52d6 | ||
|
|
e3343cbd01 | ||
|
|
71ffa0a137 | ||
|
|
2b4f8091f9 | ||
|
|
113da121e9 | ||
|
|
dd7413f973 | ||
|
|
0851fd13e6 | ||
|
|
97024041f3 | ||
|
|
22238c3200 | ||
|
|
780bb248f7 | ||
|
|
026bdb00f2 | ||
|
|
373d4ac932 | ||
|
|
7d26027752 | ||
|
|
3d426ccef8 | ||
|
|
b31e8e1cee | ||
|
|
f0923c4ed7 | ||
|
|
aabf19e63b | ||
|
|
f4fc1eb5f6 | ||
|
|
5e201a32ca | ||
|
|
438e9e0969 | ||
|
|
9554e82c47 | ||
|
|
4cf2c7a350 | ||
|
|
664d7dccdb | ||
|
|
21b02efb4d | ||
|
|
d07aa4b29e | ||
|
|
9f33043d17 | ||
|
|
2e6301fca1 | ||
|
|
83c5df2c47 | ||
|
|
759b4ef811 | ||
|
|
437ebf6265 | ||
|
|
bffd02b8c7 | ||
|
|
196b27ee9c | ||
|
|
ff08540fd3 | ||
|
|
07be380f34 | ||
|
|
76d58deefa | ||
|
|
dba9764c5e | ||
|
|
ee5d6c7c3e | ||
|
|
1492196bbf | ||
|
|
9378f69653 | ||
|
|
d2d427b533 | ||
|
|
78a6af8dae | ||
|
|
3585e02993 | ||
|
|
5af88ae61e | ||
|
|
f946117dac | ||
|
|
666a6a218f | ||
|
|
1b031582a4 | ||
|
|
afe072adf1 | ||
|
|
09d4fea9e2 | ||
|
|
58c3fba6b9 | ||
|
|
773dd7773b | ||
|
|
732ce05866 | ||
|
|
108c7d3aaa | ||
|
|
86f4256b5a | ||
|
|
84b2917706 | ||
|
|
fc66a4a19c | ||
|
|
087387087e | ||
|
|
3f404905d2 | ||
|
|
67595d6deb | ||
|
|
77f942711a | ||
|
|
e3c98ddc35 | ||
|
|
db0d966102 | ||
|
|
a29639fceb | ||
|
|
0605b7df8c | ||
|
|
51ca08719e | ||
|
|
ce9b94e93d | ||
|
|
7cc0f7cb99 | ||
|
|
06c229dfd4 | ||
|
|
2d5f6d65ce | ||
|
|
b9841351b4 | ||
|
|
d9b6d115d1 | ||
|
|
cc6dd58778 | ||
|
|
0ba931cbed | ||
|
|
a7daeb2a12 | ||
|
|
2fe9dc7ca1 | ||
|
|
b662128708 | ||
|
|
e22f33a44b | ||
|
|
5d69d2aba9 | ||
|
|
0090580a64 | ||
|
|
538a6dc08e | ||
|
|
5b199d8f25 | ||
|
|
51ddb62126 | ||
|
|
785bc33192 | ||
|
|
522bacb1f0 | ||
|
|
cf030f6f0c | ||
|
|
823164a924 | ||
|
|
2ddae623b8 | ||
|
|
86c7286aad | ||
|
|
9886316e07 | ||
|
|
8a3356859c | ||
|
|
32d5f1db85 | ||
|
|
b5975e0f05 | ||
|
|
53ade56b4e | ||
|
|
5677117c0d | ||
|
|
558ae5dc45 | ||
|
|
83f63f4c42 | ||
|
|
247e92937c | ||
|
|
63314941f6 | ||
|
|
e7345b9711 | ||
|
|
22f9fa3938 | ||
|
|
4617829d41 | ||
|
|
fc907b802f | ||
|
|
ba50fbdc3e | ||
|
|
6f77ba8aea | ||
|
|
972ff93e6c | ||
|
|
338bc2e0dc | ||
|
|
8cedad8241 | ||
|
|
32bd6d96e3 | ||
|
|
611216286e | ||
|
|
d8915dbfc9 | ||
|
|
28b12c85f4 | ||
|
|
bee221c18d | ||
|
|
878eb057d1 | ||
|
|
da7c66419a | ||
|
|
d5ad53dae7 | ||
|
|
a03075416c | ||
|
|
e4d8cf2d76 | ||
|
|
8b47d740a8 | ||
|
|
0cac3e1c40 | ||
|
|
3f13485ced | ||
|
|
24c99c4ff9 | ||
|
|
4002f2716d | ||
|
|
f0758768b9 | ||
|
|
a82b1a2e2f | ||
|
|
ccd0fd8902 | ||
|
|
1fbba5cf2d | ||
|
|
d6805cfa0f | ||
|
|
4dee965fdf | ||
|
|
35094a57cb | ||
|
|
5d254c7751 | ||
|
|
10217fc8d9 | ||
|
|
226dd28be8 | ||
|
|
c6d2b6ea8a | ||
|
|
7fd4ba3aad | ||
|
|
ecfcf11c05 | ||
|
|
30ea57c644 | ||
|
|
c4001c727a | ||
|
|
fd4cfc2ff3 | ||
|
|
03cb61f746 | ||
|
|
8eed25b469 | ||
|
|
ee09594190 | ||
|
|
66c3c0115f | ||
|
|
3e021f3a6b | ||
|
|
b524c077a4 | ||
|
|
10f7efecb2 | ||
|
|
f09ee0bdb3 | ||
|
|
d5a52241b0 | ||
|
|
52c4e15c76 | ||
|
|
fdba14691c | ||
|
|
3d136a28a0 | ||
|
|
db398d41a0 | ||
|
|
7cbaf8b5be | ||
|
|
d1c0d2b5f8 | ||
|
|
909606627f | ||
|
|
80f5d3a660 | ||
|
|
182c4752d5 | ||
|
|
273ef55857 | ||
|
|
88c2e55acf | ||
|
|
496fd79714 | ||
|
|
40a1b860bf | ||
|
|
8120913acb | ||
|
|
935eb0651d | ||
|
|
925a93686b | ||
|
|
6f545483c9 | ||
|
|
a8b018d5da | ||
|
|
6e35033f2e | ||
|
|
f79a1b2533 | ||
|
|
79ce98116a | ||
|
|
942c417cbb | ||
|
|
8df850023c | ||
|
|
eedde4abcb | ||
|
|
fcc06dfad4 | ||
|
|
ff850c4251 | ||
|
|
20f8c92bb2 | ||
|
|
22dcb39adb | ||
|
|
f572a72c2a | ||
|
|
be736c48e9 | ||
|
|
67c7c79dae | ||
|
|
61108ba760 | ||
|
|
7285f9e9ad | ||
|
|
b29de8f370 | ||
|
|
7a828b3aee | ||
|
|
afc264e846 | ||
|
|
37c1d7ea58 | ||
|
|
4a72dab02a | ||
|
|
22e5b323c8 | ||
|
|
b275ac5765 | ||
|
|
1475845675 | ||
|
|
6d6034870e | ||
|
|
0a3b1e930a | ||
|
|
836e8a5654 | ||
|
|
39ac07bfde | ||
|
|
fc178b40bc | ||
|
|
9170b24fee | ||
|
|
3674b4ed96 | ||
|
|
c9d9205bb8 | ||
|
|
2c54fd2357 | ||
|
|
d881df916e | ||
|
|
6894ed7d5c | ||
|
|
25a2b2d5d3 | ||
|
|
4fd62a58bd | ||
|
|
532dc07c7b | ||
|
|
fb2a2076a2 | ||
|
|
a8622be1c6 | ||
|
|
810fb73362 | ||
|
|
6950379d94 | ||
|
|
f9020659e6 | ||
|
|
c99f4e31c5 | ||
|
|
526c09b7ff | ||
|
|
5a37a53cb0 | ||
|
|
a57d68acd5 | ||
|
|
a33734e2d3 | ||
|
|
e5921b3949 | ||
|
|
5de25f2b43 | ||
|
|
41f4166aed | ||
|
|
4f8fa6e7aa | ||
|
|
dbd09a431a | ||
|
|
8d48dd973a | ||
|
|
c7666b314b | ||
|
|
03628505ed | ||
|
|
b368873f4d | ||
|
|
dd38f3ce13 | ||
|
|
d8b01c0257 | ||
|
|
0a2fbe1f7f | ||
|
|
de0b54ae70 | ||
|
|
54e8cce33c | ||
|
|
08bf01b649 | ||
|
|
1a66c11091 | ||
|
|
689a1de69b | ||
|
|
0dfa7425c1 | ||
|
|
4098db039e | ||
|
|
a7834611d1 | ||
|
|
9dd4e4756b | ||
|
|
108369414e | ||
|
|
00e07c0384 | ||
|
|
1ebee561bc | ||
|
|
ec0db86663 | ||
|
|
3df4bb3a54 | ||
|
|
5bd748680f | ||
|
|
e09ff31c09 | ||
|
|
07e4c1c20f | ||
|
|
daf854c692 | ||
|
|
65c1e0391c | ||
|
|
769edba1a5 | ||
|
|
2567447b24 | ||
|
|
263b412fdf | ||
|
|
037ea5b1fc | ||
|
|
18634fa805 | ||
|
|
473e4716fc | ||
|
|
59cac9c0cc | ||
|
|
92d87d983b | ||
|
|
bfc4a1bc16 | ||
|
|
1ef61f6cd3 | ||
|
|
5518ccb795 | ||
|
|
027b27dda1 | ||
|
|
6e1180ce06 | ||
|
|
e4ae9134ae | ||
|
|
640bc03c6b | ||
|
|
2a86339b1d | ||
|
|
667aed635d | ||
|
|
1d3a23e051 | ||
|
|
507732587e | ||
|
|
d1e3508d55 | ||
|
|
3a8998f1f9 | ||
|
|
de5795e368 | ||
|
|
fca71e2b95 | ||
|
|
0e2b371e59 | ||
|
|
e1fa6b4057 | ||
|
|
b6c8d3fae5 | ||
|
|
eb1c61f335 | ||
|
|
5842ce23a3 | ||
|
|
692d48df87 | ||
|
|
49bd091359 | ||
|
|
5fe2db7e56 | ||
|
|
b927f991d6 | ||
|
|
4d7e1054bd | ||
|
|
42fa0e1d1f | ||
|
|
abfe3a9b4d | ||
|
|
b9f8f1a0ad | ||
|
|
608b67af77 | ||
|
|
2a65e39848 | ||
|
|
c23bf6a0c4 | ||
|
|
50c460df5a | ||
|
|
61f0577ab2 | ||
|
|
a9aab3c1ee | ||
|
|
73da86ac0e | ||
|
|
43d08df6b3 | ||
|
|
4654f317ca | ||
|
|
d2b83027b4 | ||
|
|
7be930a69c | ||
|
|
a1b1ed0060 | ||
|
|
2cb18a3f8f | ||
|
|
2a30bb718a | ||
|
|
75ffcacfe4 | ||
|
|
1beeac545d | ||
|
|
ab98c87e7c | ||
|
|
5b453d604e | ||
|
|
90fc2a8164 | ||
|
|
0ed9301ed9 | ||
|
|
d235d076c4 | ||
|
|
9f67f42f94 | ||
|
|
31a25c94c6 | ||
|
|
6a9f687eae | ||
|
|
df7e9b1184 | ||
|
|
1350ba6c4b | ||
|
|
2692ccc7b3 | ||
|
|
f36fd5ac1a | ||
|
|
0e8678b182 | ||
|
|
ce4f1be2a6 | ||
|
|
db907b1b67 | ||
|
|
d8b4d0c1ce | ||
|
|
fb37174e5f | ||
|
|
9613127162 | ||
|
|
4e7de236d3 | ||
|
|
741dfd418d | ||
|
|
1d6dfa048e | ||
|
|
8f0a6cd810 | ||
|
|
471303a179 | ||
|
|
73a2597c8a | ||
|
|
3cd152c9d5 | ||
|
|
e70928d603 | ||
|
|
e2b33348f3 | ||
|
|
3ff712fea5 | ||
|
|
2db26ae37e | ||
|
|
375b97ca6f | ||
|
|
d331a3dd5a | ||
|
|
c0c63fd93b | ||
|
|
b5a47cae25 | ||
|
|
1f1c9dfa59 | ||
|
|
0903b4a610 | ||
|
|
884ad6557b | ||
|
|
00658f3d64 | ||
|
|
fff3ec30b8 | ||
|
|
26434f7baf | ||
|
|
f75e2fe8db | ||
|
|
0674e04597 | ||
|
|
29f1d6cd42 | ||
|
|
83592f7e4a | ||
|
|
13cd50f92c | ||
|
|
a989914fef | ||
|
|
f692284f27 | ||
|
|
05bd587c74 | ||
|
|
d292d46fcb | ||
|
|
b899a65726 | ||
|
|
63b4dd633c | ||
|
|
eba99f6968 | ||
|
|
354bef94ee | ||
|
|
e515cdc8dd | ||
|
|
2a03d1c4a9 | ||
|
|
3e3c409fc4 | ||
|
|
62e3dd250b | ||
|
|
3799929f59 | ||
|
|
2019766989 | ||
|
|
58c3b37e49 | ||
|
|
c650c2e474 | ||
|
|
ef336d552c | ||
|
|
e4cdc58399 | ||
|
|
48d3403c40 | ||
|
|
af143bdd82 | ||
|
|
343983d9af | ||
|
|
5cabf155ab | ||
|
|
60070e6076 | ||
|
|
697dbc7a96 | ||
|
|
43593d8aea | ||
|
|
1f90010cbd | ||
|
|
d839026ade | ||
|
|
34de213926 | ||
|
|
b5ec4dd305 | ||
|
|
023d3f811b | ||
|
|
53c32cc5a4 | ||
|
|
eb6ee8ea46 | ||
|
|
9f8c20c3e7 | ||
|
|
a72ad4e46c | ||
|
|
b19518a1f8 | ||
|
|
8a9d0b0bfb | ||
|
|
14954cc426 | ||
|
|
32dca9ea3f | ||
|
|
d4c41d2d94 | ||
|
|
3f331f2e62 | ||
|
|
d753af9d17 | ||
|
|
b23c7808c0 | ||
|
|
792200cac4 | ||
|
|
526b607e37 | ||
|
|
298885083b | ||
|
|
26c4804892 | ||
|
|
a507dd0c51 | ||
|
|
c6830bab16 | ||
|
|
a0457a2dd9 | ||
|
|
ce60cafa19 | ||
|
|
9e02131525 | ||
|
|
6918eeca26 | ||
|
|
fbbee1cdac | ||
|
|
89959b48a7 | ||
|
|
b26a535451 | ||
|
|
86207993b8 | ||
|
|
ee9213da76 | ||
|
|
a084b2b32f | ||
|
|
afdae8efc0 | ||
|
|
64ad862116 | ||
|
|
4d81aaa763 | ||
|
|
8cb25792ba | ||
|
|
f715034fc4 | ||
|
|
c97ebb135f | ||
|
|
97749b335a | ||
|
|
a647a602bf | ||
|
|
80581a4aa2 | ||
|
|
882b31aeaa | ||
|
|
871aee2aae | ||
|
|
62e67c345c | ||
|
|
5de9a8dce6 | ||
|
|
3adcbfaa7d | ||
|
|
213c220d83 | ||
|
|
eef18dd655 | ||
|
|
a4600e7278 | ||
|
|
c42bff99e2 | ||
|
|
b29f1c62bb | ||
|
|
df7183a572 | ||
|
|
7a5bbb1e59 | ||
|
|
a3cd0385d0 | ||
|
|
8fdc11813d | ||
|
|
fb412e8440 | ||
|
|
15a337fee8 | ||
|
|
6f141fe393 | ||
|
|
9b0f974648 | ||
|
|
ceac6a60e6 | ||
|
|
cba6630aa0 | ||
|
|
6e0eac0abc | ||
|
|
b3aadccfc4 | ||
|
|
116926f7c0 | ||
|
|
d0a7b8df9c | ||
|
|
b8a1743d8f | ||
|
|
e8b1b8fbdf | ||
|
|
e9d77e6c52 | ||
|
|
56f0a8525b | ||
|
|
778af24d82 | ||
|
|
cd6caea4b0 | ||
|
|
f63c8043af | ||
|
|
3c77c1eb2b | ||
|
|
ffec6c2014 | ||
|
|
d097531926 | ||
|
|
d339ca3599 | ||
|
|
adb1ca58b9 | ||
|
|
70ffe1de43 | ||
|
|
cf4d16b528 | ||
|
|
da2bcfdf9a | ||
|
|
7f81d21aaa | ||
|
|
d0756cf00c | ||
|
|
4fd59ce8c9 | ||
|
|
adca879fb9 | ||
|
|
3a2d612b41 | ||
|
|
332d53e016 | ||
|
|
ae1344d1a0 | ||
|
|
ae382949c8 | ||
|
|
fd8d4d3d38 | ||
|
|
a2216e4b68 | ||
|
|
4fb9fc3b3f | ||
|
|
35b4d75eea | ||
|
|
d7321d5f5f | ||
|
|
35eda57cb2 | ||
|
|
78a100135b | ||
|
|
dc6932a9ba | ||
|
|
d765674913 | ||
|
|
2af522aea7 | ||
|
|
9aba969296 | ||
|
|
2114b73c11 | ||
|
|
d591e2fd36 | ||
|
|
c61d97b0ac | ||
|
|
37ea662998 | ||
|
|
3d9d275d61 | ||
|
|
a02087e5e4 | ||
|
|
2ccf17b9b7 | ||
|
|
8ccce5666c | ||
|
|
859e984827 | ||
|
|
5e84cfb3f1 | ||
|
|
5a84d3bef7 | ||
|
|
634be5b096 | ||
|
|
80c9e24604 | ||
|
|
5808089fce | ||
|
|
d6f84ddd12 | ||
|
|
c809eeb2a8 | ||
|
|
2dfb1cf1a6 | ||
|
|
50ba9b4899 | ||
|
|
a36d312844 | ||
|
|
6653340bac | ||
|
|
b9723adc30 | ||
|
|
7648ea712c | ||
|
|
3f29897506 | ||
|
|
bebbdc4089 | ||
|
|
9000fdf6fc | ||
|
|
fbf08b94aa | ||
|
|
a6d2fe73ae | ||
|
|
e854723aa3 | ||
|
|
9b7f54fa35 | ||
|
|
9ef9f60505 | ||
|
|
076a45acc5 | ||
|
|
c239306d82 | ||
|
|
b5f2092e9c | ||
|
|
03622f61b0 | ||
|
|
3d9230ac93 | ||
|
|
2d746cbfd4 | ||
|
|
2f2813037b | ||
|
|
f6f4c8070a | ||
|
|
40bfdcdf8c | ||
|
|
59fa025292 | ||
|
|
e09d7f0ad0 | ||
|
|
6a6e53f728 | ||
|
|
16e804b068 | ||
|
|
db52a55b36 | ||
|
|
0574daca13 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: gnif
|
||||
patreon: gnif
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: lookingglass
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
74
.github/issue_template.md
vendored
74
.github/issue_template.md
vendored
@@ -1,11 +1,69 @@
|
||||
### Required information
|
||||
### Issues are for Bug Reports and Feature Requests Only!
|
||||
|
||||
Host CPU:
|
||||
Host GPU:
|
||||
Guest GPU:
|
||||
Host Kernel version:
|
||||
Host QEMU version:
|
||||
If you are looking for help or support please use one of the following methods
|
||||
|
||||
Please describe what were you doing when the problem occured. If the Windows host application crashed please check for file named `looking-glass-host.dmp` and attach it to this bug report.
|
||||
Create a New Topic on the Level1Tech's forum under the Looking Glass category:
|
||||
* https://forum.level1techs.com/c/software/lookingGlass/142
|
||||
|
||||
**Reports that do no include this information will be ignored and closed**
|
||||
Ask for help in #looking-glass in the VFIO discord server
|
||||
* https://discord.gg/4ahCn4c
|
||||
|
||||
*Issues that are not bug reports or feature requests will be closed & ignored*
|
||||
|
||||
### Errors that are not bugs
|
||||
|
||||
Some errors generated by the LG client are not bugs, but rather issues with your
|
||||
system's configuration and/or timing. Please do not report these, but rather use
|
||||
one of the above resources to ask for advice/help.
|
||||
|
||||
* `LGMP_ERR_QUEUE_UNSUBSCRIBED` - Failure to heed advice on things such as
|
||||
using `isolcpus` and CPU pinning may result in this message, especially if you
|
||||
are over-taxing your CPU.
|
||||
|
||||
* `Could not create an SDL window: *` - Failure to create a SDL window is not an
|
||||
issue with Looking Glass but rather a more substantial issue with your system,
|
||||
such as missing hardware support for the RGBA32 pixmap format, or missing
|
||||
required OpenGL EGL features.
|
||||
|
||||
* `The host application is not compatible with this client` - The Looking Glass
|
||||
Host application in Windows is the incorrect version and is not compatible,
|
||||
you need to make sure you run matching versions of both the host and client
|
||||
applications.
|
||||
|
||||
### Bug Report Required Information
|
||||
|
||||
The entire (not truncated) output from the client application (if applicable).
|
||||
To obtain this run `looking-glass-client` in a terminal.
|
||||
|
||||
```
|
||||
PASTE CLIENT OUTPUT HERE
|
||||
```
|
||||
|
||||
The entire (not truncated) log file from the host application (if applicable).
|
||||
To obtain this locate the log file on your system, it will be in one of the
|
||||
following two locations depending on how you are launching the Looking Glass Host
|
||||
application:
|
||||
|
||||
* C:\Windows\Temp\looking-glass.txt
|
||||
* C:\Users\YOUR_USER\AppData\Local\Temp\looking-glass.txt
|
||||
|
||||
This log may be quite long, please delete the file first and then proceed to
|
||||
launch the host and reproduce the issue so that the log only contains the
|
||||
pertinent information.
|
||||
|
||||
|
||||
```
|
||||
PASTE HOST LOG FILE CONTENTS HERE
|
||||
```
|
||||
|
||||
If the client is unexpetedly exiting without a backtrace, please provide one via
|
||||
gdb with the command `thread apply all bt`. If you are unsure how to do this
|
||||
please watch the video below on how to perform a Debug build and generate this
|
||||
backtrace.
|
||||
|
||||
https://www.youtube.com/watch?v=EqxxJK9Yo64
|
||||
|
||||
|
||||
```
|
||||
PASTE FULL BACKTRACE HERE
|
||||
```
|
||||
|
||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
module/*.ko
|
||||
module/*.o
|
||||
module/*.mod.c
|
||||
module/.*
|
||||
module/Module.symvers
|
||||
module/modules.order
|
||||
*.a
|
||||
*.o
|
||||
*.exe
|
||||
*/build
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -1,3 +1,6 @@
|
||||
[submodule "vendor/kvm-guest-drivers-windows"]
|
||||
path = vendor/kvm-guest-drivers-windows
|
||||
url = https://github.com/virtio-win/kvm-guest-drivers-windows.git
|
||||
[submodule "LGMP"]
|
||||
path = repos/LGMP
|
||||
url = https://github.com/gnif/LGMP.git
|
||||
[submodule "repos/PureSpice"]
|
||||
path = repos/PureSpice
|
||||
url = https://github.com/gnif/PureSpice
|
||||
|
||||
@@ -60,9 +60,9 @@ Members of the community that donated the funding to obtain the remaining hardwa
|
||||
Michael Hillman
|
||||
Andreas Jacobsen
|
||||
NikkyAi
|
||||
Michael Lindman
|
||||
Michael Lindman
|
||||
|
||||
And another 41 people that whish to remain anonymous.
|
||||
And another 41 people that wish to remain anonymous.
|
||||
|
||||
Thank you everyone for making this project possible.
|
||||
- Geoffrey McRae <geoff@hostfission.com (gnif)
|
||||
|
||||
62
README.md
62
README.md
@@ -1,14 +1,66 @@
|
||||
# LookingGlass
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough.
|
||||
# Looking Glass
|
||||
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
|
||||
VGA PCI Passthrough.
|
||||
|
||||
* Project Website: https://looking-glass.hostfission.com
|
||||
* Support Forum: https://forum.level1techs.com/t/looking-glass-guides-help-and-support/122387
|
||||
* Windows Builds of the host application: https://looking-glass.hostfission.com/downloads
|
||||
* Getting Started: https://looking-glass.hostfission.com/wiki/Installation
|
||||
|
||||
## Donations
|
||||
|
||||
I (Geoffrey McRae) am the primary developer behind this project and I have
|
||||
invested thousands of hours of development time into it.
|
||||
|
||||
If you like this project and find it useful and would like to help out you can
|
||||
support me directly using the following platforms.
|
||||
|
||||
* [GitHub](https://github.com/sponsors/gnif)
|
||||
* [Ko-Fi](https://ko-fi.com/lookingglass)
|
||||
* [Patreon](https://www.patreon.com/gnif)
|
||||
* [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ESQ72XUPGKXRY)
|
||||
* BTC - 14ZFcYjsKPiVreHqcaekvHGL846u3ZuT13
|
||||
|
||||
## Documentation
|
||||
|
||||
** IMPORTANT **
|
||||
This project contains submodules that must be checked out if building from the
|
||||
git repository! If you are not a developer and just want to compile Looking
|
||||
Glass please download the source archive from the website instead:
|
||||
|
||||
https://looking-glass.hostfission.com/downloads
|
||||
|
||||
Please also be sure to see the following files for more information
|
||||
Note: The `README.md` files are slowly being deprecated from this project in
|
||||
favor of the wiki at https://looking-glass.hostfission.com/wiki, and as such the
|
||||
information in these files may be dated.
|
||||
|
||||
* [client/README.md](client/README.md)
|
||||
* [host/README.md](host/README.md)
|
||||
* [module/README.md](module/README.md)
|
||||
|
||||
## Latest Version
|
||||
|
||||
If you would like to use the latest bleeding edge version of Looking Glass please
|
||||
be aware there will be no support at this time.
|
||||
|
||||
Latest bleeding edge builds of the Windows host application can be obtained from:
|
||||
|
||||
https://looking-glass.hostfission.com/downloads
|
||||
|
||||
# Help and support
|
||||
|
||||
## Web
|
||||
https://forum.level1techs.com/t/looking-glass-guides-help-and-support/122387
|
||||
|
||||
https://forum.level1techs.com/t/looking-glass-triage/130952
|
||||
|
||||
## Discord
|
||||
|
||||
https://discord.gg/4ahCn4c
|
||||
|
||||
## IRC
|
||||
|
||||
Join us in the #LookingGlass channel on the FreeNode network
|
||||
|
||||
## Trello
|
||||
|
||||
* https://trello.com/b/tI1Xbwsg/looking-glass
|
||||
|
||||
114
client/CMakeLists.txt
Normal file
114
client/CMakeLists.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(looking-glass-client C)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
|
||||
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()
|
||||
endif()
|
||||
|
||||
option(ENABLE_OPENGL "Enable the OpenGL renderer" ON)
|
||||
add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.")
|
||||
|
||||
option(ENABLE_EGL "Enable the EGL renderer" ON)
|
||||
add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.")
|
||||
|
||||
option(ENABLE_CB_X11 "Enable X11 clipboard integration" ON)
|
||||
add_feature_info(ENABLE_CB_X11 ENABLE_CB_X11 "X11 Clipboard Integration.")
|
||||
|
||||
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
||||
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
|
||||
|
||||
add_compile_options(
|
||||
"-Wall"
|
||||
"-Werror"
|
||||
"-Wfatal-errors"
|
||||
"-ffast-math"
|
||||
"-fdata-sections"
|
||||
"-ffunction-sections"
|
||||
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
||||
)
|
||||
|
||||
set(EXE_FLAGS "-Wl,--gc-sections")
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PKGCONFIG REQUIRED
|
||||
sdl2
|
||||
x11
|
||||
)
|
||||
|
||||
pkg_check_modules(PKGCONFIG_OPT
|
||||
xi
|
||||
)
|
||||
|
||||
execute_process(
|
||||
COMMAND cat ../VERSION
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE BUILD_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
find_package(GMP)
|
||||
|
||||
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
|
||||
add_definitions(-D ATOMIC_LOCKING)
|
||||
add_definitions(-D GL_GLEXT_PROTOTYPES)
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${PKGCONFIG_INCLUDE_DIRS} ${PKGCONFIG_OPT_INCLUDE_DIRS}
|
||||
${GMP_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
link_libraries(
|
||||
${PKGCONFIG_LIBRARIES} ${PKGCONFIG_OPT_LIBRARIES}
|
||||
${GMP_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
rt
|
||||
m
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
src/main.c
|
||||
src/app.c
|
||||
src/config.c
|
||||
src/lg-renderer.c
|
||||
src/ll.c
|
||||
src/utils.c
|
||||
)
|
||||
|
||||
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(renderers)
|
||||
add_subdirectory(clipboards)
|
||||
add_subdirectory(fonts)
|
||||
add_subdirectory(decoders)
|
||||
|
||||
add_executable(looking-glass-client ${SOURCES})
|
||||
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER} ${PKGCONFIG_OPT_CFLAGS_OTHER})
|
||||
target_link_libraries(looking-glass-client
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
lgmp
|
||||
purespice
|
||||
renderers
|
||||
clipboards
|
||||
fonts
|
||||
)
|
||||
|
||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-client DESTINATION bin/ COMPONENT binary)
|
||||
|
||||
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)
|
||||
32
client/DEBUGGING.md
Normal file
32
client/DEBUGGING.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Debugging the Looking Glass Client
|
||||
|
||||
If you are asked to provide debugging information to resolve an issue please
|
||||
follow the following procedure.
|
||||
|
||||
## If you're experiencing a crash:
|
||||
|
||||
Run the program under the `gdb` debugger (you may need to install gdb), for
|
||||
example:
|
||||
|
||||
gdb ./looking-glass-client
|
||||
|
||||
If you need to set any arguments, do so now by running `set args ARGS`, for
|
||||
example:
|
||||
|
||||
set args -F -k
|
||||
|
||||
Now start the program by typing `r`. When the application crashes you will be
|
||||
dumped back into the debugger, the application may appear to be frozen. Run
|
||||
the following command:
|
||||
|
||||
thread apply all bt
|
||||
|
||||
Once you have this information please pastebin the log from looking-glass as
|
||||
well as the information resulting from this command.
|
||||
|
||||
## If you're experencing high CPU load and/or poor performance.
|
||||
|
||||
The steps here are identical to the above, except instead of waiting for the
|
||||
program to crash, in the debugger press `CTRL+C` while the program is
|
||||
exhibiting the problem, then run `thread apply all bt` and pastebin your log
|
||||
and the results of the command.
|
||||
@@ -1,39 +0,0 @@
|
||||
BINARY = looking-glass-client
|
||||
CFLAGS = -g -O3 -std=gnu99 -march=native -Wall -Werror -I./ -I../common -DDEBUG -DATOMIC_LOCKING
|
||||
LDFLAGS = -lrt
|
||||
|
||||
CFLAGS += -ffast-math
|
||||
CFLAGS += -fdata-sections -ffunction-sections
|
||||
LDFLAGS += -Wl,--gc-sections
|
||||
|
||||
LIBS = sdl2 SDL2_ttf gl glu libssl openssl spice-protocol fontconfig x11
|
||||
CFLAGS += $(shell pkg-config --cflags $(LIBS))
|
||||
LDFLAGS += $(shell pkg-config --libs $(LIBS))
|
||||
BUILD ?= .build
|
||||
BIN ?= bin
|
||||
|
||||
CFLAGS += -DBUILD_VERSION='"$(shell git describe --always --long --dirty --abbrev=10 --tags)"'
|
||||
|
||||
OBJS = main.o \
|
||||
lg-renderer.o \
|
||||
spice/spice.o \
|
||||
ivshmem/ivshmem.o \
|
||||
renderers/opengl.o
|
||||
# renderers/opengl-basic.o
|
||||
|
||||
BUILD_OBJS = $(foreach obj,$(OBJS),$(BUILD)/$(obj))
|
||||
|
||||
all: $(BIN)/$(BINARY)
|
||||
|
||||
$(BUILD)/%.o: %.c
|
||||
@mkdir -p $(dir $@)
|
||||
gcc -c $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/$(BINARY): $(BUILD_OBJS)
|
||||
@mkdir -p $(dir $@)
|
||||
gcc -o $@ $(BUILD_OBJS) $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD) $(BIN)
|
||||
|
||||
.PHONY: clean
|
||||
164
client/README.md
Normal file
164
client/README.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Looking Glass Client
|
||||
|
||||
This is the Looking Glass client application that is designed to work in tandem with the Looking Glass Host application
|
||||
|
||||
---
|
||||
|
||||
## Building the Application
|
||||
|
||||
### Build Dependencies
|
||||
|
||||
* binutils-dev
|
||||
* cmake
|
||||
* fonts-freefont-ttf
|
||||
* libsdl2-dev
|
||||
* libsdl2-ttf-dev
|
||||
* libspice-protocol-dev
|
||||
* libfontconfig1-dev
|
||||
* libx11-dev
|
||||
* nettle-dev
|
||||
|
||||
#### Debian (and maybe Ubuntu)
|
||||
|
||||
apt-get install binutils-dev cmake fonts-freefont-ttf libsdl2-dev libsdl2-ttf-dev libspice-protocol-dev libfontconfig1-dev libx11-dev nettle-dev
|
||||
|
||||
### Building
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../
|
||||
make
|
||||
|
||||
Should this all go well you should be left with the file `looking-glass-client`
|
||||
|
||||
---
|
||||
|
||||
## Usage Tips
|
||||
|
||||
### Key Bindings
|
||||
|
||||
By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key.
|
||||
Below are a list of current key bindings:
|
||||
|
||||
| Command | Description |
|
||||
|-|-|
|
||||
| <kbd>ScrLk</kbd> | Toggle cursor screen capture |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F</kbd> | Full Screen toggle |
|
||||
| <kbd>ScrLk</kbd>+<kbd>I</kbd> | Spice keyboard & mouse enable toggle |
|
||||
| <kbd>ScrLk</kbd>+<kbd>N</kbd> | Toggle night vision mode (EGL renderer only!) |
|
||||
| <kbd>ScrLk</kbd>+<kbd>Q</kbd> | Quit |
|
||||
| <kbd>ScrLk</kbd>+<kbd>Insert</kbd> | Increase mouse sensitivity (in capture mode only) |
|
||||
| <kbd>ScrLk</kbd>+<kbd>Del</kbd> | Decrease mouse sensitivity (in capture mode only) |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F1</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F1</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F2</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F2</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F3</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F3</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F4</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F4</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F5</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F5</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F6</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F6</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F7</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F7</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F8</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F8</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F9</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F9</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F10</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F10</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F11</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F11</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F12</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F12</kbd> to the guest |
|
||||
|
||||
|
||||
|
||||
### Setting options via command line arguments
|
||||
|
||||
The syntax is simple: `module:name=value`, for example:
|
||||
|
||||
./looking-glass-client win:fullScreen=yes egl:nvGain=1
|
||||
|
||||
### Setting options via configuration files
|
||||
|
||||
By default the application will look for and load the config files in the following locations
|
||||
|
||||
* /etc/looking-glass-client.ini
|
||||
* ~/.looking-glass-client.ini
|
||||
|
||||
The format of this file is the commonly known INI format, for example:
|
||||
|
||||
[win]
|
||||
fullScreen=yes
|
||||
|
||||
[egl]
|
||||
nvGain=1
|
||||
|
||||
Command line arguments will override any options loaded from the config files.
|
||||
|
||||
### Supported options
|
||||
|
||||
```
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| app:configFile | -C | NULL | A file to read additional configuration from |
|
||||
| app:shmFile | -f | /dev/shm/looking-glass | The path to the shared memory file |
|
||||
| app:shmSize | -L | 0 | Specify the size in MB of the shared memory file (0 = detect) |
|
||||
| app:renderer | -g | auto | Specify the renderer to use |
|
||||
| app:license | -l | no | Show the license for this application and then terminate |
|
||||
| app:cursorPollInterval | | 1000 | How often to check for a cursor update in microseconds |
|
||||
| app:framePollInterval | | 1000 | How often to check for a frame update in microseconds |
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
| win:title | | Looking Glass (client) | The window title |
|
||||
| win:position | | center | Initial window position at startup |
|
||||
| win:size | | 1024x768 | Initial window size at startup |
|
||||
| win:autoResize | -a | no | Auto resize the window to the guest |
|
||||
| win:allowResize | -n | yes | Aallow the window to be manually resized |
|
||||
| win:keepAspect | -r | yes | Maintain the correct aspect ratio |
|
||||
| win:borderless | -d | no | Borderless mode |
|
||||
| win:fullScreen | -F | no | Launch in fullscreen borderless mode |
|
||||
| win:maximize | -T | no | Launch window maximized |
|
||||
| win:minimizeOnFocusLoss | | yes | Minimize window on focus loss |
|
||||
| win:fpsLimit | -K | 200 | Frame rate limit (0 = disable - not recommended) |
|
||||
| win:showFPS | -k | no | Enable the FPS & UPS display |
|
||||
| win:ignoreQuit | -Q | no | Ignore requests to quit (ie: Alt+F4) |
|
||||
| win:noScreensaver | -S | no | Prevent the screensaver from starting |
|
||||
| win:alerts | -q | yes | Show on screen alert messages |
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| input:grabKeyboard | -G | yes | Grab the keyboard in capture mode |
|
||||
| input:escapeKey | -m | 71 = ScrollLock | Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values |
|
||||
| input:hideCursor | -M | yes | Hide the local mouse cursor |
|
||||
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| spice:enable | -s | yes | Enable the built in SPICE client for input and/or clipboard support |
|
||||
| spice:host | -c | 127.0.0.1 | The SPICE server host or UNIX socket |
|
||||
| spice:port | -p | 5900 | The SPICE server port (0 = unix socket) |
|
||||
| spice:input | | yes | Use SPICE to send keyboard and mouse input events to the guest |
|
||||
| spice:clipboard | | yes | Use SPICE to syncronize the clipboard contents with the guest |
|
||||
| spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
|
||||
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM |
|
||||
| spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
|
||||
| spice:captureOnStart | | no | Capture mouse and keyboard on start |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|--------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|--------------------------------------------------------------------------|
|
||||
| egl:vsync | | no | Enable vsync |
|
||||
| egl:nvGainMax | | 1 | The maximum night vision gain |
|
||||
| egl:nvGain | | 0 | The initial night vision gain at startup |
|
||||
|--------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|------------------------------------------------------------------------------------|
|
||||
| opengl:mipmap | | yes | Enable mipmapping |
|
||||
| opengl:vsync | | yes | Enable vsync |
|
||||
| opengl:preventBuffer | | yes | Prevent the driver from buffering frames |
|
||||
| opengl:amdPinnedMem | | yes | Use GL_AMD_pinned_memory if it is available |
|
||||
|------------------------------------------------------------------------------------|
|
||||
```
|
||||
43
client/clipboards/CMakeLists.txt
Normal file
43
client/clipboards/CMakeLists.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clipboards LANGUAGES C)
|
||||
|
||||
set(CLIPBOARD_H "${CMAKE_BINARY_DIR}/include/dynamic/clipboards.h")
|
||||
set(CLIPBOARD_C "${CMAKE_BINARY_DIR}/src/clipboards.c")
|
||||
|
||||
file(WRITE ${CLIPBOARD_H} "#include \"interface/clipboard.h\"\n\n")
|
||||
file(APPEND ${CLIPBOARD_H} "extern LG_Clipboard * LG_Clipboards[];\n\n")
|
||||
|
||||
file(WRITE ${CLIPBOARD_C} "#include \"interface/clipboard.h\"\n\n")
|
||||
file(APPEND ${CLIPBOARD_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(CLIPBOARDS "_")
|
||||
set(CLIPBOARDS_LINK "_")
|
||||
function(add_clipboard name)
|
||||
set(CLIPBOARDS "${CLIPBOARDS};${name}" PARENT_SCOPE)
|
||||
set(CLIPBOARDS_LINK "${CLIPBOARDS_LINK};clipboard_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove clipboards here!
|
||||
if (ENABLE_CB_X11)
|
||||
add_clipboard(X11)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT CLIPBOARDS 0)
|
||||
list(REMOVE_AT CLIPBOARDS_LINK 0)
|
||||
|
||||
list(LENGTH CLIPBOARDS CLIPBOARD_COUNT)
|
||||
file(APPEND ${CLIPBOARD_H} "#define LG_CLIPBOARD_COUNT ${CLIPBOARD_COUNT}\n")
|
||||
|
||||
foreach(clipboard ${CLIPBOARDS})
|
||||
file(APPEND ${CLIPBOARD_C} "extern LG_Clipboard LGC_${clipboard};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${CLIPBOARD_C} "\nconst LG_Clipboard * LG_Clipboards[] =\n{\n")
|
||||
foreach(clipboard ${CLIPBOARDS})
|
||||
file(APPEND ${CLIPBOARD_C} " &LGC_${clipboard},\n")
|
||||
endforeach()
|
||||
file(APPEND ${CLIPBOARD_C} " NULL\n};\n\n")
|
||||
|
||||
add_library(clipboards STATIC ${CLIPBOARD_C})
|
||||
target_link_libraries(clipboards ${CLIPBOARDS_LINK})
|
||||
26
client/clipboards/X11/CMakeLists.txt
Normal file
26
client/clipboards/X11/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clipboard_X11 LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(CLIPBOARD_PKGCONFIG REQUIRED
|
||||
x11
|
||||
xfixes
|
||||
)
|
||||
|
||||
add_library(clipboard_X11 STATIC
|
||||
src/x11.c
|
||||
)
|
||||
|
||||
target_link_libraries(clipboard_X11
|
||||
${CLIPBOARD_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(clipboard_X11
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
359
client/clipboards/X11/src/x11.c
Normal file
359
client/clipboards/X11/src/x11.c
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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/clipboard.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
struct state
|
||||
{
|
||||
Display * display;
|
||||
Window window;
|
||||
Atom aSelection;
|
||||
Atom aCurSelection;
|
||||
Atom aTargets;
|
||||
Atom aSelData;
|
||||
Atom aIncr;
|
||||
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_ClipboardReleaseFn releaseFn;
|
||||
LG_ClipboardRequestFn requestFn;
|
||||
LG_ClipboardNotifyFn notifyFn;
|
||||
LG_ClipboardDataFn dataFn;
|
||||
LG_ClipboardData type;
|
||||
|
||||
// XFixes vars
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
};
|
||||
|
||||
static struct state * this = NULL;
|
||||
|
||||
static const char * atomTypes[] =
|
||||
{
|
||||
"UTF8_STRING",
|
||||
"image/png",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/jpeg"
|
||||
};
|
||||
|
||||
static const char * x11_cb_getName()
|
||||
{
|
||||
return "X11";
|
||||
}
|
||||
|
||||
static bool x11_cb_init(
|
||||
SDL_SysWMinfo * wminfo,
|
||||
LG_ClipboardReleaseFn releaseFn,
|
||||
LG_ClipboardNotifyFn notifyFn,
|
||||
LG_ClipboardDataFn dataFn)
|
||||
{
|
||||
// final sanity check
|
||||
if (wminfo->subsystem != SDL_SYSWM_X11)
|
||||
{
|
||||
DEBUG_ERROR("wrong subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
this = (struct state *)malloc(sizeof(struct state));
|
||||
memset(this, 0, sizeof(struct state));
|
||||
|
||||
this->display = wminfo->info.x11.display;
|
||||
this->window = wminfo->info.x11.window;
|
||||
this->aSelection = XInternAtom(this->display, "CLIPBOARD", False);
|
||||
this->aTargets = XInternAtom(this->display, "TARGETS" , False);
|
||||
this->aSelData = XInternAtom(this->display, "SEL_DATA" , False);
|
||||
this->aIncr = XInternAtom(this->display, "INCR" , False);
|
||||
this->aCurSelection = BadValue;
|
||||
this->releaseFn = releaseFn;
|
||||
this->notifyFn = notifyFn;
|
||||
this->dataFn = dataFn;
|
||||
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
{
|
||||
this->aTypes[i] = XInternAtom(this->display, atomTypes[i], False);
|
||||
if (this->aTypes[i] == BadAlloc || this->aTypes[i] == BadValue)
|
||||
{
|
||||
DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]);
|
||||
free(this);
|
||||
this = NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we need the raw X events
|
||||
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
|
||||
|
||||
// use xfixes to get clipboard change notifications
|
||||
if (!XFixesQueryExtension(this->display, &this->eventBase, &this->errorBase))
|
||||
{
|
||||
DEBUG_ERROR("failed to initialize xfixes");
|
||||
free(this);
|
||||
this = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
XFixesSelectSelectionInput(this->display, this->window, XA_PRIMARY , XFixesSetSelectionOwnerNotifyMask);
|
||||
XFixesSelectSelectionInput(this->display, this->window, this->aSelection, XFixesSetSelectionOwnerNotifyMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11_cb_free()
|
||||
{
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static void x11_cb_reply_fn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size)
|
||||
{
|
||||
XEvent *s = (XEvent *)opaque;
|
||||
|
||||
XChangeProperty(
|
||||
this->display ,
|
||||
s->xselection.requestor,
|
||||
s->xselection.property ,
|
||||
s->xselection.target ,
|
||||
8,
|
||||
PropModeReplace,
|
||||
data,
|
||||
size);
|
||||
|
||||
XSendEvent(this->display, s->xselection.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void x11_cb_wmevent(SDL_SysWMmsg * msg)
|
||||
{
|
||||
XEvent e = msg->msg.x11.event;
|
||||
|
||||
if (e.type == SelectionRequest)
|
||||
{
|
||||
XEvent * s = (XEvent *)malloc(sizeof(XEvent));
|
||||
s->xselection.type = SelectionNotify;
|
||||
s->xselection.requestor = e.xselectionrequest.requestor;
|
||||
s->xselection.selection = e.xselectionrequest.selection;
|
||||
s->xselection.target = e.xselectionrequest.target;
|
||||
s->xselection.property = e.xselectionrequest.property;
|
||||
s->xselection.time = e.xselectionrequest.time;
|
||||
|
||||
if (!this->requestFn)
|
||||
{
|
||||
s->xselection.property = None;
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// target list requested
|
||||
if (e.xselectionrequest.target == this->aTargets)
|
||||
{
|
||||
Atom targets[2];
|
||||
targets[0] = this->aTargets;
|
||||
targets[1] = this->aTypes[this->type];
|
||||
|
||||
XChangeProperty(
|
||||
e.xselectionrequest.display,
|
||||
e.xselectionrequest.requestor,
|
||||
e.xselectionrequest.property,
|
||||
XA_ATOM,
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char*)targets,
|
||||
sizeof(targets) / sizeof(Atom));
|
||||
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// look to see if we can satisfy the data type
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (this->aTypes[i] == e.xselectionrequest.target && this->type == i)
|
||||
{
|
||||
// request the data
|
||||
this->requestFn(x11_cb_reply_fn, s);
|
||||
return;
|
||||
}
|
||||
|
||||
// report no data
|
||||
s->xselection.property = None;
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
if (e.type == SelectionClear && (
|
||||
e.xselectionclear.selection == XA_PRIMARY ||
|
||||
e.xselectionclear.selection == this->aSelection)
|
||||
)
|
||||
{
|
||||
this->aCurSelection = BadValue;
|
||||
this->releaseFn();
|
||||
return;
|
||||
}
|
||||
|
||||
// if someone selected data
|
||||
if (e.type == this->eventBase + XFixesSelectionNotify)
|
||||
{
|
||||
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&e;
|
||||
|
||||
// check if the selection is valid and it isn't ourself
|
||||
if (
|
||||
(sne->selection != XA_PRIMARY && sne->selection != this->aSelection) ||
|
||||
sne->owner == this->window ||
|
||||
sne->owner == 0
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// remember which selection we are working with
|
||||
this->aCurSelection = sne->selection;
|
||||
XConvertSelection(
|
||||
this->display,
|
||||
sne->selection,
|
||||
this->aTargets,
|
||||
this->aTargets,
|
||||
this->window,
|
||||
CurrentTime);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.type == SelectionNotify)
|
||||
{
|
||||
if (e.xselection.property == None)
|
||||
return;
|
||||
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount, after;
|
||||
unsigned char *data;
|
||||
|
||||
XGetWindowProperty(
|
||||
this->display,
|
||||
this->window,
|
||||
e.xselection.property,
|
||||
0, ~0L, // start and length
|
||||
True , // delete the property
|
||||
AnyPropertyType,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data);
|
||||
|
||||
// the target list
|
||||
if (e.xselection.property == this->aTargets)
|
||||
{
|
||||
// the format is 32-bit and we must have data
|
||||
// this is technically incorrect however as it's
|
||||
// an array of padded 64-bit values
|
||||
if (!data || format != 32)
|
||||
{
|
||||
if (data)
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we support any of the targets listed
|
||||
const uint64_t * targets = (const uint64_t *)data;
|
||||
for(unsigned long i = 0; i < itemCount; ++i)
|
||||
{
|
||||
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
||||
if (this->aTypes[n] == targets[i])
|
||||
{
|
||||
// we have a match, so send the notification
|
||||
this->notifyFn(n);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// no matches
|
||||
this->notifyFn(LG_CLIPBOARD_DATA_NONE);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (format == this->aIncr)
|
||||
{
|
||||
DEBUG_WARN("fixme: large paste buffers are not yet supported");
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (this->aTypes[i] == type)
|
||||
{
|
||||
this->dataFn(i, data, itemCount);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(this->display, type));
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void x11_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type)
|
||||
{
|
||||
this->requestFn = requestFn;
|
||||
this->type = type;
|
||||
XSetSelectionOwner(this->display, XA_PRIMARY , this->window, CurrentTime);
|
||||
XSetSelectionOwner(this->display, this->aSelection, this->window, CurrentTime);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
static void x11_cb_release()
|
||||
{
|
||||
this->requestFn = NULL;
|
||||
XSetSelectionOwner(this->display, XA_PRIMARY , None, CurrentTime);
|
||||
XSetSelectionOwner(this->display, this->aSelection, None, CurrentTime);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
static void x11_cb_request(LG_ClipboardData type)
|
||||
{
|
||||
if (this->aCurSelection == BadValue)
|
||||
return;
|
||||
|
||||
XConvertSelection(
|
||||
this->display,
|
||||
this->aCurSelection,
|
||||
this->aTypes[type],
|
||||
this->aSelData,
|
||||
this->window,
|
||||
CurrentTime);
|
||||
}
|
||||
|
||||
const LG_Clipboard LGC_X11 =
|
||||
{
|
||||
.getName = x11_cb_getName,
|
||||
.init = x11_cb_init,
|
||||
.free = x11_cb_free,
|
||||
.wmevent = x11_cb_wmevent,
|
||||
.notice = x11_cb_notice,
|
||||
.release = x11_cb_release,
|
||||
.request = x11_cb_request
|
||||
};
|
||||
19
client/cmake/FindGMP.cmake
Normal file
19
client/cmake/FindGMP.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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)
|
||||
34
client/cmake/MakeObject.cmake
Normal file
34
client/cmake/MakeObject.cmake
Normal file
@@ -0,0 +1,34 @@
|
||||
function(make_object out_var)
|
||||
set(result)
|
||||
set(result_h)
|
||||
foreach(in_f ${ARGN})
|
||||
file(RELATIVE_PATH out_f ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}")
|
||||
set(out_h "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.h")
|
||||
set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.o")
|
||||
string(REGEX REPLACE "[/.]" "_" sym_in ${in_f})
|
||||
|
||||
add_custom_command(OUTPUT ${out_f}
|
||||
COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} ${in_f}
|
||||
COMMAND ${CMAKE_OBJCOPY} --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA ${out_f} ${out_f}
|
||||
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_start=b_${sym_in} ${out_f} ${out_f}
|
||||
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_end=b_${sym_in}_end ${out_f} ${out_f}
|
||||
COMMAND ${CMAKE_OBJCOPY} --strip-symbol _binary_${sym_in}_size ${out_f} ${out_f}
|
||||
DEPENDS ${in_f}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Creating object from ${in_f}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
file(WRITE ${out_h} "extern const char b_${sym_in}[];\n")
|
||||
file(APPEND ${out_h} "extern const char b_${sym_in}_end[];\n")
|
||||
file(APPEND ${out_h} "#define b_${sym_in}_size (b_${sym_in}_end - b_${sym_in})\n")
|
||||
|
||||
get_filename_component(h_dir ${out_h} DIRECTORY)
|
||||
list(APPEND result_h ${h_dir})
|
||||
list(APPEND result ${out_f})
|
||||
endforeach()
|
||||
list(REMOVE_DUPLICATES result_h)
|
||||
|
||||
set(${out_var}_OBJS "${result}" PARENT_SCOPE)
|
||||
set(${out_var}_INCS "${result_h}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
25
client/decoders/CMakeLists.txt
Normal file
25
client/decoders/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(decoders LANGUAGES C)
|
||||
|
||||
#find_package(PkgConfig)
|
||||
#pkg_check_modules(DECODERS_PKGCONFIG REQUIRED
|
||||
#)
|
||||
|
||||
add_library(decoders STATIC
|
||||
src/null.c
|
||||
src/yuv420.c
|
||||
)
|
||||
|
||||
target_link_libraries(decoders
|
||||
lg_common
|
||||
${DECODERS_PKGCONFIG_LIBRARIES}
|
||||
)
|
||||
|
||||
target_include_directories(decoders
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${DECODERS_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
981
client/decoders/src/h264.c
Normal file
981
client/decoders/src/h264.c
Normal file
@@ -0,0 +1,981 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "lg-decoder.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "memcpySSE.h"
|
||||
#include "parsers/nal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <va/va_glx.h>
|
||||
|
||||
#define SURFACE_NUM 3
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
SDL_Window * window;
|
||||
VADisplay vaDisplay;
|
||||
int vaMajorVer, vaMinorVer;
|
||||
VASurfaceID vaSurfaceID[SURFACE_NUM];
|
||||
VAConfigID vaConfigID;
|
||||
VAContextID vaContextID;
|
||||
int lastSID;
|
||||
int currentSID;
|
||||
VAPictureH264 curPic;
|
||||
VAPictureH264 oldPic;
|
||||
int frameNum;
|
||||
int fieldCount;
|
||||
VABufferID picBufferID[SURFACE_NUM];
|
||||
VABufferID matBufferID[SURFACE_NUM];
|
||||
VABufferID sliBufferID[SURFACE_NUM];
|
||||
VABufferID datBufferID[SURFACE_NUM];
|
||||
bool t2First;
|
||||
int sliceType;
|
||||
|
||||
NAL nal;
|
||||
};
|
||||
|
||||
static const unsigned char MatrixBufferH264[] = {
|
||||
//ScalingList4x4[6][16]
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
//ScalingList8x8[2][64]
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
};
|
||||
|
||||
static bool lgd_h264_create (void ** opaque);
|
||||
static void lgd_h264_destroy (void * opaque);
|
||||
static bool lgd_h264_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_h264_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_h264_get_out_format (void * opaque);
|
||||
static unsigned int lgd_h264_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_h264_get_frame_stride(void * opaque);
|
||||
static bool lgd_h264_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static bool lgd_h264_get_buffer (void * opaque, uint8_t * dst, size_t dstSize);
|
||||
|
||||
static bool lgd_h264_init_gl_texture (void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
static void lgd_h264_free_gl_texture (void * opaque, void * ref);
|
||||
static bool lgd_h264_update_gl_texture(void * opaque, void * ref);
|
||||
|
||||
#define check_surface(x, y, z) _check_surface(__LINE__, x, y, z)
|
||||
static bool _check_surface(const unsigned int line, struct Inst * this, unsigned int sid, VASurfaceStatus *out)
|
||||
{
|
||||
VASurfaceStatus surfStatus;
|
||||
VAStatus status = vaQuerySurfaceStatus(
|
||||
this->vaDisplay,
|
||||
this->vaSurfaceID[sid],
|
||||
&surfStatus
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaQuerySurfaceStatus: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
DEBUG_INFO("L%d: surface %u status: %d", line, sid, surfStatus);
|
||||
#endif
|
||||
if (out)
|
||||
*out = surfStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
this->vaSurfaceID[0] = VA_INVALID_ID;
|
||||
this->vaConfigID = VA_INVALID_ID;
|
||||
this->vaContextID = VA_INVALID_ID;
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
this->picBufferID[i] =
|
||||
this->matBufferID[i] =
|
||||
this->sliBufferID[i] =
|
||||
this->datBufferID[i] = VA_INVALID_ID;
|
||||
|
||||
if (!nal_initialize(&this->nal))
|
||||
{
|
||||
DEBUG_INFO("Failed to initialize NAL parser");
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
lgd_h264_deinitialize(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_destroy(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
nal_deinitialize(this->nal);
|
||||
lgd_h264_deinitialize(this);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static bool lgd_h264_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
lgd_h264_deinitialize(this);
|
||||
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->window = window;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("Failed to get SDL window WM Info");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
this->vaDisplay = vaGetDisplayGLX(wminfo.info.x11.display);
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported window subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAStatus status;
|
||||
status = vaInitialize(this->vaDisplay, &this->vaMajorVer, &this->vaMinorVer);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaInitialize Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Vendor: %s", vaQueryVendorString(this->vaDisplay));
|
||||
|
||||
VAEntrypoint entryPoints[5];
|
||||
int entryPointCount;
|
||||
|
||||
status = vaQueryConfigEntrypoints(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
entryPoints,
|
||||
&entryPointCount
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaQueryConfigEntrypoints Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ep;
|
||||
for(ep = 0; ep < entryPointCount; ++ep)
|
||||
if (entryPoints[ep] == VAEntrypointVLD)
|
||||
break;
|
||||
|
||||
if (ep == entryPointCount)
|
||||
{
|
||||
DEBUG_ERROR("Failed to find VAEntrypointVLD index");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAConfigAttrib attrib;
|
||||
attrib.type = VAConfigAttribRTFormat;
|
||||
vaGetConfigAttributes(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
VAEntrypointVLD,
|
||||
&attrib,
|
||||
1);
|
||||
|
||||
if (!(attrib.value & VA_RT_FORMAT_YUV420))
|
||||
{
|
||||
DEBUG_ERROR("Failed to find desired YUV420 RT format");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaCreateConfig(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
VAEntrypointVLD,
|
||||
&attrib,
|
||||
1,
|
||||
&this->vaConfigID);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateConfig");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaCreateSurfaces(
|
||||
this->vaDisplay,
|
||||
VA_RT_FORMAT_YUV420,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
this->vaSurfaceID,
|
||||
SURFACE_NUM,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateSurfaces");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
if (!check_surface(this, i, NULL))
|
||||
return false;
|
||||
|
||||
status = vaCreateContext(
|
||||
this->vaDisplay,
|
||||
this->vaConfigID,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
VA_PROGRESSIVE,
|
||||
this->vaSurfaceID,
|
||||
SURFACE_NUM,
|
||||
&this->vaContextID
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateContext");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->currentSID = 0;
|
||||
this->sliceType = 2;
|
||||
this->t2First = true;
|
||||
|
||||
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[0]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaBeginPicture");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
{
|
||||
if (this->picBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->picBufferID[i]);
|
||||
|
||||
if (this->matBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->matBufferID[i]);
|
||||
|
||||
if (this->sliBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->sliBufferID[i]);
|
||||
|
||||
if (this->datBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->datBufferID[i]);
|
||||
|
||||
this->picBufferID[i] =
|
||||
this->matBufferID[i] =
|
||||
this->sliBufferID[i] =
|
||||
this->datBufferID[i] = VA_INVALID_ID;
|
||||
}
|
||||
|
||||
if (this->vaSurfaceID[0] != VA_INVALID_ID)
|
||||
vaDestroySurfaces(this->vaDisplay, this->vaSurfaceID, SURFACE_NUM);
|
||||
this->vaSurfaceID[0] = VA_INVALID_ID;
|
||||
|
||||
if (this->vaContextID != VA_INVALID_ID)
|
||||
vaDestroyContext(this->vaDisplay, this->vaContextID);
|
||||
this->vaContextID = VA_INVALID_ID;
|
||||
|
||||
if (this->vaConfigID != VA_INVALID_ID)
|
||||
vaDestroyConfig(this->vaDisplay, this->vaConfigID);
|
||||
this->vaConfigID = VA_INVALID_ID;
|
||||
|
||||
if (this->vaDisplay)
|
||||
vaTerminate(this->vaDisplay);
|
||||
this->vaDisplay = NULL;
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_h264_get_out_format(void * opaque)
|
||||
{
|
||||
return LG_OUTPUT_YUV420;
|
||||
}
|
||||
|
||||
static unsigned int lgd_h264_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width * 4;
|
||||
}
|
||||
|
||||
static unsigned int lgd_h264_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width;
|
||||
}
|
||||
|
||||
static bool get_buffer(struct Inst * this, const VABufferType type, const unsigned int size, VABufferID * buf_id)
|
||||
{
|
||||
if (*buf_id != VA_INVALID_ID)
|
||||
return true;
|
||||
|
||||
VAStatus status = vaCreateBuffer(this->vaDisplay, this->vaContextID, type, size, 1, NULL, buf_id);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create buffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!check_surface(this, this->currentSID, NULL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_pic_buffer(struct Inst * this, const NAL_SLICE * slice)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * picBufferID = &this->picBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VAPictureParameterBufferType, sizeof(VAPictureParameterBufferH264), picBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get picBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAPictureParameterBufferH264 *p;
|
||||
status = vaMapBuffer(this->vaDisplay, *picBufferID, (void **)&p);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
const NAL_SPS * sps;
|
||||
if (!nal_get_sps(this->nal, &sps))
|
||||
{
|
||||
DEBUG_ERROR("nal_get_sps");
|
||||
return false;
|
||||
}
|
||||
|
||||
const NAL_PPS * pps;
|
||||
if (!nal_get_pps(this->nal, &pps))
|
||||
{
|
||||
DEBUG_ERROR("nal_get_pps");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(p, 0, sizeof(VAPictureParameterBufferH264));
|
||||
p->picture_width_in_mbs_minus1 = sps->pic_width_in_mbs_minus1;
|
||||
p->picture_height_in_mbs_minus1 = sps->pic_height_in_map_units_minus1;
|
||||
p->bit_depth_luma_minus8 = sps->bit_depth_luma_minus8;
|
||||
p->bit_depth_chroma_minus8 = sps->bit_depth_chroma_minus8;
|
||||
p->num_ref_frames = sps->num_ref_frames;
|
||||
|
||||
p->seq_fields.value = 0;
|
||||
p->seq_fields.bits.chroma_format_idc = sps->chroma_format_idc;
|
||||
p->seq_fields.bits.residual_colour_transform_flag = sps->gaps_in_frame_num_value_allowed_flag;
|
||||
p->seq_fields.bits.frame_mbs_only_flag = sps->frame_mbs_only_flag;
|
||||
p->seq_fields.bits.mb_adaptive_frame_field_flag = sps->mb_adaptive_frame_field_flag;
|
||||
p->seq_fields.bits.direct_8x8_inference_flag = sps->direct_8x8_inference_flag;
|
||||
p->seq_fields.bits.MinLumaBiPredSize8x8 = sps->level_idc >= 31;
|
||||
p->seq_fields.bits.log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4;
|
||||
p->seq_fields.bits.pic_order_cnt_type = sps->pic_order_cnt_type;
|
||||
p->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = sps->log2_max_pic_order_cnt_lsb_minus4;
|
||||
p->seq_fields.bits.delta_pic_order_always_zero_flag = sps->delta_pic_order_always_zero_flag;
|
||||
|
||||
#if 0
|
||||
// these are deprecated, FMO is not supported
|
||||
p->num_slice_groups_minus1 = pps->num_slice_groups_minus1;
|
||||
p->slice_group_map_type = pps->slice_group_map_type;
|
||||
p->slice_group_change_rate_minus1 = pps->slice_group_change_rate_minus1;
|
||||
#endif
|
||||
|
||||
p->pic_init_qp_minus26 = pps->pic_init_qp_minus26;
|
||||
p->pic_init_qs_minus26 = pps->pic_init_qs_minus26;
|
||||
p->chroma_qp_index_offset = pps->chroma_qp_index_offset;
|
||||
p->second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset;
|
||||
|
||||
p->pic_fields.value = 0;
|
||||
p->pic_fields.bits.entropy_coding_mode_flag = pps->entropy_coding_mode_flag;
|
||||
p->pic_fields.bits.weighted_pred_flag = pps->weighted_pred_flag;
|
||||
p->pic_fields.bits.weighted_bipred_idc = pps->weighted_bipred_idc;
|
||||
p->pic_fields.bits.transform_8x8_mode_flag = pps->transform_8x8_mode_flag;
|
||||
p->pic_fields.bits.field_pic_flag = slice->field_pic_flag;
|
||||
p->pic_fields.bits.constrained_intra_pred_flag = pps->constrained_intra_pred_flag;
|
||||
p->pic_fields.bits.pic_order_present_flag = pps->pic_order_present_flag;
|
||||
p->pic_fields.bits.deblocking_filter_control_present_flag = pps->deblocking_filter_control_present_flag;
|
||||
p->pic_fields.bits.redundant_pic_cnt_present_flag = pps->redundant_pic_cnt_present_flag;
|
||||
p->pic_fields.bits.reference_pic_flag = slice->nal_ref_idc != 0;
|
||||
|
||||
p->frame_num = slice->frame_num;
|
||||
|
||||
for(int i = 0; i < 16; ++i)
|
||||
{
|
||||
p->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
|
||||
p->ReferenceFrames[i].picture_id = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
this->curPic.picture_id = this->vaSurfaceID[this->currentSID];
|
||||
this->curPic.frame_idx = p->frame_num;
|
||||
this->curPic.flags = 0;
|
||||
this->curPic.BottomFieldOrderCnt = this->fieldCount;
|
||||
this->curPic.TopFieldOrderCnt = this->fieldCount;
|
||||
memcpy(&p->CurrPic, &this->curPic, sizeof(VAPictureH264));
|
||||
|
||||
if (this->sliceType != 2)
|
||||
{
|
||||
memcpy(&p->ReferenceFrames[0], &this->oldPic, sizeof(VAPictureH264));
|
||||
p->ReferenceFrames[0].flags = 0;
|
||||
}
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *picBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_mat_buffer(struct Inst * this)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * matBufferID = &this->matBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VAIQMatrixBufferType, sizeof(VAIQMatrixBufferH264), matBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get matBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAIQMatrixBufferH264 * m;
|
||||
status = vaMapBuffer(this->vaDisplay, *matBufferID, (void **)&m);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(m, MatrixBufferH264, sizeof(MatrixBufferH264));
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *matBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fill_pred_weight_table(
|
||||
NAL_PW_TABLE_L * list,
|
||||
uint32_t active,
|
||||
uint32_t luma_log2_weight_denom,
|
||||
uint8_t luma_weight_flag,
|
||||
short luma_weight[32],
|
||||
short luma_offset[32],
|
||||
uint32_t chroma_log2_weight_denom,
|
||||
uint8_t chroma_weight_flag,
|
||||
short chroma_weight[32][2],
|
||||
short chroma_offset[32][2]
|
||||
)
|
||||
{
|
||||
assert(active < 32);
|
||||
|
||||
for(uint32_t i = 0; i <= active; ++i)
|
||||
{
|
||||
NAL_PW_TABLE_L * l = &list[i];
|
||||
if (luma_weight_flag)
|
||||
{
|
||||
luma_weight[i] = l->luma_weight;
|
||||
luma_offset[i] = l->luma_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
luma_weight[i] = 1 << luma_log2_weight_denom;
|
||||
luma_weight[i] = 0;
|
||||
}
|
||||
|
||||
if (chroma_weight_flag)
|
||||
{
|
||||
chroma_weight[i][0] = l->chroma_weight[0];
|
||||
chroma_offset[i][0] = l->chroma_offset[0];
|
||||
chroma_weight[i][1] = l->chroma_weight[1];
|
||||
chroma_offset[i][1] = l->chroma_offset[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
chroma_weight[i][0] = 1 << chroma_log2_weight_denom;
|
||||
chroma_weight[i][0] = 0;
|
||||
chroma_weight[i][1] = 1 << chroma_log2_weight_denom;
|
||||
chroma_weight[i][1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool setup_sli_buffer(struct Inst * this, size_t srcSize, const NAL_SLICE * slice, const size_t seek)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * sliBufferID = &this->sliBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VASliceParameterBufferType, sizeof(VASliceParameterBufferH264), sliBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get sliBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VASliceParameterBufferH264 * s;
|
||||
status = vaMapBuffer(this->vaDisplay, *sliBufferID, (void **)&s);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(s, 0, sizeof(VASliceParameterBufferH264));
|
||||
|
||||
s->slice_data_size = srcSize;
|
||||
s->slice_data_bit_offset = seek << 3;
|
||||
s->slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
|
||||
|
||||
s->first_mb_in_slice = slice->first_mb_in_slice;
|
||||
s->slice_type = slice->slice_type;
|
||||
s->direct_spatial_mv_pred_flag = slice->direct_spatial_mv_pred_flag;
|
||||
s->num_ref_idx_l0_active_minus1 = slice->num_ref_idx_l0_active_minus1;
|
||||
s->num_ref_idx_l1_active_minus1 = slice->num_ref_idx_l1_active_minus1;
|
||||
s->cabac_init_idc = slice->cabac_init_idc;
|
||||
s->slice_qp_delta = slice->slice_qp_delta;
|
||||
s->disable_deblocking_filter_idc = slice->disable_deblocking_filter_idc;
|
||||
s->slice_alpha_c0_offset_div2 = slice->slice_alpha_c0_offset_div2;
|
||||
s->slice_beta_offset_div2 = slice->slice_beta_offset_div2;
|
||||
s->luma_log2_weight_denom = slice->pred_weight_table.luma_log2_weight_denom;
|
||||
s->chroma_log2_weight_denom = slice->pred_weight_table.chroma_log2_weight_denom;
|
||||
s->luma_weight_l0_flag = slice->pred_weight_table.luma_weight_flag [0];
|
||||
s->chroma_weight_l0_flag = slice->pred_weight_table.chroma_weight_flag[0];
|
||||
s->luma_weight_l1_flag = slice->pred_weight_table.luma_weight_flag [1];
|
||||
s->chroma_weight_l1_flag = slice->pred_weight_table.chroma_weight_flag[1];
|
||||
|
||||
//RefPicList0/1
|
||||
|
||||
fill_pred_weight_table(
|
||||
slice->pred_weight_table.l0,
|
||||
s->num_ref_idx_l0_active_minus1,
|
||||
s->luma_log2_weight_denom,
|
||||
s->luma_weight_l0_flag,
|
||||
s->luma_weight_l0,
|
||||
s->luma_offset_l0,
|
||||
s->chroma_log2_weight_denom,
|
||||
s->chroma_weight_l0_flag,
|
||||
s->chroma_weight_l0,
|
||||
s->chroma_weight_l0
|
||||
);
|
||||
|
||||
fill_pred_weight_table(
|
||||
slice->pred_weight_table.l1,
|
||||
s->num_ref_idx_l1_active_minus1,
|
||||
s->luma_log2_weight_denom,
|
||||
s->luma_weight_l1_flag,
|
||||
s->luma_weight_l1,
|
||||
s->luma_offset_l1,
|
||||
s->chroma_log2_weight_denom,
|
||||
s->chroma_weight_l1_flag,
|
||||
s->chroma_weight_l1,
|
||||
s->chroma_weight_l1
|
||||
);
|
||||
|
||||
#if 0
|
||||
if (this->sliceType == 2)
|
||||
{
|
||||
set_slice_parameter_buffer_t2(s, this->t2First);
|
||||
this->t2First = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_slice_parameter_buffer(s);
|
||||
memcpy(&s->RefPicList0[0], &this->oldPic, sizeof(VAPictureH264));
|
||||
s->RefPicList0[0].flags = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *sliBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_dat_buffer(struct Inst * this, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * datBufferID = &this->datBufferID[this->currentSID];
|
||||
|
||||
if (!get_buffer(this, VASliceDataBufferType, srcSize, datBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get datBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t * d;
|
||||
status = vaMapBuffer(this->vaDisplay, *datBufferID, (void **)&d);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpySSE(d, src, srcSize);
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *datBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
VAStatus status;
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
size_t seek;
|
||||
if (!nal_parse(this->nal, src, srcSize, &seek))
|
||||
{
|
||||
DEBUG_WARN("nal_parse, perhaps mid stream");
|
||||
return true;
|
||||
}
|
||||
|
||||
const NAL_SLICE * slice;
|
||||
if (!nal_get_slice(this->nal, &slice))
|
||||
{
|
||||
DEBUG_WARN("nal_get_slice failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(seek < srcSize);
|
||||
this->sliceType = slice->slice_type;
|
||||
|
||||
// don't start until we have an I-FRAME
|
||||
if (this->frameNum == 0 && this->sliceType != NAL_SLICE_TYPE_I)
|
||||
return true;
|
||||
|
||||
{
|
||||
if (!setup_pic_buffer(this, slice)) return false;
|
||||
if (!setup_mat_buffer(this)) return false;
|
||||
|
||||
VABufferID bufferIDs[] =
|
||||
{
|
||||
this->picBufferID[this->currentSID],
|
||||
this->matBufferID[this->currentSID]
|
||||
};
|
||||
|
||||
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// intel broke the ABI here, see:
|
||||
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
|
||||
// the buffers are released by vaRenderPicture in old versions
|
||||
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
|
||||
{
|
||||
this->picBufferID[this->currentSID] =
|
||||
this->matBufferID[this->currentSID] = VA_INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (!setup_sli_buffer(this, srcSize, slice, seek)) return false;
|
||||
if (!setup_dat_buffer(this, src, srcSize )) return false;
|
||||
VABufferID bufferIDs[] =
|
||||
{
|
||||
this->sliBufferID[this->currentSID],
|
||||
this->datBufferID[this->currentSID]
|
||||
};
|
||||
|
||||
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// intel broke the ABI here, see:
|
||||
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
|
||||
// the buffers are released by vaRenderPicture in old versions
|
||||
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
|
||||
{
|
||||
this->sliBufferID[this->currentSID] =
|
||||
this->datBufferID[this->currentSID] = VA_INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
status = vaEndPicture(this->vaDisplay, this->vaContextID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaEndPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// advance to the next surface and save the old picture info
|
||||
this->lastSID = this->currentSID;
|
||||
if (++this->currentSID == SURFACE_NUM)
|
||||
this->currentSID = 0;
|
||||
this->frameNum += 1;
|
||||
this->fieldCount += 2;
|
||||
memcpy(&this->oldPic, &this->curPic, sizeof(VAPictureH264));
|
||||
|
||||
// prepare the next surface
|
||||
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[this->currentSID]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaBeginPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_get_buffer(void * opaque, uint8_t * dst, size_t dstSize)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
// don't return anything until we have some data
|
||||
if (this->frameNum == 0)
|
||||
return true;
|
||||
|
||||
// ensure the surface is ready
|
||||
status = vaSyncSurface(this->vaDisplay, this->vaSurfaceID[this->lastSID]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaSyncSurface: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// this doesn't work on my system, seems the vdpau va driver is bugged
|
||||
VASurfaceStatus surfStatus;
|
||||
if (!check_surface(this, this->lastSID, &surfStatus))
|
||||
return false;
|
||||
|
||||
if (surfStatus != VASurfaceReady)
|
||||
{
|
||||
DEBUG_ERROR("vaSyncSurface didn't block, the surface is not ready!");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// get the decoded data
|
||||
VAImage decoded =
|
||||
{
|
||||
.image_id = VA_INVALID_ID,
|
||||
.buf = VA_INVALID_ID
|
||||
};
|
||||
|
||||
status = vaDeriveImage(this->vaDisplay, this->vaSurfaceID[this->lastSID], &decoded);
|
||||
if (status == VA_STATUS_ERROR_OPERATION_FAILED)
|
||||
{
|
||||
VAImageFormat format =
|
||||
{
|
||||
.fourcc = VA_FOURCC_NV12,
|
||||
.byte_order = VA_LSB_FIRST,
|
||||
.bits_per_pixel = 12
|
||||
};
|
||||
|
||||
status = vaCreateImage(
|
||||
this->vaDisplay,
|
||||
&format,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
&decoded
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaPutImage(
|
||||
this->vaDisplay,
|
||||
this->vaSurfaceID[this->lastSID],
|
||||
decoded.image_id,
|
||||
0 , 0 ,
|
||||
this->format.width, this->format.height,
|
||||
0 , 0 ,
|
||||
this->format.width, this->format.height
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaPutImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaDeriveImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t * d;
|
||||
status = vaMapBuffer(this->vaDisplay, decoded.buf, (void **)&d);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpySSE(dst, d, decoded.data_size);
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, decoded.buf);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaDestroyImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
status = vaCreateSurfaceGLX(this->vaDisplay, target, texture, ref);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
*ref = NULL;
|
||||
DEBUG_ERROR("vaCreateSurfaceGLX: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_free_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
status = vaDestroySurfaceGLX(this->vaDisplay, ref);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
DEBUG_ERROR("vaDestroySurfaceGLX: %s", vaErrorStr(status));
|
||||
}
|
||||
|
||||
static bool lgd_h264_update_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
// don't return anything until we have some data
|
||||
if (this->frameNum == 0)
|
||||
return true;
|
||||
|
||||
status = vaCopySurfaceGLX(
|
||||
this->vaDisplay,
|
||||
ref,
|
||||
this->vaSurfaceID[this->lastSID],
|
||||
0
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCopySurfaceGLX: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_H264 =
|
||||
{
|
||||
.name = "H.264",
|
||||
.create = lgd_h264_create,
|
||||
.destroy = lgd_h264_destroy,
|
||||
.initialize = lgd_h264_initialize,
|
||||
.deinitialize = lgd_h264_deinitialize,
|
||||
.get_out_format = lgd_h264_get_out_format,
|
||||
.get_frame_pitch = lgd_h264_get_frame_pitch,
|
||||
.get_frame_stride = lgd_h264_get_frame_stride,
|
||||
.decode = lgd_h264_decode,
|
||||
.get_buffer = lgd_h264_get_buffer,
|
||||
|
||||
.has_gl = true,
|
||||
.init_gl_texture = lgd_h264_init_gl_texture,
|
||||
.free_gl_texture = lgd_h264_free_gl_texture,
|
||||
.update_gl_texture = lgd_h264_update_gl_texture
|
||||
};
|
||||
130
client/decoders/src/null.c
Normal file
130
client/decoders/src/null.c
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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/decoder.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memcpySSE.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
const uint8_t * src;
|
||||
};
|
||||
|
||||
static bool lgd_null_create (void ** opaque);
|
||||
static void lgd_null_destroy (void * opaque);
|
||||
static bool lgd_null_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_null_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_null_get_out_format (void * opaque);
|
||||
static unsigned int lgd_null_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_null_get_frame_stride(void * opaque);
|
||||
static bool lgd_null_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static const uint8_t * lgd_null_get_buffer (void * opaque);
|
||||
|
||||
static bool lgd_null_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_null_destroy(void * opaque)
|
||||
{
|
||||
free(opaque);
|
||||
}
|
||||
|
||||
static bool lgd_null_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_null_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memset(this, 0, sizeof(struct Inst));
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_null_get_out_format(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
switch(this->format.type)
|
||||
{
|
||||
case FRAME_TYPE_BGRA : return LG_OUTPUT_BGRA;
|
||||
case FRAME_TYPE_RGBA : return LG_OUTPUT_RGBA;
|
||||
case FRAME_TYPE_RGBA10: return LG_OUTPUT_RGBA10;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown frame type");
|
||||
return LG_OUTPUT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int lgd_null_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.pitch;
|
||||
}
|
||||
|
||||
static unsigned int lgd_null_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.stride;
|
||||
}
|
||||
|
||||
static bool lgd_null_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->src = src;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t * lgd_null_get_buffer(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->src)
|
||||
return NULL;
|
||||
|
||||
return this->src;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_NULL =
|
||||
{
|
||||
.name = "NULL",
|
||||
.create = lgd_null_create,
|
||||
.destroy = lgd_null_destroy,
|
||||
.initialize = lgd_null_initialize,
|
||||
.deinitialize = lgd_null_deinitialize,
|
||||
.get_out_format = lgd_null_get_out_format,
|
||||
.get_frame_pitch = lgd_null_get_frame_pitch,
|
||||
.get_frame_stride = lgd_null_get_frame_stride,
|
||||
.decode = lgd_null_decode,
|
||||
.get_buffer = lgd_null_get_buffer
|
||||
};
|
||||
169
client/decoders/src/yuv420.c
Normal file
169
client/decoders/src/yuv420.c
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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/decoder.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memcpySSE.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
struct Pixel
|
||||
{
|
||||
uint8_t b, g, r, a;
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
struct Pixel * pixels;
|
||||
unsigned int yBytes;
|
||||
};
|
||||
|
||||
static bool lgd_yuv420_create (void ** opaque);
|
||||
static void lgd_yuv420_destroy (void * opaque);
|
||||
static bool lgd_yuv420_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_yuv420_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_yuv420_get_out_format (void * opaque);
|
||||
static unsigned int lgd_yuv420_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_yuv420_get_frame_stride(void * opaque);
|
||||
static bool lgd_yuv420_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static const uint8_t * lgd_yuv420_get_buffer (void * opaque);
|
||||
|
||||
static bool lgd_yuv420_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_yuv420_destroy(void * opaque)
|
||||
{
|
||||
free(opaque);
|
||||
}
|
||||
|
||||
static bool lgd_yuv420_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
|
||||
this->yBytes = format.width * format.height;
|
||||
this->pixels = malloc(sizeof(struct Pixel) * (format.width * format.height));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_yuv420_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
free(this->pixels);
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_yuv420_get_out_format(void * opaque)
|
||||
{
|
||||
return LG_OUTPUT_BGRA;
|
||||
}
|
||||
|
||||
static unsigned int lgd_yuv420_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width * 4;
|
||||
}
|
||||
|
||||
static unsigned int lgd_yuv420_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width;
|
||||
}
|
||||
|
||||
static bool lgd_yuv420_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
//FIXME: implement this properly using GLSL
|
||||
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
const unsigned int hw = this->format.width / 2;
|
||||
const unsigned int hp = this->yBytes / 4;
|
||||
|
||||
for(size_t y = 0; y < this->format.height; ++y)
|
||||
for(size_t x = 0; x < this->format.width; ++x)
|
||||
{
|
||||
const unsigned int yoff = y * this->format.width + x;
|
||||
const unsigned int uoff = this->yBytes + ((y / 2) * hw + x / 2);
|
||||
const unsigned int voff = uoff + hp;
|
||||
|
||||
float b = 1.164f * ((float)src[yoff] - 16.0f) + 2.018f * ((float)src[uoff] - 128.0f);
|
||||
float g = 1.164f * ((float)src[yoff] - 16.0f) - 0.813f * ((float)src[voff] - 128.0f) - 0.391f * ((float)src[uoff] - 128.0f);
|
||||
float r = 1.164f * ((float)src[yoff] - 16.0f) + 1.596f * ((float)src[voff] - 128.0f);
|
||||
|
||||
#define CLAMP(x) (x < 0 ? 0 : (x > 255 ? 255 : x))
|
||||
this->pixels[yoff].b = CLAMP(b);
|
||||
this->pixels[yoff].g = CLAMP(g);
|
||||
this->pixels[yoff].r = CLAMP(r);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t * lgd_yuv420_get_buffer(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return (uint8_t *)this->pixels;
|
||||
}
|
||||
|
||||
bool lgd_yuv420_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void lgd_yuv420_free_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool lgd_yuv420_update_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_YUV420 =
|
||||
{
|
||||
.name = "YUV420",
|
||||
.create = lgd_yuv420_create,
|
||||
.destroy = lgd_yuv420_destroy,
|
||||
.initialize = lgd_yuv420_initialize,
|
||||
.deinitialize = lgd_yuv420_deinitialize,
|
||||
.get_out_format = lgd_yuv420_get_out_format,
|
||||
.get_frame_pitch = lgd_yuv420_get_frame_pitch,
|
||||
.get_frame_stride = lgd_yuv420_get_frame_stride,
|
||||
.decode = lgd_yuv420_decode,
|
||||
.get_buffer = lgd_yuv420_get_buffer,
|
||||
|
||||
.has_gl = false, //FIXME: Implement this
|
||||
.init_gl_texture = lgd_yuv420_init_gl_texture,
|
||||
.free_gl_texture = lgd_yuv420_free_gl_texture,
|
||||
.update_gl_texture = lgd_yuv420_update_gl_texture
|
||||
};
|
||||
41
client/fonts/CMakeLists.txt
Normal file
41
client/fonts/CMakeLists.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
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(SDL)
|
||||
|
||||
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})
|
||||
26
client/fonts/SDL/CMakeLists.txt
Normal file
26
client/fonts/SDL/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(font_SDL LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(FONT_SDL_PKGCONFIG REQUIRED
|
||||
SDL2_ttf
|
||||
fontconfig
|
||||
)
|
||||
|
||||
add_library(font_SDL STATIC
|
||||
src/sdl.c
|
||||
)
|
||||
|
||||
target_link_libraries(font_SDL
|
||||
${FONT_SDL_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(font_SDL
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${FONT_SDL_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
153
client/fonts/SDL/src/sdl.c
Normal file
153
client/fonts/SDL/src/sdl.c
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <SDL2/SDL_ttf.h>
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
static int g_initCount = 0;
|
||||
static FcConfig * g_fontConfig = NULL;
|
||||
|
||||
struct Inst
|
||||
{
|
||||
TTF_Font * font;
|
||||
};
|
||||
|
||||
static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
|
||||
{
|
||||
if (g_initCount++ == 0)
|
||||
{
|
||||
if (TTF_Init() < 0)
|
||||
{
|
||||
DEBUG_ERROR("TTF_Init Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_fontConfig = FcInitLoadConfigAndFonts();
|
||||
if (!g_fontConfig)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
FcConfigSubstitute (g_fontConfig, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * font = FcFontMatch(g_fontConfig, pat, &result);
|
||||
|
||||
if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch))
|
||||
{
|
||||
this->font = TTF_OpenFont((char *)file, size);
|
||||
if (!this->font)
|
||||
{
|
||||
DEBUG_ERROR("TTL_OpenFont Failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
|
||||
return false;
|
||||
}
|
||||
FcPatternDestroy(pat);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgf_sdl_destroy(LG_FontObj opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (this->font)
|
||||
TTF_CloseFont(this->font);
|
||||
free(this);
|
||||
|
||||
if (--g_initCount == 0)
|
||||
TTF_Quit();
|
||||
}
|
||||
|
||||
static LG_FontBitmap * lgf_sdl_render(LG_FontObj opaque, unsigned int fg_color, const char * text)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
SDL_Surface * surface;
|
||||
SDL_Color color;
|
||||
color.r = (fg_color & 0xff000000) >> 24;
|
||||
color.g = (fg_color & 0x00ff0000) >> 16;
|
||||
color.b = (fg_color & 0x0000ff00) >> 8;
|
||||
color.a = (fg_color & 0x000000ff) >> 0;
|
||||
|
||||
if (!(surface = TTF_RenderText_Blended(this->font, text, color)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to render text: %s", TTF_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LG_FontBitmap * out = malloc(sizeof(LG_FontBitmap));
|
||||
if (!out)
|
||||
{
|
||||
SDL_FreeSurface(surface);
|
||||
DEBUG_ERROR("Failed to allocate memory for font bitmap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out->reserved = surface;
|
||||
out->width = surface->w;
|
||||
out->height = surface->h;
|
||||
out->bpp = surface->format->BytesPerPixel;
|
||||
out->pixels = surface->pixels;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void lgf_sdl_release(LG_FontObj opaque, LG_FontBitmap * font)
|
||||
{
|
||||
LG_FontBitmap * bitmap = (LG_FontBitmap *)font;
|
||||
SDL_FreeSurface(bitmap->reserved);
|
||||
free(bitmap);
|
||||
}
|
||||
|
||||
struct LG_Font LGF_SDL =
|
||||
{
|
||||
.name = "SDL",
|
||||
.create = lgf_sdl_create,
|
||||
.destroy = lgf_sdl_destroy,
|
||||
.render = lgf_sdl_render,
|
||||
.release = lgf_sdl_release
|
||||
};
|
||||
58
client/include/interface/app.h
Normal file
58
client/include/interface/app.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <SDL2/SDL.h>
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
LG_ALERT_INFO ,
|
||||
LG_ALERT_SUCCESS,
|
||||
LG_ALERT_WARNING,
|
||||
LG_ALERT_ERROR
|
||||
}
|
||||
LG_MsgAlert;
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*SuperEventFn)(SDL_Scancode key, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
* param fmt The alert message format
|
||||
@ param ... formatted message values
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param key The scancode to register
|
||||
* @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_release_keybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
* @param handle A pointer to the keybind handle to release, may be NULL
|
||||
*/
|
||||
void app_release_keybind(KeybindHandle * handle);
|
||||
62
client/include/interface/clipboard.h
Normal file
62
client/include/interface/clipboard.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||
LG_CLIPBOARD_DATA_PNG,
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
||||
}
|
||||
LG_ClipboardData;
|
||||
|
||||
typedef void (* LG_ClipboardReplyFn )(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size);
|
||||
typedef void (* LG_ClipboardRequestFn)(LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
typedef void (* LG_ClipboardReleaseFn)();
|
||||
typedef void (* LG_ClipboardNotifyFn)(LG_ClipboardData type);
|
||||
typedef void (* LG_ClipboardDataFn )(const LG_ClipboardData type, uint8_t * data, size_t size);
|
||||
|
||||
typedef const char * (* LG_ClipboardGetName)();
|
||||
typedef bool (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo, LG_ClipboardReleaseFn releaseFn, LG_ClipboardNotifyFn notifyFn, LG_ClipboardDataFn dataFn);
|
||||
typedef void (* LG_ClipboardFree)();
|
||||
typedef void (* LG_ClipboardWMEvent)(SDL_SysWMmsg * msg);
|
||||
typedef void (* LG_ClipboardNotice)(LG_ClipboardRequestFn requestFn, LG_ClipboardData type);
|
||||
typedef void (* LG_ClipboardRelease)();
|
||||
typedef void (* LG_ClipboardRequest)(LG_ClipboardData type);
|
||||
|
||||
typedef struct LG_Clipboard
|
||||
{
|
||||
LG_ClipboardGetName getName;
|
||||
LG_ClipboardInit init;
|
||||
LG_ClipboardFree free;
|
||||
LG_ClipboardWMEvent wmevent;
|
||||
LG_ClipboardNotice notice;
|
||||
LG_ClipboardRelease release;
|
||||
LG_ClipboardRequest request;
|
||||
}
|
||||
LG_Clipboard;
|
||||
75
client/include/interface/decoder.h
Normal file
75
client/include/interface/decoder.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "renderer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef enum LG_OutFormat
|
||||
{
|
||||
LG_OUTPUT_INVALID,
|
||||
|
||||
LG_OUTPUT_BGRA,
|
||||
LG_OUTPUT_RGBA,
|
||||
LG_OUTPUT_RGBA10,
|
||||
LG_OUTPUT_YUV420
|
||||
}
|
||||
LG_OutFormat;
|
||||
|
||||
typedef bool (* LG_DecoderCreate )(void ** opaque);
|
||||
typedef void (* LG_DecoderDestroy )(void * opaque);
|
||||
typedef bool (* LG_DecoderInitialize )(void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
typedef void (* LG_DecoderDeInitialize )(void * opaque);
|
||||
typedef LG_OutFormat (* LG_DecoderGetOutFormat )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFramePitch )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFrameStride)(void * opaque);
|
||||
typedef bool (* LG_DecoderDecode )(void * opaque, const uint8_t * src, size_t srcSize);
|
||||
typedef const uint8_t * (* LG_DecoderGetBuffer )(void * opaque);
|
||||
|
||||
typedef bool (* LG_DecoderInitGLTexture )(void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
typedef void (* LG_DecoderFreeGLTexture )(void * opaque, void * ref);
|
||||
typedef bool (* LG_DecoderUpdateGLTexture)(void * opaque, void * ref);
|
||||
|
||||
typedef struct LG_Decoder
|
||||
{
|
||||
// mandatory support
|
||||
const char * name;
|
||||
LG_DecoderCreate create;
|
||||
LG_DecoderDestroy destroy;
|
||||
LG_DecoderInitialize initialize;
|
||||
LG_DecoderDeInitialize deinitialize;
|
||||
LG_DecoderGetOutFormat get_out_format;
|
||||
LG_DecoderGetFramePitch get_frame_pitch;
|
||||
LG_DecoderGetFrameStride get_frame_stride;
|
||||
LG_DecoderDecode decode;
|
||||
LG_DecoderGetBuffer get_buffer;
|
||||
|
||||
// optional support
|
||||
const bool has_gl;
|
||||
LG_DecoderInitGLTexture init_gl_texture;
|
||||
LG_DecoderFreeGLTexture free_gl_texture;
|
||||
LG_DecoderUpdateGLTexture update_gl_texture;
|
||||
}
|
||||
LG_Decoder;
|
||||
50
client/include/interface/font.h
Normal file
50
client/include/interface/font.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef void * LG_FontObj;
|
||||
typedef struct LG_FontBitmap
|
||||
{
|
||||
void * reserved;
|
||||
|
||||
unsigned int width, height;
|
||||
unsigned int bpp; // bytes per pixel
|
||||
uint8_t * pixels;
|
||||
}
|
||||
LG_FontBitmap;
|
||||
|
||||
typedef bool (* LG_FontCreate )(LG_FontObj * opaque, const char * font_name, unsigned int size);
|
||||
typedef void (* LG_FontDestroy )(LG_FontObj opaque);
|
||||
typedef LG_FontBitmap * (* LG_FontRender )(LG_FontObj opaque, unsigned int fg_color, const char * text);
|
||||
typedef void (* LG_FontRelease )(LG_FontObj opaque, LG_FontBitmap * bitmap);
|
||||
|
||||
typedef struct LG_Font
|
||||
{
|
||||
// mandatory support
|
||||
const char * name;
|
||||
LG_FontCreate create;
|
||||
LG_FontDestroy destroy;
|
||||
LG_FontRender render;
|
||||
LG_FontRelease release;
|
||||
}
|
||||
LG_Font;
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -24,57 +24,46 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/KVMFR.h"
|
||||
#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)->render)
|
||||
|
||||
#define LGR_OPTION_COUNT(x) (sizeof(x) / sizeof(LG_RendererOpt))
|
||||
|
||||
typedef bool(* LG_RendererOptValidator)(const char * value);
|
||||
typedef void(* LG_RendererOptHandler )(void * opaque, const char * value);
|
||||
|
||||
typedef struct LG_RendererOpt
|
||||
{
|
||||
const char * name;
|
||||
const char * desc;
|
||||
LG_RendererOptValidator validator;
|
||||
LG_RendererOptHandler handler;
|
||||
}
|
||||
LG_RendererOpt;
|
||||
|
||||
typedef struct LG_RendererOptValue
|
||||
{
|
||||
const LG_RendererOpt * opt;
|
||||
const char * value;
|
||||
} LG_RendererOptValue;
|
||||
|
||||
typedef LG_RendererOpt * LG_RendererOptions;
|
||||
(x)->on_alert && \
|
||||
(x)->render_startup && \
|
||||
(x)->render && \
|
||||
(x)->update_fps)
|
||||
|
||||
typedef struct LG_RendererParams
|
||||
{
|
||||
TTF_Font * font;
|
||||
// TTF_Font * font;
|
||||
// TTF_Font * alertFont;
|
||||
bool showFPS;
|
||||
}
|
||||
LG_RendererParams;
|
||||
|
||||
typedef struct LG_RendererFormat
|
||||
{
|
||||
FrameType type; // frame type
|
||||
unsigned int width; // image width
|
||||
unsigned int height; // image height
|
||||
unsigned int stride; // scanline width
|
||||
unsigned int pitch; // scanline bytes
|
||||
unsigned int bpp; // bits per pixel
|
||||
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)
|
||||
}
|
||||
LG_RendererFormat;
|
||||
|
||||
typedef struct LG_RendererRect
|
||||
{
|
||||
bool valid;
|
||||
int x;
|
||||
int y;
|
||||
unsigned int w;
|
||||
@@ -90,32 +79,40 @@ typedef enum LG_RendererCursor
|
||||
}
|
||||
LG_RendererCursor;
|
||||
|
||||
typedef const char * (* LG_RendererGetName )();
|
||||
// returns the friendly name of the renderer
|
||||
typedef const char * (* LG_RendererGetName)();
|
||||
|
||||
// called pre-creation to allow the renderer to register any options it might have
|
||||
typedef void (* LG_RendererSetup)();
|
||||
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
||||
typedef void (* LG_RendererDeInitialize)(void * opaque);
|
||||
typedef void (* LG_RendererOnRestart )(void * opaque);
|
||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
|
||||
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_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const uint8_t * data);
|
||||
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const FrameBuffer * frame);
|
||||
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
|
||||
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
|
||||
|
||||
typedef struct LG_Renderer
|
||||
{
|
||||
LG_RendererCreate create;
|
||||
LG_RendererGetName get_name;
|
||||
LG_RendererOptions options;
|
||||
unsigned int option_count;
|
||||
LG_RendererSetup setup;
|
||||
|
||||
LG_RendererCreate create;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererOnRestart on_restart;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
LG_RendererOnFrameEvent on_frame_event;
|
||||
LG_RendererOnAlert on_alert;
|
||||
LG_RendererRender render_startup;
|
||||
LG_RendererRender render;
|
||||
LG_RendererUpdateFPS update_fps;
|
||||
}
|
||||
LG_Renderer;
|
||||
|
||||
// generic option helpers
|
||||
bool LG_RendererValidatorBool(const char * value);
|
||||
bool LG_RendererValueToBool (const char * value);
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -18,16 +18,16 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "lg-renderer.h"
|
||||
#include "interface/decoder.h"
|
||||
|
||||
extern const LG_Renderer LGR_OpenGL;
|
||||
//extern const LG_Renderer LGR_OpenGLBasic;
|
||||
extern const LG_Decoder LGD_NULL;
|
||||
extern const LG_Decoder LGD_YUV420;
|
||||
|
||||
const LG_Renderer * LG_Renderers[] =
|
||||
const LG_Decoder * LG_Decoders[] =
|
||||
{
|
||||
&LGR_OpenGL,
|
||||
// &LGR_OpenGLBasic,
|
||||
&LGD_NULL,
|
||||
&LGD_YUV420,
|
||||
NULL // end of array sentinal
|
||||
};
|
||||
|
||||
#define LG_RENDERER_COUNT ((sizeof(LG_Renderers) / sizeof(LG_Renderer *)) - 1)
|
||||
#define LG_DECODER_COUNT ((sizeof(LG_Decoders) / sizeof(LG_Decoder *)) - 1)
|
||||
31
client/include/ll.h
Normal file
31
client/include/ll.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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);
|
||||
unsigned int ll_count (struct ll * list);
|
||||
|
||||
void ll_reset (struct ll * list);
|
||||
bool ll_walk (struct ll * list, void ** data);
|
||||
27
client/include/utils.h
Normal file
27
client/include/utils.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// reads the specified file into a new buffer
|
||||
// the callee must free the buffer
|
||||
bool file_get_contents(const char * filename, char ** buffer, size_t * length);
|
||||
@@ -1,548 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "ivshmem.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define MAX_IRQS 32
|
||||
|
||||
struct IVSHMEMServer
|
||||
{
|
||||
int64_t version;
|
||||
int64_t clientID;
|
||||
int sharedFD;
|
||||
|
||||
int irqs[MAX_IRQS];
|
||||
int irqCount;
|
||||
};
|
||||
|
||||
struct IVSHMEMClient
|
||||
{
|
||||
uint16_t clientID;
|
||||
|
||||
int irqs[MAX_IRQS];
|
||||
int irqCount;
|
||||
|
||||
struct IVSHMEMClient * last;
|
||||
struct IVSHMEMClient * next;
|
||||
};
|
||||
|
||||
struct IVSHMEM
|
||||
{
|
||||
bool connected;
|
||||
bool shutdown;
|
||||
int socket;
|
||||
struct IVSHMEMServer server;
|
||||
struct IVSHMEMClient * clients;
|
||||
|
||||
off_t mapSize;
|
||||
void * map;
|
||||
};
|
||||
|
||||
struct IVSHMEM ivshmem =
|
||||
{
|
||||
.connected = false,
|
||||
.shutdown = false,
|
||||
.socket = -1
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// internal functions
|
||||
|
||||
void ivshmem_cleanup();
|
||||
bool ivshmem_read(void * buffer, const ssize_t size);
|
||||
bool ivshmem_read_msg(int64_t * index, int *fd);
|
||||
struct IVSHMEMClient * ivshmem_get_client(uint16_t clientID);
|
||||
void ivshmem_remove_client(struct IVSHMEMClient * client);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_connect(const char * unix_socket)
|
||||
{
|
||||
ivshmem.shutdown = false;
|
||||
ivshmem.socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (ivshmem.socket < 0)
|
||||
{
|
||||
DEBUG_ERROR("socket creation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, unix_socket, sizeof(addr.sun_path));
|
||||
|
||||
if (connect(ivshmem.socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
|
||||
{
|
||||
DEBUG_ERROR("socket connect failed");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
ivshmem.connected = true;
|
||||
|
||||
if (!ivshmem_read(&ivshmem.server.version, sizeof(ivshmem.server.version)))
|
||||
{
|
||||
DEBUG_ERROR("read protocol version failed");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ivshmem.server.version != 0)
|
||||
{
|
||||
DEBUG_ERROR("unsupported protocol version %ld", ivshmem.server.version);
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ivshmem_read(&ivshmem.server.clientID, sizeof(ivshmem.server.clientID)))
|
||||
{
|
||||
DEBUG_ERROR("read client id failed");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_PROTO("Protocol : %ld", ivshmem.server.version );
|
||||
DEBUG_PROTO("Client ID: %ld", ivshmem.server.clientID);
|
||||
|
||||
if (!ivshmem_read_msg(NULL, &ivshmem.server.sharedFD))
|
||||
{
|
||||
DEBUG_ERROR("failed to read shared memory file descriptor");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat stat;
|
||||
if (fstat(ivshmem.server.sharedFD, &stat) != 0)
|
||||
{
|
||||
DEBUG_ERROR("failed to stat shared FD");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
ivshmem.mapSize = stat.st_size;
|
||||
|
||||
DEBUG_INFO("RAM Size : %ld", ivshmem.mapSize);
|
||||
ivshmem.map = mmap(
|
||||
NULL,
|
||||
stat.st_size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
ivshmem.server.sharedFD,
|
||||
0);
|
||||
|
||||
if (!ivshmem.map)
|
||||
{
|
||||
DEBUG_ERROR("failed to map memory");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void ivshmem_cleanup()
|
||||
{
|
||||
struct IVSHMEMClient * client, * next;
|
||||
client = ivshmem.clients;
|
||||
while(client)
|
||||
{
|
||||
for(int i = 0; i < client->irqCount; ++i)
|
||||
close(client->irqs[i]);
|
||||
|
||||
next = client->next;
|
||||
free(client);
|
||||
client = next;
|
||||
}
|
||||
ivshmem.clients = NULL;
|
||||
|
||||
for(int i = 0; i < ivshmem.server.irqCount; ++i)
|
||||
close(ivshmem.server.irqs[i]);
|
||||
ivshmem.server.irqCount = 0;
|
||||
|
||||
if (ivshmem.map)
|
||||
munmap(ivshmem.map, ivshmem.mapSize);
|
||||
ivshmem.map = NULL;
|
||||
ivshmem.mapSize = 0;
|
||||
|
||||
if (ivshmem.socket >= 0)
|
||||
{
|
||||
ivshmem.shutdown = true;
|
||||
shutdown(ivshmem.socket, SHUT_RDWR);
|
||||
close(ivshmem.socket);
|
||||
ivshmem.socket = -1;
|
||||
}
|
||||
|
||||
ivshmem.connected = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void ivshmem_disconnect()
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_WARN("socket not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
ivshmem_cleanup();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_read(void * buffer, const ssize_t size)
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t len = read(ivshmem.socket, buffer, size);
|
||||
if (len != size)
|
||||
{
|
||||
DEBUG_ERROR("incomplete read");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_read_msg(int64_t * index, int * fd)
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct msghdr msg;
|
||||
struct iovec iov[1];
|
||||
union {
|
||||
struct cmsghdr cmsg;
|
||||
char control[CMSG_SPACE(sizeof(int))];
|
||||
} msg_control;
|
||||
|
||||
int64_t tmp;
|
||||
if (!index)
|
||||
index = &tmp;
|
||||
|
||||
iov[0].iov_base = index;
|
||||
iov[0].iov_len = sizeof(*index);
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = &msg_control;
|
||||
msg.msg_controllen = sizeof(msg_control);
|
||||
|
||||
int ret = recvmsg(ivshmem.socket, &msg, 0);
|
||||
if (ret < sizeof(*index))
|
||||
{
|
||||
if (!ivshmem.shutdown)
|
||||
DEBUG_ERROR("failed to read message\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
if (!ivshmem.shutdown)
|
||||
DEBUG_ERROR("lost connetion to server\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fd)
|
||||
return true;
|
||||
|
||||
*fd = -1;
|
||||
struct cmsghdr *cmsg;
|
||||
for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
|
||||
{
|
||||
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
|
||||
cmsg->cmsg_level != SOL_SOCKET ||
|
||||
cmsg->cmsg_type != SCM_RIGHTS)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(fd, CMSG_DATA(cmsg), sizeof(int));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
uint16_t ivshmem_get_id()
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ivshmem.server.clientID;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void * ivshmem_get_map()
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ivshmem.map)
|
||||
{
|
||||
DEBUG_ERROR("not mapped");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ivshmem.map;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
size_t ivshmem_get_map_size()
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ivshmem.map)
|
||||
{
|
||||
DEBUG_ERROR("not mapped");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ivshmem.mapSize;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
struct IVSHMEMClient * ivshmem_get_client(uint16_t clientID)
|
||||
{
|
||||
struct IVSHMEMClient * client = NULL;
|
||||
|
||||
if (ivshmem.clients == NULL)
|
||||
{
|
||||
client = (struct IVSHMEMClient *)malloc(sizeof(struct IVSHMEMClient));
|
||||
client->clientID = clientID;
|
||||
client->last = NULL;
|
||||
client->next = NULL;
|
||||
client->irqCount = 0;
|
||||
ivshmem.clients = client;
|
||||
return client;
|
||||
}
|
||||
|
||||
client = ivshmem.clients;
|
||||
while(client)
|
||||
{
|
||||
if (client->clientID == clientID)
|
||||
return client;
|
||||
client = client->next;
|
||||
}
|
||||
|
||||
client = (struct IVSHMEMClient *)malloc(sizeof(struct IVSHMEMClient));
|
||||
client->clientID = clientID;
|
||||
client->last = NULL;
|
||||
client->next = ivshmem.clients;
|
||||
client->irqCount = 0;
|
||||
client->next->last = client;
|
||||
ivshmem.clients = client;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void ivshmem_remove_client(struct IVSHMEMClient * client)
|
||||
{
|
||||
if (client->last)
|
||||
client->last->next = client->next;
|
||||
|
||||
if (client->next)
|
||||
client->next->last = client->last;
|
||||
|
||||
if (ivshmem.clients == client)
|
||||
ivshmem.clients = client->next;
|
||||
|
||||
free(client);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_process()
|
||||
{
|
||||
int64_t index;
|
||||
int fd;
|
||||
|
||||
if (!ivshmem_read_msg(&index, &fd))
|
||||
{
|
||||
if (!ivshmem.shutdown)
|
||||
DEBUG_ERROR("failed to read message");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
DEBUG_ERROR("invalid index -1");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index > 0xFFFF)
|
||||
{
|
||||
DEBUG_ERROR("invalid index > 0xFFFF");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == ivshmem.server.clientID)
|
||||
{
|
||||
if (fd == -1)
|
||||
{
|
||||
DEBUG_ERROR("server sent disconnect");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ivshmem.server.irqCount == MAX_IRQS)
|
||||
{
|
||||
DEBUG_WARN("maximum IRQs reached, closing extra");
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
ivshmem.server.irqs[ivshmem.server.irqCount++] = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct IVSHMEMClient * client = ivshmem_get_client(index);
|
||||
if (!client)
|
||||
{
|
||||
DEBUG_ERROR("failed to get/create client record");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
DEBUG_PROTO("remove client %u", client->clientID);
|
||||
ivshmem_remove_client(client);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (client->irqCount == MAX_IRQS)
|
||||
{
|
||||
DEBUG_WARN("maximum client IRQs reached, closing extra");
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
client->irqs[client->irqCount++] = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
enum IVSHMEMWaitResult ivshmem_wait_irq(uint16_t vector, unsigned int timeout)
|
||||
{
|
||||
if (vector > ivshmem.server.irqCount - 1)
|
||||
return false;
|
||||
|
||||
int fd = ivshmem.server.irqs[vector];
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout / 1000000L;
|
||||
tv.tv_usec = timeout % 1000000L;
|
||||
|
||||
while(true)
|
||||
{
|
||||
int ret = select(fd+1, &fds, NULL, NULL, &tv);
|
||||
if (ret < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
DEBUG_ERROR("select error");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
return IVSHMEM_WAIT_RESULT_TIMEOUT;
|
||||
|
||||
if (FD_ISSET(fd, &fds))
|
||||
{
|
||||
uint64_t kick;
|
||||
int unused = read(fd, &kick, sizeof(kick));
|
||||
(void)unused;
|
||||
return IVSHMEM_WAIT_RESULT_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return IVSHMEM_WAIT_RESULT_ERROR;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_kick_irq(uint16_t clientID, uint16_t vector)
|
||||
{
|
||||
struct IVSHMEMClient * client = ivshmem_get_client(clientID);
|
||||
if (!client)
|
||||
{
|
||||
DEBUG_ERROR("invalid client");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vector > client->irqCount - 1)
|
||||
{
|
||||
DEBUG_ERROR("invalid vector for client");
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd = client->irqs[vector];
|
||||
uint64_t kick = ivshmem.server.clientID;
|
||||
if (write(fd, &kick, sizeof(kick)) == sizeof(kick))
|
||||
return true;
|
||||
|
||||
DEBUG_ERROR("failed to send kick");
|
||||
return false;
|
||||
}
|
||||
1242
client/main.c
1242
client/main.c
File diff suppressed because it is too large
Load Diff
1009
client/parsers/nal.c
Normal file
1009
client/parsers/nal.c
Normal file
File diff suppressed because it is too large
Load Diff
305
client/parsers/nal.h
Normal file
305
client/parsers/nal.h
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define NAL_TYPE_CODED_SLICE_NON_IDR 1
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_A 2
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_B 3
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_C 4
|
||||
#define NAL_TYPE_CODED_SLICE_IDR 5
|
||||
#define NAL_TYPE_SPS 7
|
||||
#define NAL_TYPE_PPS 8
|
||||
#define NAL_TYPE_AUD 9
|
||||
#define NAL_TYPE_END_OF_SEQUENCE 10
|
||||
#define NAL_TYPE_END_OF_STREAM 11
|
||||
#define NAL_TYPE_CODED_SLICE_AUX 19
|
||||
|
||||
#define IDC_PROFILE_BASELINE 66
|
||||
#define IDC_PROFILE_MAIN 77
|
||||
#define IDC_PROFILE_EXTENDED 88
|
||||
#define IDC_PROFILE_HP 100
|
||||
#define IDC_PROFILE_Hi10P 110
|
||||
#define IDC_PROFILE_Hi422 122
|
||||
#define IDC_PROFILE_Hi444 244
|
||||
#define IDC_PROFILE_CAVLC444 44
|
||||
|
||||
#define IDC_CHROMA_FORMAT_YUV400 0
|
||||
#define IDC_CHROMA_FORMAT_YUV420 1
|
||||
#define IDC_CHROMA_FORMAT_YVU422 2
|
||||
#define IDC_CHROMA_FORMAT_YUV444 3
|
||||
|
||||
#define IDC_VUI_ASPECT_RATIO_EXTENDED_SAR 0xFF
|
||||
|
||||
#define NAL_PICTURE_TYPE_I 0
|
||||
#define NAL_PICTURE_TYPE_P 1
|
||||
#define NAL_PICTURE_TYPE_B 2
|
||||
|
||||
#define NAL_SLICE_TYPE_P 0
|
||||
#define NAL_SLICE_TYPE_B 1
|
||||
#define NAL_SLICE_TYPE_I 2
|
||||
#define NAL_SLICE_TYPE_SP 3
|
||||
#define NAL_SLICE_TYPE_SI 4
|
||||
|
||||
typedef struct NAL_SPS
|
||||
{
|
||||
uint8_t profile_idc;
|
||||
uint8_t constraint_set_flags[3];
|
||||
uint8_t level_idc;
|
||||
uint32_t seq_parameter_set_id;
|
||||
uint32_t chroma_format_idc;
|
||||
uint8_t seperate_colour_plane_flag;
|
||||
uint32_t bit_depth_luma_minus8;
|
||||
uint32_t bit_depth_chroma_minus8;
|
||||
uint8_t lossless_qpprime_y_zero_flag;
|
||||
uint8_t seq_scaling_matrix_present_flag;
|
||||
uint8_t seq_scaling_list_present_flag[12];
|
||||
uint32_t log2_max_frame_num_minus4;
|
||||
uint32_t pic_order_cnt_type;
|
||||
uint32_t log2_max_pic_order_cnt_lsb_minus4;
|
||||
uint8_t delta_pic_order_always_zero_flag;
|
||||
int32_t offset_for_non_ref_pic;
|
||||
int32_t offset_for_top_to_bottom_field;
|
||||
uint32_t num_ref_frames_in_pic_order_cnt_cycle;
|
||||
int32_t * offset_for_ref_frame;
|
||||
uint32_t num_ref_frames;
|
||||
uint8_t gaps_in_frame_num_value_allowed_flag;
|
||||
uint32_t pic_width_in_mbs_minus1;
|
||||
uint32_t pic_height_in_map_units_minus1;
|
||||
uint8_t frame_mbs_only_flag;
|
||||
uint8_t mb_adaptive_frame_field_flag;
|
||||
uint8_t direct_8x8_inference_flag;
|
||||
uint8_t frame_cropping_flag;
|
||||
uint32_t frame_crop_left_offset;
|
||||
uint32_t frame_crop_right_offset;
|
||||
uint32_t frame_crop_top_offset;
|
||||
uint32_t frame_crop_bottom_offset;
|
||||
uint8_t vui_parameters_present_flag;
|
||||
}
|
||||
NAL_SPS;
|
||||
|
||||
typedef struct NAL_CPB
|
||||
{
|
||||
uint32_t bit_rate_value_minus1;
|
||||
uint32_t cpb_size_value_minus1;
|
||||
uint8_t cbr_flag;
|
||||
}
|
||||
NAL_CPB;
|
||||
|
||||
typedef struct NAL_HRD
|
||||
{
|
||||
uint32_t cpb_cnt_minus1;
|
||||
uint8_t bit_rate_scale;
|
||||
uint8_t cpb_size_scale;
|
||||
uint8_t cpb_size_count;
|
||||
NAL_CPB * cpb;
|
||||
uint8_t initial_cpb_removal_delay_length_minus1;
|
||||
uint8_t cpb_removal_delay_length_minus1;
|
||||
uint8_t dpb_output_delay_length_minus1;
|
||||
uint8_t time_offset_length;
|
||||
}
|
||||
NAL_HRD;
|
||||
|
||||
typedef struct NAL_VUI
|
||||
{
|
||||
uint8_t aspect_ratio_info_present_flag;
|
||||
uint8_t aspect_ratio_idc;
|
||||
uint16_t sar_width;
|
||||
uint16_t sar_height;
|
||||
uint8_t overscan_info_present_flag;
|
||||
uint8_t overscan_appropriate_flag;
|
||||
uint8_t video_signal_type_present_flag;
|
||||
uint8_t video_format;
|
||||
uint8_t video_full_range_flag;
|
||||
uint8_t colour_description_present_flag;
|
||||
uint8_t colour_primaries;
|
||||
uint8_t transfer_characteristics;
|
||||
uint8_t matrix_coefficients;
|
||||
uint8_t chroma_loc_info_present_flag;
|
||||
uint32_t chroma_sample_loc_type_top_field;
|
||||
uint32_t chroma_sample_loc_type_bottom_field;
|
||||
uint8_t timing_info_present_flag;
|
||||
uint32_t num_units_in_tick;
|
||||
uint32_t time_scale;
|
||||
uint8_t fixed_frame_rate_flag;
|
||||
uint8_t nal_hrd_parameters_present_flag;
|
||||
NAL_HRD nal_hrd_parameters;
|
||||
uint8_t vcl_hrd_parameters_present_flag;
|
||||
NAL_HRD vcl_hrd_parameters;
|
||||
uint8_t low_delay_hrd_flag;
|
||||
uint8_t pic_struct_present_flag;
|
||||
uint8_t bitstream_restriction_flag;
|
||||
uint8_t motion_vectors_over_pic_boundaries_flag;
|
||||
uint32_t max_bytes_per_pic_denom;
|
||||
uint32_t max_bits_per_mb_denom;
|
||||
uint32_t log2_max_mv_length_horizontal;
|
||||
uint32_t log2_max_mv_length_vertical;
|
||||
uint32_t num_reorder_frames;
|
||||
uint32_t max_dec_frame_buffering;
|
||||
}
|
||||
NAL_VUI;
|
||||
|
||||
typedef struct NAL_SLICE_GROUP_T0
|
||||
{
|
||||
uint32_t run_length_minus1;
|
||||
}
|
||||
NAL_SLICE_GROUP_T0;
|
||||
|
||||
typedef struct NAL_SLICE_GROUP_T2
|
||||
{
|
||||
uint32_t top_left;
|
||||
uint32_t bottom_right;
|
||||
}
|
||||
NAL_SLICE_GROUP_T2;
|
||||
|
||||
typedef union NAL_SLICE_GROUP
|
||||
{
|
||||
NAL_SLICE_GROUP_T0 t0;
|
||||
NAL_SLICE_GROUP_T2 t2;
|
||||
}
|
||||
NAL_SLICE_GROUP;
|
||||
|
||||
typedef struct NAL_PPS
|
||||
{
|
||||
uint32_t pic_parameter_set_id;
|
||||
uint32_t seq_parameter_set_id;
|
||||
uint8_t entropy_coding_mode_flag;
|
||||
uint8_t pic_order_present_flag;
|
||||
uint32_t num_slice_groups_minus1;
|
||||
NAL_SLICE_GROUP * slice_groups;
|
||||
uint32_t slice_group_map_type;
|
||||
uint8_t slice_group_change_direction_flag;
|
||||
uint32_t slice_group_change_rate_minus1;
|
||||
uint32_t pic_size_in_map_units_minus1;
|
||||
uint32_t * slice_group_id;
|
||||
uint32_t num_ref_idx_l0_active_minus1;
|
||||
uint32_t num_ref_idx_l1_active_minus1;
|
||||
uint8_t weighted_pred_flag;
|
||||
uint8_t weighted_bipred_idc;
|
||||
int32_t pic_init_qp_minus26;
|
||||
int32_t pic_init_qs_minus26;
|
||||
int32_t chroma_qp_index_offset;
|
||||
uint8_t deblocking_filter_control_present_flag;
|
||||
uint8_t constrained_intra_pred_flag;
|
||||
uint8_t redundant_pic_cnt_present_flag;
|
||||
|
||||
uint8_t transform_8x8_mode_flag;
|
||||
uint8_t pic_scaling_matrix_present_flag;
|
||||
uint8_t pic_scaling_list_present_flag[6];
|
||||
int32_t scaling_list_4x4[6];
|
||||
int32_t scaling_list_8x8[2];
|
||||
int32_t second_chroma_qp_index_offset;
|
||||
}
|
||||
NAL_PPS;
|
||||
|
||||
typedef struct NAL_RPL_REORDER_L
|
||||
{
|
||||
bool valid;
|
||||
uint32_t reordering_of_pic_nums_idc;
|
||||
uint32_t abs_diff_pic_num_minus1;
|
||||
uint32_t long_term_pic_num;
|
||||
}
|
||||
NAL_RPL_REORDER_L;
|
||||
|
||||
typedef struct NAL_RPL_REORDER
|
||||
{
|
||||
uint8_t ref_pic_list_reordering_flag_l0;
|
||||
NAL_RPL_REORDER_L l0[3];
|
||||
uint8_t ref_pic_list_reordering_flag_l1;
|
||||
NAL_RPL_REORDER_L l1[3];
|
||||
}
|
||||
NAL_RPL_REORDER;
|
||||
|
||||
typedef struct NAL_PW_TABLE_L
|
||||
{
|
||||
int32_t luma_weight;
|
||||
int32_t luma_offset;
|
||||
int32_t chroma_weight[2];
|
||||
int32_t chroma_offset[2];
|
||||
}
|
||||
NAL_PW_TABLE_L;
|
||||
|
||||
typedef struct NAL_PW_TABLE
|
||||
{
|
||||
uint32_t luma_log2_weight_denom;
|
||||
uint32_t chroma_log2_weight_denom;
|
||||
uint8_t luma_weight_flag[2];
|
||||
uint8_t chroma_weight_flag[2];
|
||||
NAL_PW_TABLE_L * l0;
|
||||
NAL_PW_TABLE_L * l1;
|
||||
}
|
||||
NAL_PW_TABLE;
|
||||
|
||||
typedef struct NAL_RP_MARKING
|
||||
{
|
||||
uint8_t no_output_of_prior_pics_flag;
|
||||
uint8_t long_term_reference_flag;
|
||||
uint8_t adaptive_ref_pic_marking_mode_flag;
|
||||
uint32_t memory_management_control_operation;
|
||||
uint32_t difference_of_pic_nums_minus1;
|
||||
uint32_t long_term_pic_num;
|
||||
uint32_t long_term_frame_idx;
|
||||
uint32_t max_long_term_frame_idx_plus1;
|
||||
}
|
||||
NAL_RP_MARKING;
|
||||
|
||||
typedef struct NAL_SLICE
|
||||
{
|
||||
uint8_t nal_ref_idc;
|
||||
uint32_t first_mb_in_slice;
|
||||
uint32_t slice_type;
|
||||
uint32_t pic_parameter_set_id;
|
||||
uint32_t frame_num;
|
||||
uint8_t field_pic_flag;
|
||||
uint8_t bottom_field_flag;
|
||||
uint32_t idr_pic_id;
|
||||
uint32_t pic_order_cnt_lsb;
|
||||
int32_t delta_pic_order_cnt_bottom;
|
||||
int32_t delta_pic_order_cnt[2];
|
||||
uint32_t redundant_pic_cnt;
|
||||
uint8_t direct_spatial_mv_pred_flag;
|
||||
uint8_t num_ref_idx_active_override_flag;
|
||||
uint32_t num_ref_idx_l0_active_minus1;
|
||||
uint32_t num_ref_idx_l1_active_minus1;
|
||||
NAL_RPL_REORDER ref_pic_list_reordering;
|
||||
NAL_PW_TABLE pred_weight_table;
|
||||
NAL_RP_MARKING dec_ref_pic_marking;
|
||||
uint32_t cabac_init_idc;
|
||||
int32_t slice_qp_delta;
|
||||
uint8_t sp_for_switch_flag;
|
||||
int32_t slice_qs_delta;
|
||||
uint32_t disable_deblocking_filter_idc;
|
||||
int32_t slice_alpha_c0_offset_div2;
|
||||
int32_t slice_beta_offset_div2;
|
||||
uint32_t slice_group_change_cycle;
|
||||
}
|
||||
NAL_SLICE;
|
||||
|
||||
typedef struct NAL * NAL;
|
||||
|
||||
bool nal_initialize (NAL * ptr);
|
||||
void nal_deinitialize(NAL this );
|
||||
bool nal_parse (NAL this, const uint8_t * src, size_t size, size_t * seek);
|
||||
|
||||
bool nal_get_primary_picture_type(NAL this, uint8_t * pic_type);
|
||||
bool nal_get_sps (NAL this, const NAL_SPS ** sps );
|
||||
bool nal_get_pps (NAL this, const NAL_PPS ** pps );
|
||||
bool nal_get_slice(NAL this, const NAL_SLICE ** slice);
|
||||
46
client/renderers/CMakeLists.txt
Normal file
46
client/renderers/CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(renderers LANGUAGES C)
|
||||
|
||||
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(WRITE ${RENDERER_C} "#include \"interface/renderer.h\"\n\n")
|
||||
file(APPEND ${RENDERER_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(RENDERERS "_")
|
||||
set(RENDERERS_LINK "_")
|
||||
function(add_renderer name)
|
||||
set(RENDERERS "${RENDERERS};${name}" PARENT_SCOPE)
|
||||
set(RENDERERS_LINK "${RENDERERS_LINK};renderer_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove renderers here!
|
||||
if(ENABLE_EGL)
|
||||
add_renderer(EGL)
|
||||
endif()
|
||||
if (ENABLE_OPENGL)
|
||||
add_renderer(OpenGL)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT RENDERERS 0)
|
||||
list(REMOVE_AT RENDERERS_LINK 0)
|
||||
|
||||
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")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${RENDERER_C} "\nconst LG_Renderer * LG_Renderers[] =\n{\n")
|
||||
foreach(renderer ${RENDERERS})
|
||||
file(APPEND ${RENDERER_C} " &LGR_${renderer},\n")
|
||||
endforeach()
|
||||
file(APPEND ${RENDERER_C} " NULL\n};")
|
||||
|
||||
add_library(renderers STATIC ${RENDERER_C})
|
||||
target_link_libraries(renderers ${RENDERERS_LINK})
|
||||
63
client/renderers/EGL/CMakeLists.txt
Normal file
63
client/renderers/EGL/CMakeLists.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(renderer_EGL LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(RENDERER_EGL_PKGCONFIG REQUIRED
|
||||
egl
|
||||
gl
|
||||
)
|
||||
|
||||
pkg_check_modules(RENDERER_EGL_OPT_PKGCONFIG
|
||||
wayland-egl
|
||||
)
|
||||
|
||||
include(MakeObject)
|
||||
make_object(
|
||||
EGL_SHADER
|
||||
shader/desktop.vert
|
||||
shader/desktop_rgb.frag
|
||||
shader/desktop_yuv.frag
|
||||
shader/cursor.vert
|
||||
shader/cursor_rgb.frag
|
||||
shader/cursor_mono.frag
|
||||
shader/fps.vert
|
||||
shader/fps.frag
|
||||
shader/fps_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
|
||||
)
|
||||
|
||||
add_library(renderer_EGL STATIC
|
||||
egl.c
|
||||
debug.c
|
||||
shader.c
|
||||
texture.c
|
||||
model.c
|
||||
desktop.c
|
||||
cursor.c
|
||||
fps.c
|
||||
draw.c
|
||||
splash.c
|
||||
alert.c
|
||||
${EGL_SHADER_OBJS}
|
||||
)
|
||||
|
||||
target_link_libraries(renderer_EGL
|
||||
${RENDERER_EGL_PKGCONFIG_LIBRARIES}
|
||||
${RENDERER_EGL_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
fonts
|
||||
)
|
||||
|
||||
target_include_directories(renderer_EGL
|
||||
PRIVATE
|
||||
src
|
||||
${EGL_SHADER_INCS}
|
||||
${RENDERER_EGL_PKGCONFIG_INCLUDE_DIRS}
|
||||
${RENDERER_EGL_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
218
client/renderers/EGL/alert.c
Normal file
218
client/renderers/EGL/alert.c
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
cahe 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))
|
||||
{
|
||||
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_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
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
33
client/renderers/EGL/alert.h
Normal file
33
client/renderers/EGL/alert.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "interface/font.h"
|
||||
|
||||
typedef struct EGL_Alert EGL_Alert;
|
||||
|
||||
bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_alert_free(EGL_Alert ** alert);
|
||||
|
||||
void egl_alert_set_color(EGL_Alert * alert, const uint32_t color);
|
||||
void egl_alert_set_text (EGL_Alert * alert, const char * str);
|
||||
void egl_alert_render (EGL_Alert * alert, const float scaleX, const float scaleY);
|
||||
289
client/renderers/EGL/cursor.c
Normal file
289
client/renderers/EGL/cursor.c
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
cahe 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 "cursor.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 "cursor.vert.h"
|
||||
#include "cursor_rgb.frag.h"
|
||||
#include "cursor_mono.frag.h"
|
||||
|
||||
struct EGL_Cursor
|
||||
{
|
||||
LG_Lock lock;
|
||||
LG_RendererCursor type;
|
||||
int width;
|
||||
int height;
|
||||
int stride;
|
||||
uint8_t * data;
|
||||
size_t dataSize;
|
||||
bool update;
|
||||
|
||||
// cursor state
|
||||
bool visible;
|
||||
float x, y, w, h;
|
||||
|
||||
// textures
|
||||
struct EGL_Texture * texture;
|
||||
struct EGL_Texture * textureMono;
|
||||
|
||||
// shaders
|
||||
struct EGL_Shader * shader;
|
||||
struct EGL_Shader * shaderMono;
|
||||
|
||||
// uniforms
|
||||
GLuint uMousePos;
|
||||
GLuint uMousePosMono;
|
||||
|
||||
// model
|
||||
struct EGL_Model * model;
|
||||
};
|
||||
|
||||
bool egl_cursor_init(EGL_Cursor ** cursor)
|
||||
{
|
||||
*cursor = (EGL_Cursor *)malloc(sizeof(EGL_Cursor));
|
||||
if (!*cursor)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*cursor, 0, sizeof(EGL_Cursor));
|
||||
LG_LOCK_INIT((*cursor)->lock);
|
||||
|
||||
if (!egl_texture_init(&(*cursor)->texture))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_texture_init(&(*cursor)->textureMono))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor mono texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*cursor)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*cursor)->shaderMono))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor mono shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile(
|
||||
(*cursor)->shader,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile(
|
||||
(*cursor)->shaderMono,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the cursor mono shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*cursor)->uMousePos = egl_shader_get_uniform_location((*cursor)->shader , "mouse");
|
||||
(*cursor)->uMousePosMono = egl_shader_get_uniform_location((*cursor)->shaderMono, "mouse");
|
||||
|
||||
if (!egl_model_init(&(*cursor)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*cursor)->model);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_cursor_free(EGL_Cursor ** cursor)
|
||||
{
|
||||
if (!*cursor)
|
||||
return;
|
||||
|
||||
LG_LOCK_FREE((*cursor)->lock);
|
||||
if ((*cursor)->data)
|
||||
free((*cursor)->data);
|
||||
|
||||
egl_texture_free(&(*cursor)->texture );
|
||||
egl_texture_free(&(*cursor)->textureMono);
|
||||
egl_shader_free (&(*cursor)->shader );
|
||||
egl_shader_free (&(*cursor)->shaderMono );
|
||||
egl_model_free (&(*cursor)->model );
|
||||
|
||||
free(*cursor);
|
||||
*cursor = NULL;
|
||||
}
|
||||
|
||||
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data)
|
||||
{
|
||||
LG_LOCK(cursor->lock);
|
||||
|
||||
cursor->type = type;
|
||||
cursor->width = width;
|
||||
cursor->height = (type == LG_CURSOR_MONOCHROME ? height / 2 : height);
|
||||
cursor->stride = stride;
|
||||
|
||||
const size_t size = height * stride;
|
||||
if (size > cursor->dataSize)
|
||||
{
|
||||
if (cursor->data)
|
||||
free(cursor->data);
|
||||
|
||||
cursor->data = (uint8_t *)malloc(size);
|
||||
if (!cursor->data)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc buffer for cursor shape");
|
||||
return false;
|
||||
}
|
||||
|
||||
cursor->dataSize = size;
|
||||
}
|
||||
|
||||
memcpy(cursor->data, data, size);
|
||||
cursor->update = true;
|
||||
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_cursor_set_size(EGL_Cursor * cursor, const float w, const float h)
|
||||
{
|
||||
cursor->w = w;
|
||||
cursor->h = h;
|
||||
}
|
||||
|
||||
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y)
|
||||
{
|
||||
cursor->visible = visible;
|
||||
cursor->x = x;
|
||||
cursor->y = y;
|
||||
}
|
||||
|
||||
void egl_cursor_render(EGL_Cursor * cursor)
|
||||
{
|
||||
if (!cursor->visible)
|
||||
return;
|
||||
|
||||
if (cursor->update)
|
||||
{
|
||||
LG_LOCK(cursor->lock);
|
||||
cursor->update = false;
|
||||
|
||||
uint8_t * data = cursor->data;
|
||||
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
// fall through
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_texture_setup(cursor->texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false);
|
||||
egl_texture_update(cursor->texture, data);
|
||||
egl_model_set_texture(cursor->model, cursor->texture);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
uint32_t and[cursor->width * cursor->height];
|
||||
uint32_t xor[cursor->width * cursor->height];
|
||||
|
||||
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);
|
||||
const uint8_t * srcXor = srcAnd + cursor->stride * cursor->height;
|
||||
const uint8_t mask = 0x80 >> (x % 8);
|
||||
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;
|
||||
}
|
||||
|
||||
egl_texture_setup (cursor->texture , EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false);
|
||||
egl_texture_setup (cursor->textureMono, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false);
|
||||
egl_texture_update(cursor->texture , (uint8_t *)and);
|
||||
egl_texture_update(cursor->textureMono, (uint8_t *)xor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
egl_shader_use(cursor->shader);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
|
||||
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
|
||||
egl_model_set_texture(cursor->model, cursor->texture);
|
||||
egl_model_render(cursor->model);
|
||||
|
||||
egl_shader_use(cursor->shaderMono);
|
||||
glUniform4f(cursor->uMousePosMono, cursor->x, cursor->y, cursor->w, cursor->h / 2);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_model_set_texture(cursor->model, cursor->textureMono);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_shader_use(cursor->shader);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
egl_shader_use(cursor->shaderMono);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
}
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
34
client/renderers/EGL/cursor.h
Normal file
34
client/renderers/EGL/cursor.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "interface/renderer.h"
|
||||
|
||||
typedef struct EGL_Cursor EGL_Cursor;
|
||||
|
||||
bool egl_cursor_init(EGL_Cursor ** cursor);
|
||||
void egl_cursor_free(EGL_Cursor ** cursor);
|
||||
|
||||
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data);
|
||||
void egl_cursor_set_size (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_cursor_render (EGL_Cursor * cursor);
|
||||
58
client/renderers/EGL/debug.c
Normal file
58
client/renderers/EGL/debug.c
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <GL/gl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void egl_debug_printf(char * format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
|
||||
GLenum error = glGetError();
|
||||
switch(error)
|
||||
{
|
||||
case GL_NO_ERROR:
|
||||
fprintf(stderr, " (GL_NO_ERROR)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_ENUM:
|
||||
fprintf(stderr, " (GL_INVALID_ENUM)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_VALUE:
|
||||
fprintf(stderr, " (GL_INVALID_VALUE)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_OPERATION:
|
||||
fprintf(stderr, " (GL_INVALID_OPERATION)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
fprintf(stderr, " (GL_INVALID_FRAMEBUFFER_OPERATION)\n");
|
||||
break;
|
||||
|
||||
case GL_OUT_OF_MEMORY:
|
||||
fprintf(stderr, " (GL_OUT_OF_MEMORY)\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
27
client/renderers/EGL/debug.h
Normal file
27
client/renderers/EGL/debug.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <common/debug.h>
|
||||
|
||||
#define EGL_DEBUG_PRINT(type, fmt, ...) do {egl_debug_printf(type " %20s:%-4u | %-30s | " fmt, STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
|
||||
#define EGL_ERROR(fmt, ...) EGL_DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
|
||||
|
||||
void egl_debug_printf(char * format, ...);
|
||||
261
client/renderers/EGL/desktop.c
Normal file
261
client/renderers/EGL/desktop.c
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
cahe 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.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "interface/app.h"
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "desktop.vert.h"
|
||||
#include "desktop_rgb.frag.h"
|
||||
#include "desktop_yuv.frag.h"
|
||||
|
||||
struct DesktopShader
|
||||
{
|
||||
EGL_Shader * shader;
|
||||
GLint uDesktopPos;
|
||||
GLint uDesktopSize;
|
||||
GLint uNearest;
|
||||
GLint uNV, uNVGain;
|
||||
};
|
||||
|
||||
struct EGL_Desktop
|
||||
{
|
||||
void * egl;
|
||||
|
||||
EGL_Texture * texture;
|
||||
struct DesktopShader * shader; // the active shader
|
||||
EGL_Model * model;
|
||||
|
||||
// internals
|
||||
int width, height;
|
||||
|
||||
// shader instances
|
||||
struct DesktopShader shader_generic;
|
||||
struct DesktopShader shader_yuv;
|
||||
|
||||
// night vision
|
||||
KeybindHandle kbNV;
|
||||
int nvMax;
|
||||
int nvGain;
|
||||
};
|
||||
|
||||
// forwards
|
||||
void egl_desktop_toggle_nv(SDL_Scancode key, void * opaque);
|
||||
|
||||
static bool egl_init_desktop_shader(
|
||||
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))
|
||||
return false;
|
||||
|
||||
if (!egl_shader_compile(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->uNearest = egl_shader_get_uniform_location(shader->shader, "nearest" );
|
||||
shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" );
|
||||
shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_desktop_init(void * egl, EGL_Desktop ** desktop)
|
||||
{
|
||||
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
|
||||
if (!*desktop)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*desktop, 0, sizeof(EGL_Desktop));
|
||||
|
||||
if (!egl_texture_init(&(*desktop)->texture))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_init_desktop_shader(
|
||||
&(*desktop)->shader_generic,
|
||||
b_shader_desktop_vert , b_shader_desktop_vert_size,
|
||||
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the generic desktop shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_init_desktop_shader(
|
||||
&(*desktop)->shader_yuv,
|
||||
b_shader_desktop_vert , b_shader_desktop_vert_size,
|
||||
b_shader_desktop_yuv_frag, b_shader_desktop_yuv_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the yuv desktop shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_model_init(&(*desktop)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*desktop)->model);
|
||||
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
|
||||
|
||||
(*desktop)->egl = egl;
|
||||
(*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop);
|
||||
|
||||
(*desktop)->nvMax = option_get_int("egl", "nvGainMax");
|
||||
(*desktop)->nvGain = option_get_int("egl", "nvGain" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void egl_desktop_toggle_nv(SDL_Scancode key, void * opaque)
|
||||
{
|
||||
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
|
||||
if (desktop->nvGain++ == desktop->nvMax)
|
||||
desktop->nvGain = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void egl_desktop_free(EGL_Desktop ** desktop)
|
||||
{
|
||||
if (!*desktop)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*desktop)->texture );
|
||||
egl_shader_free (&(*desktop)->shader_generic.shader);
|
||||
egl_shader_free (&(*desktop)->shader_yuv.shader );
|
||||
egl_model_free (&(*desktop)->model );
|
||||
|
||||
app_release_keybind(&(*desktop)->kbNV);
|
||||
|
||||
free(*desktop);
|
||||
*desktop = NULL;
|
||||
}
|
||||
|
||||
bool egl_desktop_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer * frame)
|
||||
{
|
||||
if (sourceChanged)
|
||||
{
|
||||
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_YUV420:
|
||||
pixFmt = EGL_PF_YUV420;
|
||||
desktop->shader = &desktop->shader_yuv;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported frame format");
|
||||
return false;
|
||||
}
|
||||
|
||||
desktop->width = format.width;
|
||||
desktop->height = format.height;
|
||||
|
||||
if (!egl_texture_setup(
|
||||
desktop->texture,
|
||||
pixFmt,
|
||||
format.width,
|
||||
format.height,
|
||||
format.pitch,
|
||||
true // streaming texture
|
||||
))
|
||||
{
|
||||
DEBUG_ERROR("Failed to setup the desktop texture");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!egl_texture_update_from_frame(desktop->texture, frame))
|
||||
return false;
|
||||
|
||||
enum EGL_TexStatus status;
|
||||
if ((status = egl_texture_process(desktop->texture)) != 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, const bool nearest)
|
||||
{
|
||||
if (!desktop->shader)
|
||||
return false;
|
||||
|
||||
const struct DesktopShader * shader = desktop->shader;
|
||||
egl_shader_use(shader->shader);
|
||||
glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY);
|
||||
glUniform1i(shader->uNearest , nearest ? 1 : 0);
|
||||
glUniform2f(shader->uDesktopSize, desktop->width, desktop->height);
|
||||
|
||||
if (desktop->nvGain)
|
||||
{
|
||||
glUniform1i(shader->uNV, 1);
|
||||
glUniform1f(shader->uNVGain, (float)desktop->nvGain);
|
||||
}
|
||||
else
|
||||
glUniform1i(shader->uNV, 0);
|
||||
|
||||
egl_model_render(desktop->model);
|
||||
return true;
|
||||
}
|
||||
32
client/renderers/EGL/desktop.h
Normal file
32
client/renderers/EGL/desktop.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "interface/renderer.h"
|
||||
|
||||
typedef struct EGL_Desktop EGL_Desktop;
|
||||
|
||||
bool egl_desktop_init(void * egl, EGL_Desktop ** desktop);
|
||||
void egl_desktop_free(EGL_Desktop ** desktop);
|
||||
|
||||
bool egl_desktop_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer * frame);
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);
|
||||
66
client/renderers/EGL/draw.c
Normal file
66
client/renderers/EGL/draw.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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);
|
||||
}
|
||||
25
client/renderers/EGL/draw.h
Normal file
25
client/renderers/EGL/draw.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "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);
|
||||
626
client/renderers/EGL/egl.c
Normal file
626
client/renderers/EGL/egl.c
Normal file
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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/renderer.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/sysinfo.h"
|
||||
#include "common/time.h"
|
||||
#include "common/locking.h"
|
||||
#include "utils.h"
|
||||
#include "dynamic/fonts.h"
|
||||
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
#include <wayland-egl.h>
|
||||
#endif
|
||||
|
||||
#include "model.h"
|
||||
#include "shader.h"
|
||||
#include "desktop.h"
|
||||
#include "cursor.h"
|
||||
#include "fps.h"
|
||||
#include "splash.h"
|
||||
#include "alert.h"
|
||||
|
||||
#define SPLASH_FADE_TIME 1000000
|
||||
#define ALERT_TIMEOUT 2000000
|
||||
|
||||
struct Options
|
||||
{
|
||||
bool vsync;
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererParams params;
|
||||
struct Options opt;
|
||||
|
||||
EGLNativeWindowType nativeWind;
|
||||
EGLDisplay display;
|
||||
EGLConfig configs;
|
||||
EGLSurface surface;
|
||||
EGLContext context, frameContext;
|
||||
|
||||
EGL_Desktop * desktop; // the desktop
|
||||
EGL_Cursor * cursor; // the mouse cursor
|
||||
EGL_FPS * fps; // the fps display
|
||||
EGL_Splash * splash; // the splash screen
|
||||
EGL_Alert * alert; // the alert display
|
||||
|
||||
LG_RendererFormat format;
|
||||
bool start;
|
||||
uint64_t waitFadeTime;
|
||||
bool waitDone;
|
||||
|
||||
bool showAlert;
|
||||
uint64_t alertTimeout;
|
||||
bool useCloseFlag;
|
||||
bool closeFlag;
|
||||
|
||||
int width, height;
|
||||
LG_RendererRect destRect;
|
||||
|
||||
float translateX , translateY;
|
||||
float scaleX , scaleY;
|
||||
float splashRatio;
|
||||
float screenScaleX, screenScaleY;
|
||||
bool useNearest;
|
||||
|
||||
bool cursorVisible;
|
||||
int cursorX , cursorY;
|
||||
float mouseWidth , mouseHeight;
|
||||
float mouseScaleX, mouseScaleY;
|
||||
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
};
|
||||
|
||||
|
||||
static struct Option egl_options[] =
|
||||
{
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "vsync",
|
||||
.description = "Enable vsync",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "doubleBuffer",
|
||||
.description = "Enable double buffering",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "multisample",
|
||||
.description = "Enable Multisampling",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "nvGainMax",
|
||||
.description = "The maximum night vision gain",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 1
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "nvGain",
|
||||
.description = "The initial night vision gain at startup",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 0
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
void update_mouse_shape(struct Inst * this);
|
||||
|
||||
const char * egl_get_name()
|
||||
{
|
||||
return "EGL";
|
||||
}
|
||||
|
||||
void egl_setup()
|
||||
{
|
||||
option_register(egl_options);
|
||||
}
|
||||
|
||||
bool egl_create(void ** opaque, const LG_RendererParams params)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
|
||||
// safe off parameteres and init our default option values
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
memcpy(&this->params, ¶ms, sizeof(LG_RendererParams));
|
||||
|
||||
this->opt.vsync = option_get_bool("egl", "vsync");
|
||||
|
||||
this->translateX = 0;
|
||||
this->translateY = 0;
|
||||
this->scaleX = 1.0f;
|
||||
this->scaleY = 1.0f;
|
||||
this->screenScaleX = 1.0f;
|
||||
this->screenScaleY = 1.0f;
|
||||
|
||||
this->font = LG_Fonts[0];
|
||||
if (!this->font->create(&this->fontObj, NULL, 16))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create a font instance");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
{
|
||||
const bool doubleBuffer = option_get_bool("egl", "doubleBuffer");
|
||||
DEBUG_INFO("Double buffering is %s", doubleBuffer ? "on" : "off");
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , doubleBuffer ? 1 : 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
if (option_get_bool("egl", "multisample"))
|
||||
{
|
||||
int maxSamples = sysinfo_gfx_max_multisample();
|
||||
if (maxSamples > 1)
|
||||
{
|
||||
if (maxSamples > 4)
|
||||
maxSamples = 4;
|
||||
|
||||
DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (this->font && this->fontObj)
|
||||
this->font->destroy(this->fontObj);
|
||||
|
||||
egl_desktop_free(&this->desktop);
|
||||
egl_cursor_free (&this->cursor);
|
||||
egl_fps_free (&this->fps );
|
||||
egl_splash_free (&this->splash);
|
||||
egl_alert_free (&this->alert );
|
||||
|
||||
LG_LOCK_FREE(this->lock);
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
void egl_on_restart(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
eglDestroyContext(this->display, this->frameContext);
|
||||
this->frameContext = NULL;
|
||||
this->start = false;
|
||||
}
|
||||
|
||||
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
|
||||
|
||||
glViewport(0, 0, width, height);
|
||||
|
||||
if (destRect.valid)
|
||||
{
|
||||
this->translateX = 1.0f - (((destRect.w / 2) + destRect.x) * 2) / (float)width;
|
||||
this->translateY = 1.0f - (((destRect.h / 2) + destRect.y) * 2) / (float)height;
|
||||
this->scaleX = (float)destRect.w / (float)width;
|
||||
this->scaleY = (float)destRect.h / (float)height;
|
||||
}
|
||||
|
||||
this->mouseScaleX = 2.0f / this->format.width ;
|
||||
this->mouseScaleY = 2.0f / this->format.height;
|
||||
egl_cursor_set_size(this->cursor,
|
||||
(this->mouseWidth * (1.0f / this->format.width )) * this->scaleX,
|
||||
(this->mouseHeight * (1.0f / this->format.height)) * this->scaleY
|
||||
);
|
||||
|
||||
this->splashRatio = (float)width / (float)height;
|
||||
this->screenScaleX = 1.0f / width;
|
||||
this->screenScaleY = 1.0f / height;
|
||||
|
||||
egl_cursor_set_state(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
|
||||
);
|
||||
}
|
||||
|
||||
bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!egl_cursor_set_shape(this->cursor, cursor, width, height, pitch, data))
|
||||
{
|
||||
DEBUG_ERROR("Failed to update the cursor shape");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->mouseWidth = width;
|
||||
this->mouseHeight = height;
|
||||
egl_cursor_set_size(this->cursor,
|
||||
(this->mouseWidth * (1.0f / this->format.width )) * this->scaleX,
|
||||
(this->mouseHeight * (1.0f / this->format.height)) * this->scaleY
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->cursorVisible = visible;
|
||||
this->cursorX = x;
|
||||
this->cursorY = y;
|
||||
|
||||
egl_cursor_set_state(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer * frame)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
const bool sourceChanged = (
|
||||
this->format.type != format.type ||
|
||||
this->format.width != format.width ||
|
||||
this->format.height != format.height ||
|
||||
this->format.pitch != format.pitch
|
||||
);
|
||||
|
||||
if (sourceChanged)
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
|
||||
this->useNearest = this->width < format.width || this->height < format.height;
|
||||
|
||||
/* this event runs in a second thread so we need to init it here */
|
||||
if (!this->frameContext)
|
||||
{
|
||||
static EGLint attrs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
if (!(this->frameContext = eglCreateContext(this->display, this->configs, this->context, attrs)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the frame context");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->frameContext))
|
||||
{
|
||||
DEBUG_ERROR("Failed to make the frame context current");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!egl_desktop_update(this->desktop, sourceChanged, format, frame))
|
||||
{
|
||||
DEBUG_INFO("Failed to to update the desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->start = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
static const uint32_t colors[] =
|
||||
{
|
||||
0x0000CCCC, // LG_ALERT_INFO
|
||||
0x00CC00CC, // LG_ALERT_SUCCESS
|
||||
0xCC7F00CC, // LG_ALERT_WARNING
|
||||
0xFF0000CC // LG_ALERT_ERROR
|
||||
};
|
||||
|
||||
if (alert > LG_ALERT_ERROR || alert < 0)
|
||||
{
|
||||
DEBUG_ERROR("Invalid alert value");
|
||||
return;
|
||||
}
|
||||
|
||||
egl_alert_set_color(this->alert, colors[alert]);
|
||||
egl_alert_set_text (this->alert, message );
|
||||
|
||||
if (closeFlag)
|
||||
{
|
||||
this->useCloseFlag = true;
|
||||
*closeFlag = &this->closeFlag;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->useCloseFlag = false;
|
||||
this->alertTimeout = microtime() + ALERT_TIMEOUT;
|
||||
}
|
||||
|
||||
this->showAlert = true;
|
||||
}
|
||||
|
||||
bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *client_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
DEBUG_INFO("Supported extensions: %s", client_exts);
|
||||
|
||||
bool useNative = false;
|
||||
if (strstr(client_exts, "EGL_KHR_platform_base") != NULL)
|
||||
useNative = true;
|
||||
|
||||
DEBUG_INFO("use native: %s", useNative ? "true" : "false");
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
{
|
||||
if (!useNative)
|
||||
this->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, wminfo.info.x11.display, NULL);
|
||||
else
|
||||
{
|
||||
EGLNativeDisplayType native = (EGLNativeDisplayType)wminfo.info.x11.display;
|
||||
this->display = eglGetDisplay(native);
|
||||
}
|
||||
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
{
|
||||
int width, height;
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
if (!useNative)
|
||||
this->display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wminfo.info.wl.display, NULL);
|
||||
else
|
||||
{
|
||||
EGLNativeDisplayType native = (EGLNativeDisplayType)wminfo.info.wl.display;
|
||||
this->display = eglGetDisplay(native);
|
||||
}
|
||||
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->display == EGL_NO_DISPLAY)
|
||||
{
|
||||
DEBUG_ERROR("eglGetDisplay failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eglInitialize(this->display, NULL, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Unable to initialize EGL");
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint attr[] =
|
||||
{
|
||||
EGL_BUFFER_SIZE , 32,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SAMPLE_BUFFERS , 1,
|
||||
EGL_SAMPLES , 4,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLint num_config;
|
||||
if (!eglChooseConfig(this->display, attr, &this->configs, 1, &num_config))
|
||||
{
|
||||
DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, NULL);
|
||||
if (this->surface == EGL_NO_SURFACE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint ctxattr[] =
|
||||
{
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
this->context = eglCreateContext(this->display, this->configs, EGL_NO_CONTEXT, ctxattr);
|
||||
if (this->context == EGL_NO_CONTEXT)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL context (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
|
||||
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
|
||||
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
|
||||
|
||||
if (!egl_desktop_init(this, &this->desktop))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_cursor_init(&this->cursor))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_fps_init(&this->fps, this->font, this->fontObj))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the FPS display");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_splash_init(&this->splash))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the splash screen");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_alert_init(&this->alert, this->font, this->fontObj))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert display");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_render(void * opaque, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (this->start && egl_desktop_render(this->desktop,
|
||||
this->translateX, this->translateY,
|
||||
this->scaleX , this->scaleY ,
|
||||
this->useNearest))
|
||||
{
|
||||
if (!this->waitFadeTime)
|
||||
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
||||
egl_cursor_render(this->cursor);
|
||||
}
|
||||
|
||||
if (!this->waitDone)
|
||||
{
|
||||
float a = 1.0f;
|
||||
if (!this->waitFadeTime)
|
||||
a = 1.0f;
|
||||
else
|
||||
{
|
||||
uint64_t t = microtime();
|
||||
if (t > this->waitFadeTime)
|
||||
this->waitDone = true;
|
||||
else
|
||||
{
|
||||
uint64_t delta = this->waitFadeTime - t;
|
||||
a = 1.0f / SPLASH_FADE_TIME * delta;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->waitDone)
|
||||
egl_splash_render(this->splash, a, this->splashRatio);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this->start)
|
||||
egl_splash_render(this->splash, 1.0f, this->splashRatio);
|
||||
}
|
||||
|
||||
if (this->showAlert)
|
||||
{
|
||||
bool close = false;
|
||||
if (this->useCloseFlag)
|
||||
close = this->closeFlag;
|
||||
else if (this->alertTimeout < microtime())
|
||||
close = true;
|
||||
|
||||
if (close)
|
||||
this->showAlert = false;
|
||||
else
|
||||
egl_alert_render(this->alert, this->screenScaleX, this->screenScaleY);
|
||||
}
|
||||
|
||||
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
|
||||
eglSwapBuffers(this->display, this->surface);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->params.showFPS)
|
||||
return;
|
||||
|
||||
egl_fps_update(this->fps, avgUPS, avgFPS);
|
||||
}
|
||||
|
||||
struct LG_Renderer LGR_EGL =
|
||||
{
|
||||
.get_name = egl_get_name,
|
||||
.setup = egl_setup,
|
||||
.create = egl_create,
|
||||
.initialize = egl_initialize,
|
||||
.deinitialize = egl_deinitialize,
|
||||
.on_restart = egl_on_restart,
|
||||
.on_resize = egl_on_resize,
|
||||
.on_mouse_shape = egl_on_mouse_shape,
|
||||
.on_mouse_event = egl_on_mouse_event,
|
||||
.on_frame_event = egl_on_frame_event,
|
||||
.on_alert = egl_on_alert,
|
||||
.render_startup = egl_render_startup,
|
||||
.render = egl_render,
|
||||
.update_fps = egl_update_fps
|
||||
};
|
||||
196
client/renderers/EGL/fps.c
Normal file
196
client/renderers/EGL/fps.c
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
cahe 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 "utils.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 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))
|
||||
{
|
||||
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_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
{
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
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->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);
|
||||
}
|
||||
32
client/renderers/EGL/fps.h
Normal file
32
client/renderers/EGL/fps.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "interface/font.h"
|
||||
|
||||
typedef struct EGL_FPS EGL_FPS;
|
||||
|
||||
bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_fps_free(EGL_FPS ** fps);
|
||||
|
||||
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);
|
||||
219
client/renderers/EGL/model.c
Normal file
219
client/renderers/EGL/model.c
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "model.h"
|
||||
#include "shader.h"
|
||||
#include "texture.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
struct EGL_Model
|
||||
{
|
||||
bool rebuild;
|
||||
struct ll * verticies;
|
||||
size_t vertexCount;
|
||||
bool finish;
|
||||
|
||||
bool hasBuffer;
|
||||
GLuint buffer;
|
||||
|
||||
EGL_Shader * shader;
|
||||
EGL_Texture * texture;
|
||||
};
|
||||
|
||||
struct FloatList
|
||||
{
|
||||
GLfloat * v;
|
||||
GLfloat * u;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
void update_uniform_bindings(EGL_Model * model);
|
||||
|
||||
bool egl_model_init(EGL_Model ** model)
|
||||
{
|
||||
*model = (EGL_Model *)malloc(sizeof(EGL_Model));
|
||||
if (!*model)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Model");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*model, 0, sizeof(EGL_Model));
|
||||
|
||||
(*model)->verticies = ll_new();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_model_free(EGL_Model ** model)
|
||||
{
|
||||
if (!*model)
|
||||
return;
|
||||
|
||||
struct FloatList * fl;
|
||||
while(ll_shift((*model)->verticies, (void **)&fl))
|
||||
{
|
||||
free(fl->u);
|
||||
free(fl->v);
|
||||
free(fl);
|
||||
}
|
||||
ll_free((*model)->verticies);
|
||||
|
||||
if ((*model)->hasBuffer)
|
||||
glDeleteBuffers(1, &(*model)->buffer);
|
||||
|
||||
free(*model);
|
||||
*model = NULL;
|
||||
}
|
||||
|
||||
void egl_model_set_default(EGL_Model * model)
|
||||
{
|
||||
static const GLfloat square[] =
|
||||
{
|
||||
-1.0f, -1.0f, 0.0f,
|
||||
1.0f, -1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f,
|
||||
1.0f, 1.0f, 0.0f
|
||||
};
|
||||
|
||||
static const GLfloat uvs[] =
|
||||
{
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f
|
||||
};
|
||||
|
||||
egl_model_add_verticies(model, square, uvs, 4);
|
||||
}
|
||||
|
||||
void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count)
|
||||
{
|
||||
struct FloatList * fl = (struct FloatList *)malloc(sizeof(struct FloatList));
|
||||
|
||||
fl->count = count;
|
||||
fl->v = (GLfloat *)malloc(sizeof(GLfloat) * count * 3);
|
||||
fl->u = (GLfloat *)malloc(sizeof(GLfloat) * count * 2);
|
||||
memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3);
|
||||
|
||||
if (uvs)
|
||||
memcpy(fl->u, uvs, sizeof(GLfloat) * count * 2);
|
||||
else
|
||||
memset(fl->u, 0 , sizeof(GLfloat) * count * 2);
|
||||
|
||||
ll_push(model->verticies, fl);
|
||||
model->rebuild = true;
|
||||
model->vertexCount += count;
|
||||
}
|
||||
|
||||
void egl_model_render(EGL_Model * model)
|
||||
{
|
||||
if (!model->vertexCount)
|
||||
return;
|
||||
|
||||
if (model->rebuild)
|
||||
{
|
||||
if (model->hasBuffer)
|
||||
glDeleteBuffers(1, &model->buffer);
|
||||
|
||||
/* create a buffer large enough */
|
||||
glGenBuffers(1, &model->buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, model->buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * (model->vertexCount * 5), NULL, GL_STATIC_DRAW);
|
||||
|
||||
GLintptr offset = 0;
|
||||
|
||||
/* buffer the verticies */
|
||||
struct FloatList * fl;
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&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);)
|
||||
{
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 2, fl->u);
|
||||
offset += sizeof(GLfloat) * fl->count * 2;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 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));
|
||||
|
||||
if (model->shader)
|
||||
egl_shader_use(model->shader);
|
||||
|
||||
if (model->texture)
|
||||
egl_texture_bind(model->texture);
|
||||
|
||||
/* draw the arrays */
|
||||
GLint offset = 0;
|
||||
struct FloatList * fl;
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
{
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, offset, fl->count);
|
||||
offset += fl->count;
|
||||
}
|
||||
|
||||
/* unbind and cleanup */
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void egl_model_set_shader(EGL_Model * model, EGL_Shader * shader)
|
||||
{
|
||||
model->shader = shader;
|
||||
update_uniform_bindings(model);
|
||||
}
|
||||
|
||||
void egl_model_set_texture(EGL_Model * model, EGL_Texture * texture)
|
||||
{
|
||||
model->texture = texture;
|
||||
update_uniform_bindings(model);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,19 +17,22 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "shader.h"
|
||||
#include "texture.h"
|
||||
|
||||
bool spice_connect(const char * host, const short port, const char * password);
|
||||
void spice_disconnect();
|
||||
bool spice_process();
|
||||
bool spice_ready();
|
||||
#include <GL/gl.h>
|
||||
|
||||
bool spice_key_down (uint32_t code);
|
||||
bool spice_key_up (uint32_t code);
|
||||
bool spice_mouse_mode (bool server);
|
||||
bool spice_mouse_position(uint32_t x, uint32_t y);
|
||||
bool spice_mouse_motion ( int32_t x, int32_t y);
|
||||
bool spice_mouse_press (uint32_t button);
|
||||
bool spice_mouse_release (uint32_t button);
|
||||
typedef struct EGL_Model EGL_Model;
|
||||
|
||||
bool egl_model_init(EGL_Model ** model);
|
||||
void egl_model_free(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_model_render(EGL_Model * model);
|
||||
225
client/renderers/EGL/shader.c
Normal file
225
client/renderers/EGL/shader.c
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "shader.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
struct EGL_Shader
|
||||
{
|
||||
bool hasShader;
|
||||
GLuint shader;
|
||||
};
|
||||
|
||||
bool egl_shader_init(EGL_Shader ** this)
|
||||
{
|
||||
*this = (EGL_Shader *)malloc(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)
|
||||
{
|
||||
if (!*this)
|
||||
return;
|
||||
|
||||
if ((*this)->hasShader)
|
||||
glDeleteProgram((*this)->shader);
|
||||
|
||||
free(*this);
|
||||
*this = NULL;
|
||||
}
|
||||
|
||||
bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * fragment_file)
|
||||
{
|
||||
char * vertex_code, * fragment_code;
|
||||
size_t vertex_size, fragment_size;
|
||||
|
||||
if (!file_get_contents(vertex_file, &vertex_code, &vertex_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read vertex shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Loaded vertex shader: %s", vertex_file);
|
||||
|
||||
if (!file_get_contents(fragment_file, &fragment_code, &fragment_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read fragment shader");
|
||||
free(vertex_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Loaded fragment shader: %s", fragment_file);
|
||||
|
||||
bool ret = egl_shader_compile(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)
|
||||
{
|
||||
if (this->hasShader)
|
||||
{
|
||||
glDeleteProgram(this->shader);
|
||||
this->hasShader = false;
|
||||
}
|
||||
|
||||
GLint length;
|
||||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
|
||||
length = vertex_size;
|
||||
glShaderSource(vertexShader, 1, (const char**)&vertex_code, &length);
|
||||
glCompileShader(vertexShader);
|
||||
|
||||
GLint result = GL_FALSE;
|
||||
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);
|
||||
if (result == GL_FALSE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile vertex shader");
|
||||
|
||||
int logLength;
|
||||
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
return false;
|
||||
}
|
||||
|
||||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
|
||||
length = fragment_size;
|
||||
glShaderSource(fragmentShader, 1, (const char**)&fragment_code, &length);
|
||||
glCompileShader(fragmentShader);
|
||||
|
||||
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);
|
||||
if (result == GL_FALSE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile fragment shader");
|
||||
|
||||
int logLength;
|
||||
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
|
||||
glDeleteShader(fragmentShader);
|
||||
glDeleteShader(vertexShader );
|
||||
return false;
|
||||
}
|
||||
|
||||
this->shader = glCreateProgram();
|
||||
glAttachShader(this->shader, vertexShader );
|
||||
glAttachShader(this->shader, fragmentShader);
|
||||
glLinkProgram(this->shader);
|
||||
|
||||
glGetProgramiv(this->shader, GL_LINK_STATUS, &result);
|
||||
if (result == GL_FALSE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to link shader program");
|
||||
|
||||
int logLength;
|
||||
glGetProgramiv(this->shader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetProgramInfoLog(this->shader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
|
||||
glDetachShader(this->shader, vertexShader );
|
||||
glDetachShader(this->shader, fragmentShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
glDeleteShader(vertexShader );
|
||||
glDeleteProgram(this->shader );
|
||||
return false;
|
||||
}
|
||||
|
||||
glDetachShader(this->shader, vertexShader );
|
||||
glDetachShader(this->shader, fragmentShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
glDeleteShader(vertexShader );
|
||||
|
||||
this->hasShader = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_shader_use(EGL_Shader * this)
|
||||
{
|
||||
if (this->hasShader)
|
||||
glUseProgram(this->shader);
|
||||
else
|
||||
DEBUG_ERROR("Shader program has not been compiled");
|
||||
}
|
||||
|
||||
void egl_shader_associate_textures(EGL_Shader * this, const int count)
|
||||
{
|
||||
char name[] = "sampler1";
|
||||
glUseProgram(this->shader);
|
||||
for(int i = 0; i < count; ++i, name[7]++)
|
||||
{
|
||||
GLint loc = glGetUniformLocation(this->shader, name);
|
||||
if (loc == -1)
|
||||
{
|
||||
DEBUG_WARN("Shader uniform location `%s` not found", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
glUniform1i(loc, i);
|
||||
}
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
GLint egl_shader_get_uniform_location(EGL_Shader * this, const char * name)
|
||||
{
|
||||
if (!this->shader)
|
||||
{
|
||||
DEBUG_ERROR("Shader program has not been compiled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return glGetUniformLocation(this->shader, name);
|
||||
}
|
||||
37
client/renderers/EGL/shader.h
Normal file
37
client/renderers/EGL/shader.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <stddef.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef struct EGL_Shader EGL_Shader;
|
||||
|
||||
bool egl_shader_init(EGL_Shader ** shader);
|
||||
void egl_shader_free(EGL_Shader ** shader);
|
||||
|
||||
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);
|
||||
|
||||
void egl_shader_associate_textures(EGL_Shader * shader, const int count);
|
||||
GLint egl_shader_get_uniform_location(EGL_Shader * shader, const char * name);
|
||||
12
client/renderers/EGL/shader/alert.frag
Normal file
12
client/renderers/EGL/shader/alert.frag
Normal file
@@ -0,0 +1,12 @@
|
||||
#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);
|
||||
}
|
||||
24
client/renderers/EGL/shader/alert.vert
Normal file
24
client/renderers/EGL/shader/alert.vert
Normal file
@@ -0,0 +1,24 @@
|
||||
#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;
|
||||
}
|
||||
9
client/renderers/EGL/shader/alert_bg.frag
Normal file
9
client/renderers/EGL/shader/alert_bg.frag
Normal file
@@ -0,0 +1,9 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec4 c;
|
||||
out highp vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = c;
|
||||
}
|
||||
25
client/renderers/EGL/shader/cursor.vert
Normal file
25
client/renderers/EGL/shader/cursor.vert
Normal file
@@ -0,0 +1,25 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec4 mouse;
|
||||
|
||||
out highp vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
gl_Position.x += 1.0f;
|
||||
gl_Position.y -= 1.0f;
|
||||
|
||||
gl_Position.x *= mouse.z;
|
||||
gl_Position.y *= mouse.w;
|
||||
|
||||
gl_Position.x += mouse.x;
|
||||
gl_Position.y -= mouse.y;
|
||||
|
||||
uv = vertexUV;
|
||||
}
|
||||
14
client/renderers/EGL/shader/cursor_mono.frag
Normal file
14
client/renderers/EGL/shader/cursor_mono.frag
Normal file
@@ -0,0 +1,14 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp vec4 tmp = texture(sampler1, uv);
|
||||
if (tmp.rgb == vec3(0.0, 0.0, 0.0))
|
||||
discard;
|
||||
color = tmp;
|
||||
}
|
||||
11
client/renderers/EGL/shader/cursor_rgb.frag
Normal file
11
client/renderers/EGL/shader/cursor_rgb.frag
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texture(sampler1, uv);
|
||||
}
|
||||
20
client/renderers/EGL/shader/desktop.vert
Normal file
20
client/renderers/EGL/shader/desktop.vert
Normal file
@@ -0,0 +1,20 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec4 position;
|
||||
|
||||
out highp vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
gl_Position.x -= position.x;
|
||||
gl_Position.y -= position.y;
|
||||
gl_Position.x *= position.z;
|
||||
gl_Position.y *= position.w;
|
||||
|
||||
uv = vertexUV;
|
||||
}
|
||||
27
client/renderers/EGL/shader/desktop_rgb.frag
Normal file
27
client/renderers/EGL/shader/desktop_rgb.frag
Normal file
@@ -0,0 +1,27 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
uniform int nearest;
|
||||
uniform highp vec2 size;
|
||||
|
||||
uniform int nv;
|
||||
uniform highp float nvGain;
|
||||
|
||||
void main()
|
||||
{
|
||||
if(nearest == 1)
|
||||
color = texture(sampler1, uv);
|
||||
else
|
||||
color = texelFetch(sampler1, ivec2(uv * size), 0);
|
||||
|
||||
if (nv == 1)
|
||||
{
|
||||
highp float lumi = 1.0 - (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
|
||||
color *= 1.0 + lumi;
|
||||
color *= nvGain;
|
||||
}
|
||||
}
|
||||
53
client/renderers/EGL/shader/desktop_yuv.frag
Normal file
53
client/renderers/EGL/shader/desktop_yuv.frag
Normal file
@@ -0,0 +1,53 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform int nearest;
|
||||
uniform highp vec2 size;
|
||||
|
||||
uniform int nv;
|
||||
uniform highp float nvGain;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
uniform sampler2D sampler2;
|
||||
uniform sampler2D sampler3;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp vec4 yuv;
|
||||
if(nearest == 1)
|
||||
{
|
||||
yuv = vec4(
|
||||
texture(sampler1, uv).r,
|
||||
texture(sampler2, uv).r,
|
||||
texture(sampler3, uv).r,
|
||||
1.0
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
highp ivec2 px = ivec2(uv * size);
|
||||
yuv = vec4(
|
||||
texelFetch(sampler1, px, 0).r,
|
||||
texelFetch(sampler2, px, 0).r,
|
||||
texelFetch(sampler3, px, 0).r,
|
||||
1.0
|
||||
);
|
||||
}
|
||||
|
||||
highp mat4 yuv_to_rgb = mat4(
|
||||
1.0, 0.0 , 1.402, -0.701,
|
||||
1.0, -0.344, -0.714, 0.529,
|
||||
1.0, 1.772, 0.0 , -0.886,
|
||||
1.0, 1.0 , 1.0 , 1.0
|
||||
);
|
||||
|
||||
color = yuv * yuv_to_rgb;
|
||||
if (nv == 1)
|
||||
{
|
||||
highp float lumi = 1.0 - (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
|
||||
color *= 1.0 + lumi;
|
||||
color *= nvGain;
|
||||
}
|
||||
}
|
||||
11
client/renderers/EGL/shader/fps.frag
Normal file
11
client/renderers/EGL/shader/fps.frag
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texture(sampler1, uv);
|
||||
}
|
||||
22
client/renderers/EGL/shader/fps.vert
Normal file
22
client/renderers/EGL/shader/fps.vert
Normal file
@@ -0,0 +1,22 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec2 screen;
|
||||
uniform vec2 size;
|
||||
|
||||
out highp vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
gl_Position.xy *= screen.xy * size.xy;
|
||||
gl_Position.x -= 1.0 - (screen.x * size.x);
|
||||
gl_Position.y += 1.0 - (screen.y * size.y);
|
||||
gl_Position.x += screen.x * 10.0;
|
||||
gl_Position.y -= screen.y * 10.0;
|
||||
|
||||
uv = vertexUV;
|
||||
}
|
||||
8
client/renderers/EGL/shader/fps_bg.frag
Normal file
8
client/renderers/EGL/shader/fps_bg.frag
Normal file
@@ -0,0 +1,8 @@
|
||||
#version 300 es
|
||||
|
||||
out highp vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vec4(0.0, 0.0, 1.0, 0.5);
|
||||
}
|
||||
13
client/renderers/EGL/shader/splash_bg.frag
Normal file
13
client/renderers/EGL/shader/splash_bg.frag
Normal file
@@ -0,0 +1,13 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec3 pos;
|
||||
in highp float a;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp float d = 1.0 - sqrt(pos.x * pos.x + pos.y * pos.y) / 2.0;
|
||||
color = vec4(0.234375 * d, 0.015625f * d, 0.425781f * d, a);
|
||||
}
|
||||
17
client/renderers/EGL/shader/splash_bg.vert
Normal file
17
client/renderers/EGL/shader/splash_bg.vert
Normal file
@@ -0,0 +1,17 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
uniform float alpha;
|
||||
|
||||
out highp vec3 pos;
|
||||
out highp float a;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
pos = vertexPosition_modelspace;
|
||||
a = alpha;
|
||||
}
|
||||
11
client/renderers/EGL/shader/splash_logo.frag
Normal file
11
client/renderers/EGL/shader/splash_logo.frag
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 300 es
|
||||
|
||||
out highp vec4 color;
|
||||
in highp float a;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vec4(1.0, 1.0, 1.0, a);
|
||||
}
|
||||
16
client/renderers/EGL/shader/splash_logo.vert
Normal file
16
client/renderers/EGL/shader/splash_logo.vert
Normal file
@@ -0,0 +1,16 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
uniform vec2 scale;
|
||||
|
||||
out highp float a;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.y *= scale.y;
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
a = scale.x;
|
||||
}
|
||||
177
client/renderers/EGL/splash.c
Normal file
177
client/renderers/EGL/splash.c
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
cahe 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 "splash.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "draw.h"
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "splash_bg.vert.h"
|
||||
#include "splash_bg.frag.h"
|
||||
#include "splash_logo.vert.h"
|
||||
#include "splash_logo.frag.h"
|
||||
|
||||
struct EGL_Splash
|
||||
{
|
||||
EGL_Shader * bgShader;
|
||||
EGL_Model * bg;
|
||||
|
||||
EGL_Shader * logoShader;
|
||||
EGL_Model * logo;
|
||||
|
||||
// uniforms
|
||||
GLint uBGAlpha;
|
||||
GLint uScale;
|
||||
};
|
||||
|
||||
bool egl_splash_init(EGL_Splash ** splash)
|
||||
{
|
||||
*splash = (EGL_Splash *)malloc(sizeof(EGL_Splash));
|
||||
if (!*splash)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Splash");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*splash, 0, sizeof(EGL_Splash));
|
||||
|
||||
if (!egl_shader_init(&(*splash)->bgShader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the splash bgShader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*splash)->bgShader,
|
||||
b_shader_splash_bg_vert, b_shader_splash_bg_vert_size,
|
||||
b_shader_splash_bg_frag, b_shader_splash_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the splash bgShader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*splash)->uBGAlpha = egl_shader_get_uniform_location((*splash)->bgShader, "alpha");
|
||||
|
||||
if (!egl_model_init(&(*splash)->bg))
|
||||
{
|
||||
DEBUG_ERROR("Failed to intiailize the splash bg model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*splash)->bg);
|
||||
|
||||
if (!egl_shader_init(&(*splash)->logoShader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the splash logoShader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*splash)->logoShader,
|
||||
b_shader_splash_logo_vert, b_shader_splash_logo_vert_size,
|
||||
b_shader_splash_logo_frag, b_shader_splash_logo_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the splash logoShader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*splash)->uScale = egl_shader_get_uniform_location((*splash)->logoShader, "scale");
|
||||
|
||||
if (!egl_model_init(&(*splash)->logo))
|
||||
{
|
||||
DEBUG_ERROR("Failed to intiailize the splash model");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* build the splash model */
|
||||
#define P(x) ((1.0f/800.0f)*(float)(x))
|
||||
egl_draw_torus_arc((*splash)->logo, 30, P( 0 ), P(0), P(102), P(98), 0.0f, -M_PI);
|
||||
egl_draw_torus ((*splash)->logo, 30, P(-100), P(8), P(8 ), P(4 ));
|
||||
egl_draw_torus ((*splash)->logo, 30, P( 100), P(8), P(8 ), P(4 ));
|
||||
|
||||
egl_draw_torus ((*splash)->logo, 60, P(0), P(0), P(83), P(79));
|
||||
egl_draw_torus ((*splash)->logo, 60, P(0), P(0), P(67), P(63));
|
||||
|
||||
static const GLfloat lines[][12] =
|
||||
{
|
||||
{
|
||||
P( -2), P(-140), 0.0f,
|
||||
P( -2), P(-100), 0.0f,
|
||||
P( 2), P(-140), 0.0f,
|
||||
P( 2), P(-100), 0.0f
|
||||
},
|
||||
{
|
||||
P(-26), P(-144), 0.0f,
|
||||
P(-26), P(-140), 0.0f,
|
||||
P( 26), P(-144), 0.0f,
|
||||
P( 26), P(-140), 0.0f
|
||||
},
|
||||
{
|
||||
P(-40), P(-156), 0.0f,
|
||||
P(-40), P(-152), 0.0f,
|
||||
P( 40), P(-156), 0.0f,
|
||||
P( 40), P(-152), 0.0f
|
||||
}
|
||||
};
|
||||
|
||||
egl_model_add_verticies((*splash)->logo, lines[0], NULL, 4);
|
||||
egl_model_add_verticies((*splash)->logo, lines[1], NULL, 4);
|
||||
egl_model_add_verticies((*splash)->logo, lines[2], NULL, 4);
|
||||
|
||||
egl_draw_torus_arc((*splash)->logo, 10, P(-26), P(-154), P(10), P(14), M_PI , -M_PI / 2.0);
|
||||
egl_draw_torus_arc((*splash)->logo, 10, P( 26), P(-154), P(10), P(14), M_PI / 2.0f, -M_PI / 2.0);
|
||||
#undef P
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_splash_free(EGL_Splash ** splash)
|
||||
{
|
||||
if (!*splash)
|
||||
return;
|
||||
|
||||
egl_model_free(&(*splash)->logo);
|
||||
|
||||
free(*splash);
|
||||
*splash = NULL;
|
||||
}
|
||||
|
||||
void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY)
|
||||
{
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
egl_shader_use(splash->bgShader);
|
||||
glUniform1f(splash->uBGAlpha, alpha);
|
||||
egl_model_render(splash->bg);
|
||||
|
||||
egl_shader_use(splash->logoShader);
|
||||
glUniform2f(splash->uScale, alpha, scaleY);
|
||||
egl_model_render(splash->logo);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
29
client/renderers/EGL/splash.h
Normal file
29
client/renderers/EGL/splash.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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>
|
||||
|
||||
typedef struct EGL_Splash EGL_Splash;
|
||||
|
||||
bool egl_splash_init(EGL_Splash ** splash);
|
||||
void egl_splash_free(EGL_Splash ** splash);
|
||||
|
||||
void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY);
|
||||
479
client/renderers/EGL/texture.c
Normal file
479
client/renderers/EGL/texture.c
Normal file
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "texture.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/framebuffer.h"
|
||||
#include "debug.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
/* this must be a multiple of 2 */
|
||||
#define TEXTURE_COUNT 2
|
||||
|
||||
struct Tex
|
||||
{
|
||||
GLuint t[3];
|
||||
bool hasPBO;
|
||||
GLuint pbo;
|
||||
void * map;
|
||||
GLsync sync;
|
||||
};
|
||||
|
||||
struct TexState
|
||||
{
|
||||
_Atomic(uint8_t) w, u, s, d;
|
||||
};
|
||||
|
||||
struct EGL_Texture
|
||||
{
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
size_t width, height, stride;
|
||||
size_t bpp;
|
||||
bool streaming;
|
||||
bool ready;
|
||||
|
||||
int planeCount;
|
||||
GLuint samplers[3];
|
||||
size_t planes [3][3];
|
||||
GLintptr offsets [3];
|
||||
GLenum intFormat;
|
||||
GLenum format;
|
||||
GLenum dataType;
|
||||
size_t pboBufferSize;
|
||||
|
||||
struct TexState state;
|
||||
int textureCount;
|
||||
struct Tex tex[TEXTURE_COUNT];
|
||||
};
|
||||
|
||||
bool egl_texture_init(EGL_Texture ** texture)
|
||||
{
|
||||
*texture = (EGL_Texture *)malloc(sizeof(EGL_Texture));
|
||||
if (!*texture)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*texture, 0, sizeof(EGL_Texture));
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_texture_free(EGL_Texture ** texture)
|
||||
{
|
||||
if (!*texture)
|
||||
return;
|
||||
|
||||
if ((*texture)->planeCount > 0)
|
||||
glDeleteSamplers((*texture)->planeCount, (*texture)->samplers);
|
||||
|
||||
for(int i = 0; i < (*texture)->textureCount; ++i)
|
||||
{
|
||||
struct Tex * t = &(*texture)->tex[i];
|
||||
if (t->hasPBO)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, t->pbo);
|
||||
if ((*texture)->tex[i].map)
|
||||
{
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
(*texture)->tex[i].map = NULL;
|
||||
}
|
||||
glDeleteBuffers(1, &t->pbo);
|
||||
if (t->sync)
|
||||
glDeleteSync(t->sync);
|
||||
}
|
||||
|
||||
if ((*texture)->planeCount > 0)
|
||||
glDeleteTextures((*texture)->planeCount, t->t);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
free(*texture);
|
||||
*texture = NULL;
|
||||
}
|
||||
|
||||
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||
texture->tex[i].map = glMapBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
texture->pboBufferSize,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_UNSYNCHRONIZED_BIT |
|
||||
GL_MAP_INVALIDATE_BUFFER_BIT
|
||||
);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
if (!texture->tex[i].map)
|
||||
{
|
||||
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_texture_unmap(EGL_Texture * texture, uint8_t i)
|
||||
{
|
||||
if (!texture->tex[i].map)
|
||||
return;
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
texture->tex[i].map = NULL;
|
||||
}
|
||||
|
||||
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming)
|
||||
{
|
||||
int planeCount;
|
||||
|
||||
if (texture->streaming)
|
||||
{
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
egl_texture_unmap(texture, i);
|
||||
if (texture->tex[i].hasPBO)
|
||||
{
|
||||
glDeleteBuffers(1, &texture->tex[i].pbo);
|
||||
texture->tex[i].hasPBO = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture->pixFmt = pixFmt;
|
||||
texture->width = width;
|
||||
texture->height = height;
|
||||
texture->stride = stride;
|
||||
texture->streaming = streaming;
|
||||
texture->textureCount = streaming ? TEXTURE_COUNT : 1;
|
||||
texture->ready = false;
|
||||
|
||||
atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed);
|
||||
|
||||
switch(pixFmt)
|
||||
{
|
||||
case EGL_PF_BGRA:
|
||||
planeCount = 1;
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_BGRA;
|
||||
texture->planes[0][0] = width;
|
||||
texture->planes[0][1] = height;
|
||||
texture->planes[0][2] = stride / 4;
|
||||
texture->offsets[0] = 0;
|
||||
texture->intFormat = GL_BGRA;
|
||||
texture->dataType = GL_UNSIGNED_BYTE;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
case EGL_PF_RGBA:
|
||||
planeCount = 1;
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_RGBA;
|
||||
texture->planes[0][0] = width;
|
||||
texture->planes[0][1] = height;
|
||||
texture->planes[0][2] = stride / 4;
|
||||
texture->offsets[0] = 0;
|
||||
texture->intFormat = GL_BGRA;
|
||||
texture->dataType = GL_UNSIGNED_BYTE;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
case EGL_PF_RGBA10:
|
||||
planeCount = 1;
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_RGBA;
|
||||
texture->planes[0][0] = width;
|
||||
texture->planes[0][1] = height;
|
||||
texture->planes[0][2] = stride / 4;
|
||||
texture->offsets[0] = 0;
|
||||
texture->intFormat = GL_RGB10_A2;
|
||||
texture->dataType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
case EGL_PF_YUV420:
|
||||
planeCount = 3;
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_RED;
|
||||
texture->planes[0][0] = width;
|
||||
texture->planes[0][1] = height;
|
||||
texture->planes[0][2] = stride;
|
||||
texture->planes[1][0] = width / 2;
|
||||
texture->planes[1][1] = height / 2;
|
||||
texture->planes[1][2] = stride / 2;
|
||||
texture->planes[2][0] = width / 2;
|
||||
texture->planes[2][1] = height / 2;
|
||||
texture->planes[2][2] = stride / 2;
|
||||
texture->offsets[0] = 0;
|
||||
texture->offsets[1] = stride * height;
|
||||
texture->offsets[2] = texture->offsets[1] + (texture->offsets[1] / 4);
|
||||
texture->dataType = GL_UNSIGNED_BYTE;
|
||||
texture->pboBufferSize = texture->offsets[2] + (texture->offsets[1] / 4);
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported pixel format");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (planeCount > texture->planeCount)
|
||||
{
|
||||
if (texture->planeCount > 0)
|
||||
glDeleteSamplers(texture->planeCount, texture->samplers);
|
||||
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
if (texture->planeCount > 0)
|
||||
glDeleteTextures(texture->planeCount, texture->tex[i].t);
|
||||
glGenTextures(planeCount, texture->tex[i].t);
|
||||
}
|
||||
|
||||
glGenSamplers(planeCount, texture->samplers);
|
||||
for(int p = 0; p < planeCount; ++p)
|
||||
{
|
||||
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
texture->planeCount = planeCount;
|
||||
}
|
||||
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
for(int p = 0; p < planeCount; ++p)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[i].t[p]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->planes[p][0],
|
||||
texture->planes[p][1], 0, texture->format, texture->dataType, NULL);
|
||||
}
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
if (!streaming)
|
||||
return true;
|
||||
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
glGenBuffers(1, &texture->tex[i].pbo);
|
||||
texture->tex[i].hasPBO = true;
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||
glBufferStorage(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
texture->pboBufferSize,
|
||||
NULL,
|
||||
GL_MAP_WRITE_BIT
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_warn_slow()
|
||||
{
|
||||
static bool warnDone = false;
|
||||
if (!warnDone)
|
||||
{
|
||||
warnDone = true;
|
||||
DEBUG_BREAK();
|
||||
DEBUG_WARN("The guest is providing updates faster then your computer can display them");
|
||||
DEBUG_WARN("This is a hardware limitation, expect microstutters & frame skips");
|
||||
DEBUG_BREAK();
|
||||
}
|
||||
}
|
||||
|
||||
bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
|
||||
{
|
||||
if (texture->streaming)
|
||||
{
|
||||
const uint8_t sw =
|
||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||
|
||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||
{
|
||||
egl_warn_slow();
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t t = sw % TEXTURE_COUNT;
|
||||
if (!egl_texture_map(texture, t))
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
|
||||
memcpy(texture->tex[t].map, buffer, texture->pboBufferSize);
|
||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
||||
egl_texture_unmap(texture, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int p = 0; p < texture->planeCount; ++p)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[0].t[p]);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][0]);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
|
||||
texture->format, texture->dataType, buffer + texture->offsets[p]);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame)
|
||||
{
|
||||
if (!texture->streaming)
|
||||
return false;
|
||||
|
||||
const uint8_t sw =
|
||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||
|
||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||
{
|
||||
egl_warn_slow();
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t t = sw % TEXTURE_COUNT;
|
||||
if (!egl_texture_map(texture, t))
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
|
||||
framebuffer_read(
|
||||
frame,
|
||||
texture->tex[t].map,
|
||||
texture->stride,
|
||||
texture->height,
|
||||
texture->width,
|
||||
texture->bpp,
|
||||
texture->stride
|
||||
);
|
||||
|
||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
||||
egl_texture_unmap(texture, t);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
|
||||
{
|
||||
if (!texture->streaming)
|
||||
return EGL_TEX_STATUS_OK;
|
||||
|
||||
const uint8_t su =
|
||||
atomic_load_explicit(&texture->state.u, memory_order_acquire);
|
||||
|
||||
const uint8_t nextu = su + 1;
|
||||
if (
|
||||
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
|
||||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
|
||||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
|
||||
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
|
||||
|
||||
/* update the texture */
|
||||
const uint8_t t = su % TEXTURE_COUNT;
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[t].pbo);
|
||||
for(int p = 0; p < texture->planeCount; ++p)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[p]);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][2]);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
|
||||
texture->format, texture->dataType, (const void *)texture->offsets[p]);
|
||||
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
/* create a fence to prevent usage before the update is complete */
|
||||
texture->tex[t].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
|
||||
/* we must flush to ensure the sync is in the command buffer */
|
||||
glFlush();
|
||||
|
||||
texture->ready = true;
|
||||
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
|
||||
|
||||
return EGL_TEX_STATUS_OK;
|
||||
}
|
||||
|
||||
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
||||
{
|
||||
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
|
||||
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
|
||||
|
||||
if (texture->streaming)
|
||||
{
|
||||
if (!texture->ready)
|
||||
return EGL_TEX_STATUS_NOTREADY;
|
||||
|
||||
const uint8_t t = ss % TEXTURE_COUNT;
|
||||
if (texture->tex[t].sync != 0)
|
||||
{
|
||||
switch(glClientWaitSync(texture->tex[t].sync, 0, 20000000)) // 20ms
|
||||
{
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
glDeleteSync(texture->tex[t].sync);
|
||||
texture->tex[t].sync = 0;
|
||||
|
||||
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
|
||||
memory_order_release) + 1;
|
||||
break;
|
||||
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
break;
|
||||
|
||||
case GL_WAIT_FAILED:
|
||||
case GL_INVALID_VALUE:
|
||||
glDeleteSync(texture->tex[t].sync);
|
||||
texture->tex[t].sync = 0;
|
||||
EGL_ERROR("glClientWaitSync failed");
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (ss != sd && ss != (uint8_t)(sd + 1))
|
||||
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
|
||||
memory_order_release) + 1;
|
||||
}
|
||||
|
||||
const uint8_t t = sd % TEXTURE_COUNT;
|
||||
for(int i = 0; i < texture->planeCount; ++i)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + i);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[i]);
|
||||
glBindSampler(i, texture->samplers[i]);
|
||||
}
|
||||
|
||||
return EGL_TEX_STATUS_OK;
|
||||
}
|
||||
|
||||
int egl_texture_count(EGL_Texture * texture)
|
||||
{
|
||||
return texture->planeCount;
|
||||
}
|
||||
53
client/renderers/EGL/texture.h
Normal file
53
client/renderers/EGL/texture.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "shader.h"
|
||||
#include "common/framebuffer.h"
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef struct EGL_Texture EGL_Texture;
|
||||
|
||||
enum EGL_PixelFormat
|
||||
{
|
||||
EGL_PF_RGBA,
|
||||
EGL_PF_BGRA,
|
||||
EGL_PF_RGBA10,
|
||||
EGL_PF_YUV420
|
||||
};
|
||||
|
||||
enum EGL_TexStatus
|
||||
{
|
||||
EGL_TEX_STATUS_NOTREADY,
|
||||
EGL_TEX_STATUS_OK,
|
||||
EGL_TEX_STATUS_ERROR
|
||||
};
|
||||
|
||||
bool egl_texture_init(EGL_Texture ** tex);
|
||||
void egl_texture_free(EGL_Texture ** tex);
|
||||
|
||||
bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming);
|
||||
bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
|
||||
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame);
|
||||
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
|
||||
enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture);
|
||||
int egl_texture_count (EGL_Texture * texture);
|
||||
25
client/renderers/OpenGL/CMakeLists.txt
Normal file
25
client/renderers/OpenGL/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(renderer_Opengl LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(RENDERER_OPENGL_PKGCONFIG REQUIRED
|
||||
gl
|
||||
glu
|
||||
)
|
||||
|
||||
add_library(renderer_OpenGL STATIC
|
||||
opengl.c
|
||||
)
|
||||
|
||||
target_link_libraries(renderer_OpenGL
|
||||
${RENDERER_OPENGL_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
decoders
|
||||
fonts
|
||||
)
|
||||
|
||||
target_include_directories(renderer_OpenGL
|
||||
PRIVATE
|
||||
src
|
||||
${RENDERER_OPENGL_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
1358
client/renderers/OpenGL/opengl.c
Normal file
1358
client/renderers/OpenGL/opengl.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,814 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "lg-renderer.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glu.h>
|
||||
#include <GL/glx.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "memcpySSE.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define FRAME_TEXTURE 0
|
||||
#define FPS_TEXTURE 1
|
||||
#define MOUSE_TEXTURE 2
|
||||
#define TEXTURE_COUNT 3
|
||||
|
||||
static PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI = NULL;
|
||||
static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI = NULL;
|
||||
|
||||
struct Options
|
||||
{
|
||||
bool mipmap;
|
||||
bool vsync;
|
||||
bool preventBuffer;
|
||||
bool splitMouse;
|
||||
};
|
||||
|
||||
static struct Options defaultOptions =
|
||||
{
|
||||
.mipmap = true,
|
||||
.vsync = true,
|
||||
.preventBuffer = true,
|
||||
.splitMouse = false
|
||||
};
|
||||
|
||||
struct LGR_OpenGLBasic
|
||||
{
|
||||
LG_RendererParams params;
|
||||
struct Options opt;
|
||||
|
||||
bool configured;
|
||||
SDL_Window * sdlWindow;
|
||||
SDL_GLContext glContext;
|
||||
bool doneInfo;
|
||||
|
||||
SDL_Point window;
|
||||
bool resizeWindow;
|
||||
bool frameUpdate;
|
||||
|
||||
LG_RendererFormat format;
|
||||
GLuint intFormat;
|
||||
GLuint vboFormat;
|
||||
size_t texSize;
|
||||
|
||||
uint64_t drawStart;
|
||||
int texList;
|
||||
int fpsList;
|
||||
int mouseList;
|
||||
LG_RendererRect destRect;
|
||||
|
||||
bool hasTextures;
|
||||
GLuint textures[TEXTURE_COUNT];
|
||||
|
||||
uint gpuFrameCount;
|
||||
bool fpsTexture;
|
||||
uint64_t lastFrameTime;
|
||||
uint64_t renderTime;
|
||||
uint64_t frameCount;
|
||||
uint64_t renderCount;
|
||||
SDL_Rect fpsRect;
|
||||
|
||||
bool mouseUpdate;
|
||||
bool newShape;
|
||||
uint64_t lastMouseDraw;
|
||||
LG_RendererCursor mouseType;
|
||||
bool mouseVisible;
|
||||
SDL_Rect mousePos;
|
||||
};
|
||||
|
||||
bool lgr_opengl_basic_check_error(const char * name)
|
||||
{
|
||||
GLenum error = glGetError();
|
||||
if (error == GL_NO_ERROR)
|
||||
return false;
|
||||
|
||||
const GLubyte * errStr = gluErrorString(error);
|
||||
DEBUG_ERROR("%s = %d (%s)", name, error, errStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char * lgr_opengl_basic_get_name()
|
||||
{
|
||||
return "OpenGL-Basic";
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_create(void ** opaque, const LG_RendererParams params)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct LGR_OpenGLBasic));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct LGR_OpenGLBasic));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct LGR_OpenGLBasic));
|
||||
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)*opaque;
|
||||
memcpy(&this->params, ¶ms , sizeof(LG_RendererParams));
|
||||
memcpy(&this->opt , &defaultOptions, sizeof(struct Options ));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (!glXGetVideoSyncSGI)
|
||||
{
|
||||
glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC )glXGetProcAddress((const GLubyte *)"glXGetVideoSyncSGI" );
|
||||
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddress((const GLubyte *)"glXWaitVideoSyncSGI");
|
||||
|
||||
if (!glXGetVideoSyncSGI || !glXWaitVideoSyncSGI)
|
||||
{
|
||||
glXGetVideoSyncSGI = NULL;
|
||||
DEBUG_ERROR("Failed to get proc addresses");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_configure(void * opaque, SDL_Window *window, const LG_RendererFormat format)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (this->configured)
|
||||
{
|
||||
DEBUG_ERROR("Renderer already configured, call deconfigure first");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->sdlWindow = window;
|
||||
this->glContext = SDL_GL_CreateContext(window);
|
||||
if (!this->glContext)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the OpenGL context");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->doneInfo)
|
||||
{
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
this->doneInfo = true;
|
||||
}
|
||||
|
||||
if (SDL_GL_MakeCurrent(window, this->glContext) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to make the GL context current");
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0);
|
||||
|
||||
// check if the GPU supports GL_ARB_buffer_storage first
|
||||
// there is no advantage to this renderer if it is not present.
|
||||
const GLubyte * extensions = glGetString(GL_EXTENSIONS);
|
||||
if (!gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions))
|
||||
{
|
||||
DEBUG_INFO("The GPU doesn't support GL_ARB_buffer_storage");
|
||||
return false;
|
||||
}
|
||||
|
||||
// assume 24 and 32 bit formats are RGB and RGBA
|
||||
switch(format.bpp)
|
||||
{
|
||||
case 24:
|
||||
this->intFormat = GL_RGB8;
|
||||
this->vboFormat = GL_BGR;
|
||||
break;
|
||||
|
||||
case 32:
|
||||
this->intFormat = GL_RGBA8;
|
||||
this->vboFormat = GL_BGRA;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_INFO("%d bpp not supported", format.bpp);
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate the texture size in bytes
|
||||
this->texSize = format.height * format.pitch;
|
||||
|
||||
// generate lists for drawing
|
||||
this->texList = glGenLists(1);
|
||||
this->fpsList = glGenLists(1);
|
||||
this->mouseList = glGenLists(1);
|
||||
|
||||
// create the textures
|
||||
glGenTextures(TEXTURE_COUNT, this->textures);
|
||||
if (lgr_opengl_basic_check_error("glGenTextures"))
|
||||
return false;
|
||||
this->hasTextures = true;
|
||||
|
||||
// create the frame texture
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
|
||||
if (lgr_opengl_basic_check_error("glBindTexture"))
|
||||
return false;
|
||||
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
this->intFormat,
|
||||
format.width,
|
||||
format.height,
|
||||
0,
|
||||
this->vboFormat,
|
||||
GL_UNSIGNED_BYTE,
|
||||
(void*)0
|
||||
);
|
||||
if (lgr_opengl_basic_check_error("glTexImage2D"))
|
||||
return false;
|
||||
|
||||
// configure the texture
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
// create the display list
|
||||
glNewList(this->texList, GL_COMPILE);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(format.width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , format.height);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(format.width, format.height);
|
||||
glEnd();
|
||||
glEndList();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_COLOR_MATERIAL);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
this->resizeWindow = true;
|
||||
this->drawStart = nanotime();
|
||||
glXGetVideoSyncSGI(&this->gpuFrameCount);
|
||||
|
||||
// copy the format into the local storage
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->configured = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void lgr_opengl_basic_deconfigure(void * opaque)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return;
|
||||
|
||||
if (this->hasTextures)
|
||||
{
|
||||
glDeleteTextures(TEXTURE_COUNT, this->textures);
|
||||
this->hasTextures = false;
|
||||
}
|
||||
|
||||
if (this->glContext)
|
||||
{
|
||||
SDL_GL_DeleteContext(this->glContext);
|
||||
this->glContext = NULL;
|
||||
}
|
||||
|
||||
this->configured = false;
|
||||
}
|
||||
|
||||
void lgr_opengl_basic_deinitialize(void * opaque)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
if (this->configured)
|
||||
lgr_opengl_basic_deconfigure(opaque);
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_is_compatible(void * opaque, const LG_RendererFormat format)
|
||||
{
|
||||
const struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return false;
|
||||
|
||||
return (memcmp(&this->format, &format, sizeof(LG_RendererFormat)) == 0);
|
||||
}
|
||||
|
||||
void lgr_opengl_basic_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return;
|
||||
|
||||
this->window.x = width;
|
||||
this->window.y = height;
|
||||
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
|
||||
|
||||
this->resizeWindow = true;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return false;
|
||||
|
||||
this->mouseType = cursor;
|
||||
switch(cursor)
|
||||
{
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glTexImage2D
|
||||
(
|
||||
GL_TEXTURE_2D,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
width ,
|
||||
height ,
|
||||
0 ,
|
||||
GL_BGRA, // windows cursors are in BGRA format
|
||||
GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->mousePos.w = width;
|
||||
this->mousePos.h = height;
|
||||
|
||||
glNewList(this->mouseList, GL_COMPILE);
|
||||
glEnable(GL_BLEND);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , height);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, height);
|
||||
glEnd();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_BLEND);
|
||||
glEndList();
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
const int hheight = height / 2;
|
||||
uint32_t d[width * height];
|
||||
for(int y = 0; y < hheight; ++y)
|
||||
for(int x = 0; x < width; ++x)
|
||||
{
|
||||
const uint8_t * srcAnd = data + (pitch * y) + (x / 8);
|
||||
const uint8_t * srcXor = srcAnd + pitch * hheight;
|
||||
const uint8_t mask = 0x80 >> (x % 8);
|
||||
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
|
||||
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
|
||||
|
||||
d[y * width + x ] = andMask;
|
||||
d[y * width + x + width * hheight] = xorMask;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glTexImage2D
|
||||
(
|
||||
GL_TEXTURE_2D,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
width ,
|
||||
height ,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
d
|
||||
);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->mousePos.w = width;
|
||||
this->mousePos.h = hheight;
|
||||
|
||||
glNewList(this->mouseList, GL_COMPILE);
|
||||
glEnable(GL_COLOR_LOGIC_OP);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glLogicOp(GL_AND);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , hheight);
|
||||
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, hheight);
|
||||
glEnd();
|
||||
glLogicOp(GL_XOR);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , hheight);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, hheight);
|
||||
glEnd();
|
||||
glDisable(GL_COLOR_LOGIC_OP);
|
||||
glEndList();
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
//TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this->mouseUpdate = true;
|
||||
this->newShape = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return false;
|
||||
|
||||
if (this->mousePos.x == x && this->mousePos.y == y && this->mouseVisible == visible)
|
||||
return true;
|
||||
|
||||
this->mouseVisible = visible;
|
||||
this->mousePos.x = x;
|
||||
this->mousePos.y = y;
|
||||
this->mouseUpdate = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_on_frame_event(void * opaque, const uint8_t * data)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Invalid opaque pointer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->configured)
|
||||
{
|
||||
DEBUG_ERROR("Not configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->params.showFPS && this->renderTime > 1e9)
|
||||
{
|
||||
char str[128];
|
||||
const float avgFPS = 1000.0f / (((float)this->renderTime / this->frameCount ) / 1e6f);
|
||||
const float renderFPS = 1000.0f / (((float)this->renderTime / this->renderCount) / 1e6f);
|
||||
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
|
||||
SDL_Color color = {0xff, 0xff, 0xff};
|
||||
SDL_Surface *textSurface = NULL;
|
||||
if (!(textSurface = TTF_RenderText_Blended(this->params.font, str, color)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to render text");
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D , this->textures[FPS_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, textSurface->w );
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
textSurface->format->BytesPerPixel,
|
||||
textSurface->w,
|
||||
textSurface->h,
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
textSurface->pixels
|
||||
);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->fpsRect.x = 5;
|
||||
this->fpsRect.y = 5;
|
||||
this->fpsRect.w = textSurface->w;
|
||||
this->fpsRect.h = textSurface->h;
|
||||
|
||||
SDL_FreeSurface(textSurface);
|
||||
|
||||
this->renderTime = 0;
|
||||
this->frameCount = 0;
|
||||
this->renderCount = 0;
|
||||
this->fpsTexture = true;
|
||||
|
||||
glNewList(this->fpsList, GL_COMPILE);
|
||||
glEnable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glVertex2i(this->fpsRect.x , this->fpsRect.y );
|
||||
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
|
||||
glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
|
||||
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
|
||||
glEnd();
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FPS_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f , 0.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y );
|
||||
glTexCoord2f(1.0f , 0.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
|
||||
glTexCoord2f(0.0f , 1.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
|
||||
glEnd();
|
||||
glDisable(GL_BLEND);
|
||||
glEndList();
|
||||
}
|
||||
|
||||
// bind the texture and update it
|
||||
glBindTexture(GL_TEXTURE_2D , this->textures[FRAME_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride );
|
||||
|
||||
// update the texture
|
||||
glTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
this->format.width ,
|
||||
this->format.height,
|
||||
this->vboFormat,
|
||||
GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
lgr_opengl_basic_check_error("glTexSubImage2D");
|
||||
|
||||
// unbind the buffer
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
const bool mipmap = this->opt.mipmap && (
|
||||
(this->format.width > this->destRect.w) ||
|
||||
(this->format.height > this->destRect.h));
|
||||
|
||||
if (mipmap)
|
||||
{
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
++this->frameCount;
|
||||
this->frameUpdate = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void lgr_opengl_basic_draw_mouse(struct LGR_OpenGLBasic * this)
|
||||
{
|
||||
if (!this->mouseVisible)
|
||||
return;
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
|
||||
glCallList(this->mouseList);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_render(void * opaque)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return false;
|
||||
|
||||
if (this->resizeWindow)
|
||||
{
|
||||
// setup the projection matrix
|
||||
glViewport(0, 0, this->window.x, this->window.y);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluOrtho2D(0, this->window.x, this->window.y, 0);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glTranslatef(this->destRect.x, this->destRect.y, 0.0f);
|
||||
glScalef(
|
||||
(float)this->destRect.w / (float)this->format.width,
|
||||
(float)this->destRect.h / (float)this->format.height,
|
||||
1.0f
|
||||
);
|
||||
|
||||
// update the scissor rect to prevent drawing outside of the frame
|
||||
glScissor(
|
||||
this->destRect.x,
|
||||
this->destRect.y,
|
||||
this->destRect.w,
|
||||
this->destRect.h
|
||||
);
|
||||
|
||||
this->resizeWindow = false;
|
||||
}
|
||||
|
||||
|
||||
if (this->opt.splitMouse)
|
||||
{
|
||||
if (!this->frameUpdate)
|
||||
{
|
||||
if (!this->mouseUpdate)
|
||||
return true;
|
||||
|
||||
if (!this->newShape)
|
||||
{
|
||||
// don't update the mouse too fast
|
||||
const uint64_t delta = nanotime() - this->lastMouseDraw;
|
||||
if (delta < 5e6)
|
||||
return true;
|
||||
}
|
||||
this->newShape = false;
|
||||
|
||||
glDrawBuffer(GL_FRONT);
|
||||
glCallList(this->texList);
|
||||
lgr_opengl_basic_draw_mouse(this);
|
||||
if (this->fpsTexture)
|
||||
glCallList(this->fpsList);
|
||||
glDrawBuffer(GL_BACK);
|
||||
glFlush();
|
||||
|
||||
this->mouseUpdate = false;
|
||||
this->lastMouseDraw = nanotime();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
glCallList(this->texList);
|
||||
lgr_opengl_basic_draw_mouse(this);
|
||||
if (this->fpsTexture)
|
||||
glCallList(this->fpsList);
|
||||
|
||||
if (this->opt.preventBuffer)
|
||||
{
|
||||
unsigned int before, after;
|
||||
glXGetVideoSyncSGI(&before);
|
||||
SDL_GL_SwapWindow(this->sdlWindow);
|
||||
|
||||
// wait for the swap to happen to ensure we dont buffer frames
|
||||
glXGetVideoSyncSGI(&after);
|
||||
if (before == after)
|
||||
glXWaitVideoSyncSGI(1, 0, &before);
|
||||
}
|
||||
else
|
||||
SDL_GL_SwapWindow(this->sdlWindow);
|
||||
|
||||
const uint64_t t = nanotime();
|
||||
this->renderTime += t - this->lastFrameTime;
|
||||
this->lastFrameTime = t;
|
||||
++this->renderCount;
|
||||
|
||||
this->frameUpdate = false;
|
||||
this->mouseUpdate = false;
|
||||
this->lastMouseDraw = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void handle_opt_mipmap(void * opaque, const char *value)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.mipmap = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_vsync(void * opaque, const char *value)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.vsync = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_prevent_buffer(void * opaque, const char *value)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.preventBuffer = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_split_mouse(void * opaque, const char *value)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.splitMouse = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static LG_RendererOpt lgr_opengl_basic_options[] =
|
||||
{
|
||||
{
|
||||
.name = "mipmap",
|
||||
.desc = "Enable or disable mipmapping [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_mipmap
|
||||
},
|
||||
{
|
||||
.name = "vsync",
|
||||
.desc ="Enable or disable vsync [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_vsync
|
||||
},
|
||||
{
|
||||
.name = "preventBuffer",
|
||||
.desc = "Prevent the driver from buffering frames [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_prevent_buffer
|
||||
},
|
||||
{
|
||||
.name = "splitMouse",
|
||||
.desc = "Draw mouse updates directly to the front buffer [default: disabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_split_mouse
|
||||
}
|
||||
};
|
||||
|
||||
const LG_Renderer LGR_OpenGLBasic =
|
||||
{
|
||||
.get_name = lgr_opengl_basic_get_name,
|
||||
.options = lgr_opengl_basic_options,
|
||||
.option_count = LGR_OPTION_COUNT(lgr_opengl_basic_options),
|
||||
.create = lgr_opengl_basic_create,
|
||||
.initialize = lgr_opengl_basic_initialize,
|
||||
.configure = lgr_opengl_basic_configure,
|
||||
.deconfigure = lgr_opengl_basic_deconfigure,
|
||||
.deinitialize = lgr_opengl_basic_deinitialize,
|
||||
.is_compatible = lgr_opengl_basic_is_compatible,
|
||||
.on_resize = lgr_opengl_basic_on_resize,
|
||||
.on_mouse_shape = lgr_opengl_basic_on_mouse_shape,
|
||||
.on_mouse_event = lgr_opengl_basic_on_mouse_event,
|
||||
.on_frame_event = lgr_opengl_basic_on_frame_event,
|
||||
.render = lgr_opengl_basic_render
|
||||
};
|
||||
@@ -1,951 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "lg-renderer.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glu.h>
|
||||
#include <GL/glx.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "memcpySSE.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define BUFFER_COUNT 2
|
||||
|
||||
#define FRAME_TEXTURE 0
|
||||
#define FPS_TEXTURE 1
|
||||
#define MOUSE_TEXTURE 2
|
||||
#define TEXTURE_COUNT 3
|
||||
|
||||
static PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI = NULL;
|
||||
static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI = NULL;
|
||||
|
||||
struct Options
|
||||
{
|
||||
bool mipmap;
|
||||
bool vsync;
|
||||
bool preventBuffer;
|
||||
};
|
||||
|
||||
static struct Options defaultOptions =
|
||||
{
|
||||
.mipmap = true,
|
||||
.vsync = true,
|
||||
.preventBuffer = true,
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererParams params;
|
||||
struct Options opt;
|
||||
|
||||
bool configured;
|
||||
bool reconfigure;
|
||||
SDL_GLContext glContext;
|
||||
bool doneInfo;
|
||||
|
||||
SDL_Point window;
|
||||
bool resizeWindow;
|
||||
bool frameUpdate;
|
||||
|
||||
LG_Lock formatLock;
|
||||
LG_RendererFormat format;
|
||||
GLuint intFormat;
|
||||
GLuint vboFormat;
|
||||
size_t texSize;
|
||||
|
||||
uint64_t drawStart;
|
||||
bool hasBuffers;
|
||||
GLuint vboID[1];
|
||||
uint8_t * texPixels[BUFFER_COUNT];
|
||||
LG_Lock syncLock;
|
||||
int texIndex, wTexIndex;
|
||||
int texList;
|
||||
int fpsList;
|
||||
int mouseList;
|
||||
LG_RendererRect destRect;
|
||||
|
||||
bool hasTextures;
|
||||
GLuint textures[TEXTURE_COUNT];
|
||||
|
||||
uint gpuFrameCount;
|
||||
bool fpsTexture;
|
||||
uint64_t lastFrameTime;
|
||||
uint64_t renderTime;
|
||||
uint64_t frameCount;
|
||||
uint64_t renderCount;
|
||||
SDL_Rect fpsRect;
|
||||
|
||||
LG_Lock mouseLock;
|
||||
LG_RendererCursor mouseCursor;
|
||||
int mouseWidth;
|
||||
int mouseHeight;
|
||||
int mousePitch;
|
||||
uint8_t * mouseData;
|
||||
size_t mouseDataSize;
|
||||
|
||||
bool mouseUpdate;
|
||||
bool newShape;
|
||||
uint64_t lastMouseDraw;
|
||||
LG_RendererCursor mouseType;
|
||||
bool mouseVisible;
|
||||
SDL_Rect mousePos;
|
||||
};
|
||||
|
||||
static bool _check_gl_error(unsigned int line, const char * name);
|
||||
#define check_gl_error(name) _check_gl_error(__LINE__, name)
|
||||
|
||||
static void deconfigure(struct Inst * this);
|
||||
static bool configure(struct Inst * this, SDL_Window *window);
|
||||
static void update_mouse_shape(struct Inst * this, bool * newShape);
|
||||
static bool draw_frame(struct Inst * this, bool * frameUpdate);
|
||||
static void draw_mouse(struct Inst * this);
|
||||
|
||||
const char * opengl_get_name()
|
||||
{
|
||||
return "OpenGL";
|
||||
}
|
||||
|
||||
bool opengl_create(void ** opaque, const LG_RendererParams params)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
memcpy(&this->params, ¶ms , sizeof(LG_RendererParams));
|
||||
memcpy(&this->opt , &defaultOptions, sizeof(struct Options ));
|
||||
|
||||
LG_LOCK_INIT(this->formatLock);
|
||||
LG_LOCK_INIT(this->syncLock );
|
||||
LG_LOCK_INIT(this->mouseLock );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (!glXGetVideoSyncSGI)
|
||||
{
|
||||
glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC )glXGetProcAddress((const GLubyte *)"glXGetVideoSyncSGI" );
|
||||
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddress((const GLubyte *)"glXWaitVideoSyncSGI");
|
||||
|
||||
if (!glXGetVideoSyncSGI || !glXWaitVideoSyncSGI)
|
||||
{
|
||||
glXGetVideoSyncSGI = NULL;
|
||||
DEBUG_ERROR("Failed to get proc addresses");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
deconfigure(this);
|
||||
if (this->mouseData)
|
||||
free(this->mouseData);
|
||||
|
||||
LG_LOCK_FREE(this->formatLock);
|
||||
LG_LOCK_FREE(this->syncLock );
|
||||
LG_LOCK_FREE(this->mouseLock );
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->window.x = width;
|
||||
this->window.y = height;
|
||||
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
|
||||
|
||||
this->resizeWindow = true;
|
||||
}
|
||||
|
||||
bool opengl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
LG_LOCK(this->mouseLock);
|
||||
this->mouseCursor = cursor;
|
||||
this->mouseWidth = width;
|
||||
this->mouseHeight = height;
|
||||
this->mousePitch = pitch;
|
||||
|
||||
const size_t size = height * pitch;
|
||||
if (size > this->mouseDataSize)
|
||||
{
|
||||
if (this->mouseData)
|
||||
free(this->mouseData);
|
||||
this->mouseData = (uint8_t *)malloc(size);
|
||||
this->mouseDataSize = size;
|
||||
}
|
||||
|
||||
memcpy(this->mouseData, data, size);
|
||||
this->newShape = true;
|
||||
LG_UNLOCK(this->mouseLock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (this->mousePos.x == x && this->mousePos.y == y && this->mouseVisible == visible)
|
||||
return true;
|
||||
|
||||
this->mouseVisible = visible;
|
||||
this->mousePos.x = x;
|
||||
this->mousePos.y = y;
|
||||
this->mouseUpdate = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Invalid opaque pointer");
|
||||
return false;
|
||||
}
|
||||
|
||||
LG_LOCK(this->formatLock);
|
||||
if (this->reconfigure)
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this->configured || memcmp(&this->format, &format, sizeof(LG_RendererFormat)) != 0)
|
||||
{
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->reconfigure = true;
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
LG_UNLOCK(this->formatLock);
|
||||
|
||||
// lock, perform the update, then unlock
|
||||
LG_LOCK(this->syncLock);
|
||||
memcpySSE(this->texPixels[this->wTexIndex], data, this->texSize);
|
||||
this->frameUpdate = true;
|
||||
LG_UNLOCK(this->syncLock);
|
||||
|
||||
++this->frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_render(void * opaque, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (!configure(this, window))
|
||||
return true;
|
||||
|
||||
if (this->resizeWindow)
|
||||
{
|
||||
// setup the projection matrix
|
||||
glViewport(0, 0, this->window.x, this->window.y);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluOrtho2D(0, this->window.x, this->window.y, 0);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glTranslatef(this->destRect.x, this->destRect.y, 0.0f);
|
||||
glScalef(
|
||||
(float)this->destRect.w / (float)this->format.width,
|
||||
(float)this->destRect.h / (float)this->format.height,
|
||||
1.0f
|
||||
);
|
||||
|
||||
// update the scissor rect to prevent drawing outside of the frame
|
||||
glScissor(
|
||||
this->destRect.x,
|
||||
this->destRect.y,
|
||||
this->destRect.w,
|
||||
this->destRect.h
|
||||
);
|
||||
|
||||
this->resizeWindow = false;
|
||||
}
|
||||
|
||||
if (this->params.showFPS && this->renderTime > 1e9)
|
||||
{
|
||||
char str[128];
|
||||
const float avgFPS = 1000.0f / (((float)this->renderTime / this->frameCount ) / 1e6f);
|
||||
const float renderFPS = 1000.0f / (((float)this->renderTime / this->renderCount) / 1e6f);
|
||||
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
|
||||
SDL_Color color = {0xff, 0xff, 0xff};
|
||||
SDL_Surface *textSurface = NULL;
|
||||
if (!(textSurface = TTF_RenderText_Blended(this->params.font, str, color)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to render text");
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D , this->textures[FPS_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, textSurface->w );
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
textSurface->format->BytesPerPixel,
|
||||
textSurface->w,
|
||||
textSurface->h,
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
textSurface->pixels
|
||||
);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->fpsRect.x = 5;
|
||||
this->fpsRect.y = 5;
|
||||
this->fpsRect.w = textSurface->w;
|
||||
this->fpsRect.h = textSurface->h;
|
||||
|
||||
SDL_FreeSurface(textSurface);
|
||||
|
||||
this->renderTime = 0;
|
||||
this->frameCount = 0;
|
||||
this->renderCount = 0;
|
||||
this->fpsTexture = true;
|
||||
|
||||
glNewList(this->fpsList, GL_COMPILE);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glVertex2i(this->fpsRect.x , this->fpsRect.y );
|
||||
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
|
||||
glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
|
||||
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
|
||||
glEnd();
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FPS_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f , 0.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y );
|
||||
glTexCoord2f(1.0f , 0.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
|
||||
glTexCoord2f(0.0f , 1.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
|
||||
glEnd();
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
glPopMatrix();
|
||||
glEndList();
|
||||
}
|
||||
|
||||
bool frameUpdate;
|
||||
if (!draw_frame(this, &frameUpdate))
|
||||
return false;
|
||||
|
||||
bool newShape;
|
||||
update_mouse_shape(this, &newShape);
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
glCallList(this->texList + this->texIndex);
|
||||
draw_mouse(this);
|
||||
if (this->fpsTexture)
|
||||
glCallList(this->fpsList);
|
||||
|
||||
if (this->opt.preventBuffer)
|
||||
{
|
||||
unsigned int before, after;
|
||||
glXGetVideoSyncSGI(&before);
|
||||
SDL_GL_SwapWindow(window);
|
||||
|
||||
// wait for the swap to happen to ensure we dont buffer frames //
|
||||
glXGetVideoSyncSGI(&after);
|
||||
if (before == after)
|
||||
glXWaitVideoSyncSGI(1, 0, &before);
|
||||
}
|
||||
else
|
||||
SDL_GL_SwapWindow(window);
|
||||
|
||||
const uint64_t t = nanotime();
|
||||
this->renderTime += t - this->lastFrameTime;
|
||||
this->lastFrameTime = t;
|
||||
++this->renderCount;
|
||||
|
||||
this->mouseUpdate = false;
|
||||
this->lastMouseDraw = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void handle_opt_mipmap(void * opaque, const char *value)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.mipmap = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_vsync(void * opaque, const char *value)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.vsync = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_prevent_buffer(void * opaque, const char *value)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.preventBuffer = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static LG_RendererOpt opengl_options[] =
|
||||
{
|
||||
{
|
||||
.name = "mipmap",
|
||||
.desc = "Enable or disable mipmapping [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_mipmap
|
||||
},
|
||||
{
|
||||
.name = "vsync",
|
||||
.desc ="Enable or disable vsync [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_vsync
|
||||
},
|
||||
{
|
||||
.name = "preventBuffer",
|
||||
.desc = "Prevent the driver from buffering frames [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_prevent_buffer
|
||||
}
|
||||
};
|
||||
|
||||
const LG_Renderer LGR_OpenGL =
|
||||
{
|
||||
.get_name = opengl_get_name,
|
||||
.options = opengl_options,
|
||||
.option_count = LGR_OPTION_COUNT(opengl_options),
|
||||
.create = opengl_create,
|
||||
.initialize = opengl_initialize,
|
||||
.deinitialize = opengl_deinitialize,
|
||||
.on_resize = opengl_on_resize,
|
||||
.on_mouse_shape = opengl_on_mouse_shape,
|
||||
.on_mouse_event = opengl_on_mouse_event,
|
||||
.on_frame_event = opengl_on_frame_event,
|
||||
.render = opengl_render
|
||||
};
|
||||
|
||||
static bool _check_gl_error(unsigned int line, const char * name)
|
||||
{
|
||||
GLenum error = glGetError();
|
||||
if (error == GL_NO_ERROR)
|
||||
return false;
|
||||
|
||||
const GLubyte * errStr = gluErrorString(error);
|
||||
DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool configure(struct Inst * this, SDL_Window *window)
|
||||
{
|
||||
LG_LOCK(this->formatLock);
|
||||
if (!this->reconfigure)
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return this->configured;
|
||||
}
|
||||
|
||||
if (this->configured)
|
||||
deconfigure(this);
|
||||
|
||||
this->glContext = SDL_GL_CreateContext(window);
|
||||
if (!this->glContext)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the OpenGL context");
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->doneInfo)
|
||||
{
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
this->doneInfo = true;
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0);
|
||||
|
||||
// check if the GPU supports GL_ARB_buffer_storage first
|
||||
// there is no advantage to this renderer if it is not present.
|
||||
const GLubyte * extensions = glGetString(GL_EXTENSIONS);
|
||||
if (!gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions))
|
||||
{
|
||||
DEBUG_INFO("The GPU doesn't support GL_ARB_buffer_storage");
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
// assume 24 and 32 bit formats are RGB and RGBA
|
||||
switch(this->format.bpp)
|
||||
{
|
||||
case 24:
|
||||
this->intFormat = GL_RGB8;
|
||||
this->vboFormat = GL_BGR;
|
||||
break;
|
||||
|
||||
case 32:
|
||||
this->intFormat = GL_RGBA8;
|
||||
this->vboFormat = GL_BGRA;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_INFO("%d bpp not supported", this->format.bpp);
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate the texture size in bytes
|
||||
this->texSize = this->format.height * this->format.pitch;
|
||||
|
||||
// generate lists for drawing
|
||||
this->texList = glGenLists(BUFFER_COUNT);
|
||||
this->fpsList = glGenLists(1);
|
||||
this->mouseList = glGenLists(1);
|
||||
|
||||
// generate the pixel unpack buffers
|
||||
glGenBuffers(1, this->vboID);
|
||||
if (check_gl_error("glGenBuffers"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
this->hasBuffers = true;
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[0]);
|
||||
if (check_gl_error("glBindBuffer"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
glBufferStorage(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
this->texSize * BUFFER_COUNT,
|
||||
NULL,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_PERSISTENT_BIT
|
||||
);
|
||||
if (check_gl_error("glBufferStorage"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->texPixels[0] = glMapBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
this->texSize * BUFFER_COUNT,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_PERSISTENT_BIT |
|
||||
GL_MAP_FLUSH_EXPLICIT_BIT
|
||||
);
|
||||
|
||||
if (check_gl_error("glMapBufferRange"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 1; i < BUFFER_COUNT; ++i)
|
||||
this->texPixels[i] = this->texPixels[i-1] + this->texSize;
|
||||
|
||||
// create the textures
|
||||
glGenTextures(TEXTURE_COUNT, this->textures);
|
||||
if (check_gl_error("glGenTextures"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
this->hasTextures = true;
|
||||
|
||||
// create the frame texture
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
|
||||
if (check_gl_error("glBindTexture"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
this->intFormat,
|
||||
this->format.width,
|
||||
this->format.height * BUFFER_COUNT,
|
||||
0,
|
||||
this->vboFormat,
|
||||
GL_UNSIGNED_BYTE,
|
||||
(void*)0
|
||||
);
|
||||
if (check_gl_error("glTexImage2D"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
// configure the texture
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
for (int i = 0; i < BUFFER_COUNT; ++i)
|
||||
{
|
||||
const float ts = (1.0f / BUFFER_COUNT) * i;
|
||||
const float te = (1.0f / BUFFER_COUNT) + ts;
|
||||
|
||||
// create the display lists
|
||||
glNewList(this->texList + i, GL_COMPILE);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, ts); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, ts); glVertex2i(this->format.width, 0 );
|
||||
glTexCoord2f(0.0f, te); glVertex2i(0 , this->format.height);
|
||||
glTexCoord2f(1.0f, te); glVertex2i(this->format.width, this->format.height);
|
||||
glEnd();
|
||||
glEndList();
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_COLOR_MATERIAL);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
this->resizeWindow = true;
|
||||
this->drawStart = nanotime();
|
||||
glXGetVideoSyncSGI(&this->gpuFrameCount);
|
||||
|
||||
this->configured = true;
|
||||
this->reconfigure = false;
|
||||
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void deconfigure(struct Inst * this)
|
||||
{
|
||||
if (!this->configured)
|
||||
return;
|
||||
|
||||
if (this->hasTextures)
|
||||
{
|
||||
glDeleteTextures(TEXTURE_COUNT, this->textures);
|
||||
this->hasTextures = false;
|
||||
}
|
||||
|
||||
if (this->hasBuffers)
|
||||
{
|
||||
glDeleteBuffers(1, this->vboID);
|
||||
this->hasBuffers = false;
|
||||
}
|
||||
|
||||
if (this->glContext)
|
||||
{
|
||||
SDL_GL_DeleteContext(this->glContext);
|
||||
this->glContext = NULL;
|
||||
}
|
||||
|
||||
this->configured = false;
|
||||
}
|
||||
|
||||
static void update_mouse_shape(struct Inst * this, bool * newShape)
|
||||
{
|
||||
LG_LOCK(this->mouseLock);
|
||||
*newShape = this->newShape;
|
||||
if (!this->newShape)
|
||||
{
|
||||
LG_UNLOCK(this->mouseLock);
|
||||
return;
|
||||
}
|
||||
|
||||
const LG_RendererCursor cursor = this->mouseCursor;
|
||||
const int width = this->mouseWidth;
|
||||
const int height = this->mouseHeight;
|
||||
const int pitch = this->mousePitch;
|
||||
const uint8_t * data = this->mouseData;
|
||||
|
||||
this->mouseType = cursor;
|
||||
switch(cursor)
|
||||
{
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glTexImage2D
|
||||
(
|
||||
GL_TEXTURE_2D,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
width ,
|
||||
height ,
|
||||
0 ,
|
||||
GL_BGRA, // windows cursors are in BGRA format
|
||||
GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->mousePos.w = width;
|
||||
this->mousePos.h = height;
|
||||
|
||||
glNewList(this->mouseList, GL_COMPILE);
|
||||
glEnable(GL_BLEND);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , height);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, height);
|
||||
glEnd();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_BLEND);
|
||||
glEndList();
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
const int hheight = height / 2;
|
||||
uint32_t d[width * height];
|
||||
for(int y = 0; y < hheight; ++y)
|
||||
for(int x = 0; x < width; ++x)
|
||||
{
|
||||
const uint8_t * srcAnd = data + (pitch * y) + (x / 8);
|
||||
const uint8_t * srcXor = srcAnd + pitch * hheight;
|
||||
const uint8_t mask = 0x80 >> (x % 8);
|
||||
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
|
||||
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
|
||||
|
||||
d[y * width + x ] = andMask;
|
||||
d[y * width + x + width * hheight] = xorMask;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glTexImage2D
|
||||
(
|
||||
GL_TEXTURE_2D,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
width ,
|
||||
height ,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
d
|
||||
);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->mousePos.w = width;
|
||||
this->mousePos.h = hheight;
|
||||
|
||||
glNewList(this->mouseList, GL_COMPILE);
|
||||
glEnable(GL_COLOR_LOGIC_OP);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glLogicOp(GL_AND);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , hheight);
|
||||
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, hheight);
|
||||
glEnd();
|
||||
glLogicOp(GL_XOR);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , hheight);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, hheight);
|
||||
glEnd();
|
||||
glDisable(GL_COLOR_LOGIC_OP);
|
||||
glEndList();
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
//TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this->mouseUpdate = true;
|
||||
LG_UNLOCK(this->mouseLock);
|
||||
}
|
||||
|
||||
static bool draw_frame(struct Inst * this, bool * frameUpdate)
|
||||
{
|
||||
LG_LOCK(this->syncLock);
|
||||
*frameUpdate = this->frameUpdate;
|
||||
if (!this->frameUpdate)
|
||||
{
|
||||
LG_UNLOCK(this->syncLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
this->texIndex = this->wTexIndex;
|
||||
if (++this->wTexIndex == BUFFER_COUNT)
|
||||
this->wTexIndex = 0;
|
||||
this->frameUpdate = false;
|
||||
LG_UNLOCK(this->syncLock);
|
||||
|
||||
LG_LOCK(this->formatLock);
|
||||
|
||||
// bind the texture and update it
|
||||
glBindTexture(GL_TEXTURE_2D , this->textures[FRAME_TEXTURE]);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[0] );
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride );
|
||||
|
||||
// copy the buffer to the texture
|
||||
glFlushMappedBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
this->texSize * this->texIndex,
|
||||
this->texSize
|
||||
);
|
||||
|
||||
// update the texture
|
||||
glTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
this->texIndex * this->format.height,
|
||||
this->format.width ,
|
||||
this->format.height,
|
||||
this->vboFormat,
|
||||
GL_UNSIGNED_BYTE,
|
||||
(void*)(this->texIndex * this->texSize)
|
||||
);
|
||||
if (check_gl_error("glTexSubImage2D"))
|
||||
DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
|
||||
this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
|
||||
);
|
||||
|
||||
// unbind the buffer
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
const bool mipmap = this->opt.mipmap && (
|
||||
(this->format.width > this->destRect.w) ||
|
||||
(this->format.height > this->destRect.h));
|
||||
|
||||
if (mipmap)
|
||||
{
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void draw_mouse(struct Inst * this)
|
||||
{
|
||||
if (!this->mouseVisible)
|
||||
return;
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
|
||||
glCallList(this->mouseList);
|
||||
glPopMatrix();
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <stdint.h>
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
typedef struct SpicePoint16
|
||||
{
|
||||
int16_t x, y;
|
||||
}
|
||||
SpicePoint16;
|
||||
|
||||
typedef struct SpiceMsgMainInit
|
||||
{
|
||||
uint32_t session_id;
|
||||
uint32_t display_channels_hint;
|
||||
uint32_t supported_mouse_modes;
|
||||
uint32_t current_mouse_mode;
|
||||
uint32_t agent_connected;
|
||||
uint32_t agent_tokens;
|
||||
uint32_t multi_media_time;
|
||||
uint32_t ram_hint;
|
||||
}
|
||||
SpiceMsgMainInit;
|
||||
|
||||
typedef struct SpiceMsgcMainMouseModeRequest
|
||||
{
|
||||
uint16_t mouse_mode;
|
||||
}
|
||||
SpiceMsgcMainMouseModeRequest;
|
||||
|
||||
typedef struct SpiceMsgPing
|
||||
{
|
||||
uint32_t id;
|
||||
uint64_t timestamp;
|
||||
}
|
||||
SpiceMsgPing,
|
||||
SpiceMsgcPong;
|
||||
|
||||
typedef struct SpiceMsgSetAck
|
||||
{
|
||||
uint32_t generation;
|
||||
uint32_t window;
|
||||
}
|
||||
SpiceMsgSetAck;
|
||||
|
||||
typedef struct SpiceMsgcAckSync
|
||||
{
|
||||
uint32_t generation;
|
||||
}
|
||||
SpiceMsgcAckSync;
|
||||
|
||||
typedef struct SpiceMsgNotify
|
||||
{
|
||||
uint64_t time_stamp;
|
||||
uint32_t severity;
|
||||
uint32_t visibility;
|
||||
uint32_t what;
|
||||
uint32_t message_len;
|
||||
//char message[message_len+1]
|
||||
}
|
||||
SpiceMsgNotify;
|
||||
|
||||
typedef struct SpiceMsgInputsInit
|
||||
{
|
||||
uint16_t modifiers;
|
||||
}
|
||||
SpiceMsgInputsInit,
|
||||
SpiceMsgInputsKeyModifiers,
|
||||
SpiceMsgcInputsKeyModifiers;
|
||||
|
||||
typedef struct SpiceMsgcKeyDown
|
||||
{
|
||||
uint32_t code;
|
||||
}
|
||||
SpiceMsgcKeyDown,
|
||||
SpiceMsgcKeyUp;
|
||||
|
||||
typedef struct SpiceMsgcMousePosition
|
||||
{
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint16_t button_state;
|
||||
uint8_t display_id;
|
||||
}
|
||||
SpiceMsgcMousePosition;
|
||||
|
||||
typedef struct SpiceMsgcMouseMotion
|
||||
{
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint16_t button_state;
|
||||
}
|
||||
SpiceMsgcMouseMotion;
|
||||
|
||||
typedef struct SpiceMsgcMousePress
|
||||
{
|
||||
uint8_t button;
|
||||
uint16_t button_state;
|
||||
}
|
||||
SpiceMsgcMousePress,
|
||||
SpiceMsgcMouseRelease;
|
||||
|
||||
#pragma pack(pop)
|
||||
@@ -1,900 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "spice.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <spice/protocol.h>
|
||||
#include <spice/error_codes.h>
|
||||
|
||||
#include "messages.h"
|
||||
|
||||
#ifdef DEBUG_SPICE_MOUSE
|
||||
#define DEBUG_MOUSE(fmt, args...) DEBUG_PRINT("[M]", fmt, ##args)
|
||||
#else
|
||||
#define DEBUG_MOUSE(fmt, args...) do {} while(0)
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_SPICE_KEYBOARD
|
||||
#define DEBUG_KEYBOARD(fmt, args...) DEBUG_PRINT("[K]", fmt, ##args)
|
||||
#else
|
||||
#define DEBUG_KEYBOARD(fmt, args...) do {} while(0)
|
||||
#endif
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// internal structures
|
||||
struct SpiceChannel
|
||||
{
|
||||
bool connected;
|
||||
bool initDone;
|
||||
uint8_t channelType;
|
||||
int socket;
|
||||
uint32_t ackFrequency;
|
||||
uint32_t ackCount;
|
||||
uint32_t serial;
|
||||
};
|
||||
|
||||
struct SpiceKeyboard
|
||||
{
|
||||
uint32_t modifiers;
|
||||
};
|
||||
|
||||
#define SPICE_MOUSE_QUEUE_SIZE 32
|
||||
|
||||
struct SpiceMouse
|
||||
{
|
||||
uint32_t buttonState;
|
||||
|
||||
int sentCount;
|
||||
SpiceMsgcMouseMotion queue[SPICE_MOUSE_QUEUE_SIZE];
|
||||
int rpos, wpos;
|
||||
int queueLen;
|
||||
};
|
||||
|
||||
struct Spice
|
||||
{
|
||||
char password[32];
|
||||
struct sockaddr_in addr;
|
||||
|
||||
uint32_t sessionID;
|
||||
uint32_t channelID;
|
||||
struct SpiceChannel scMain;
|
||||
struct SpiceChannel scInputs;
|
||||
|
||||
struct SpiceKeyboard kb;
|
||||
struct SpiceMouse mouse;
|
||||
};
|
||||
|
||||
// globals
|
||||
struct Spice spice =
|
||||
{
|
||||
.sessionID = 0,
|
||||
.scMain .connected = false,
|
||||
.scMain .channelType = SPICE_CHANNEL_MAIN,
|
||||
.scInputs.connected = false,
|
||||
.scInputs.channelType = SPICE_CHANNEL_INPUTS,
|
||||
};
|
||||
|
||||
// internal forward decls
|
||||
bool spice_connect_channel (struct SpiceChannel * channel);
|
||||
void spice_disconnect_channel(struct SpiceChannel * channel);
|
||||
|
||||
bool spice_process_ack(struct SpiceChannel * channel);
|
||||
|
||||
bool spice_on_common_read (struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled);
|
||||
bool spice_on_main_channel_read ();
|
||||
bool spice_on_inputs_channel_read();
|
||||
|
||||
bool spice_read (const struct SpiceChannel * channel, void * buffer, const ssize_t size);
|
||||
ssize_t spice_write (const struct SpiceChannel * channel, const void * buffer, const ssize_t size);
|
||||
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size);
|
||||
bool spice_discard (const struct SpiceChannel * channel, ssize_t size);
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_connect(const char * host, const short port, const char * password)
|
||||
{
|
||||
strncpy(spice.password, password, sizeof(spice.password));
|
||||
memset(&spice.addr, 0, sizeof(struct sockaddr_in));
|
||||
inet_pton(AF_INET, host, &spice.addr.sin_addr);
|
||||
spice.addr.sin_family = AF_INET;
|
||||
spice.addr.sin_port = htons(port);
|
||||
|
||||
spice.channelID = 0;
|
||||
if (!spice_connect_channel(&spice.scMain))
|
||||
{
|
||||
DEBUG_ERROR("connect main channel failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void spice_disconnect()
|
||||
{
|
||||
spice_disconnect_channel(&spice.scMain );
|
||||
spice_disconnect_channel(&spice.scInputs);
|
||||
|
||||
spice.sessionID = 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_ready()
|
||||
{
|
||||
return spice.scMain.connected &&
|
||||
spice.scInputs.connected;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_process()
|
||||
{
|
||||
fd_set readSet;
|
||||
FD_ZERO(&readSet);
|
||||
FD_SET(spice.scMain.socket , &readSet);
|
||||
FD_SET(spice.scInputs.socket, &readSet);
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
int rc = select(FD_SETSIZE, &readSet, NULL, NULL, &timeout);
|
||||
if (rc < 0)
|
||||
{
|
||||
DEBUG_ERROR("select failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < FD_SETSIZE; ++i)
|
||||
if (FD_ISSET(i, &readSet))
|
||||
{
|
||||
if (i == spice.scMain.socket)
|
||||
{
|
||||
if (spice_on_main_channel_read())
|
||||
{
|
||||
if (spice.scMain.connected && !spice_process_ack(&spice.scMain))
|
||||
{
|
||||
DEBUG_ERROR("failed to process ack on main channel");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("failed to perform read on main channel");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (spice.scInputs.connected && i == spice.scInputs.socket)
|
||||
{
|
||||
if (spice_on_inputs_channel_read())
|
||||
{
|
||||
if (!spice_process_ack(&spice.scInputs))
|
||||
{
|
||||
DEBUG_ERROR("failed to process ack on inputs channel");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("failed to perform read on inputs channel");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_process_ack(struct SpiceChannel * channel)
|
||||
{
|
||||
if (channel->ackFrequency == 0)
|
||||
return true;
|
||||
|
||||
if (channel->ackCount++ != channel->ackFrequency)
|
||||
return true;
|
||||
|
||||
channel->ackCount = 0;
|
||||
return spice_write_msg(channel, SPICE_MSGC_ACK, "\0", 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_common_read(struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled)
|
||||
{
|
||||
if (!spice_read(channel, header, sizeof(SpiceDataHeader)))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
*handled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
printf("socket: %d, serial: %6u, type: %2u, size %6u, sub_list %4u\n",
|
||||
channel->socket,
|
||||
header->serial, header->type, header->size, header->sub_list);
|
||||
#endif
|
||||
|
||||
if (!channel->initDone)
|
||||
{
|
||||
*handled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
switch(header->type)
|
||||
{
|
||||
case SPICE_MSG_MIGRATE:
|
||||
case SPICE_MSG_MIGRATE_DATA:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_MIGRATE_DATA");
|
||||
|
||||
*handled = true;
|
||||
DEBUG_WARN("migration is not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
case SPICE_MSG_SET_ACK:
|
||||
{
|
||||
DEBUG_INFO("SPICE_MSG_SET_ACK");
|
||||
*handled = true;
|
||||
|
||||
SpiceMsgSetAck in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
channel->ackFrequency = in.window;
|
||||
|
||||
SpiceMsgcAckSync out;
|
||||
out.generation = in.generation;
|
||||
if (!spice_write_msg(channel, SPICE_MSGC_ACK_SYNC, &out, sizeof(out)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_PING:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_PING");
|
||||
*handled = true;
|
||||
|
||||
SpiceMsgPing in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
if (!spice_discard(channel, header->size - sizeof(in)))
|
||||
{
|
||||
DEBUG_ERROR("failed discarding enough bytes from the ping packet");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcPong out;
|
||||
out.id = in.id;
|
||||
out.timestamp = in.timestamp;
|
||||
if (!spice_write_msg(channel, SPICE_MSGC_PONG, &out, sizeof(out)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_WAIT_FOR_CHANNELS:
|
||||
case SPICE_MSG_DISCONNECTING :
|
||||
{
|
||||
*handled = true;
|
||||
DEBUG_FIXME("ignored wait-for-channels or disconnect message");
|
||||
return false;
|
||||
}
|
||||
|
||||
case SPICE_MSG_NOTIFY:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_NOTIFY");
|
||||
SpiceMsgNotify in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
char msg[in.message_len+1];
|
||||
if (!spice_read(channel, msg, in.message_len+1))
|
||||
return false;
|
||||
|
||||
DEBUG_INFO("notify message: %s", msg);
|
||||
*handled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
*handled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_main_channel_read()
|
||||
{
|
||||
struct SpiceChannel *channel = &spice.scMain;
|
||||
|
||||
SpiceDataHeader header;
|
||||
bool handled;
|
||||
|
||||
if (!spice_on_common_read(channel, &header, &handled))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handled)
|
||||
return true;
|
||||
|
||||
if (!channel->initDone)
|
||||
{
|
||||
if (header.type != SPICE_MSG_MAIN_INIT)
|
||||
{
|
||||
spice_disconnect();
|
||||
DEBUG_ERROR("expected main init message but got type %u", header.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_PROTO("SPICE_MSG_MAIN_INIT");
|
||||
|
||||
channel->initDone = true;
|
||||
SpiceMsgMainInit msg;
|
||||
if (!spice_read(channel, &msg, sizeof(msg)))
|
||||
{
|
||||
spice_disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
spice.sessionID = msg.session_id;
|
||||
if (!spice_connect_channel(&spice.scInputs))
|
||||
{
|
||||
DEBUG_ERROR("failed to connect inputs channel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && !spice_mouse_mode(false))
|
||||
{
|
||||
DEBUG_ERROR("failed to set mouse mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DEBUG_WARN("main channel unhandled message type %u", header.type);
|
||||
spice_discard(channel, header.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_inputs_channel_read()
|
||||
{
|
||||
struct SpiceChannel *channel = &spice.scInputs;
|
||||
|
||||
SpiceDataHeader header;
|
||||
bool handled;
|
||||
|
||||
if (!spice_on_common_read(channel, &header, &handled))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handled)
|
||||
return true;
|
||||
|
||||
switch(header.type)
|
||||
{
|
||||
case SPICE_MSG_INPUTS_INIT:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_INIT");
|
||||
|
||||
if (channel->initDone)
|
||||
{
|
||||
DEBUG_ERROR("input init message already done");
|
||||
return false;
|
||||
}
|
||||
|
||||
channel->initDone = true;
|
||||
|
||||
SpiceMsgInputsInit in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_INPUTS_KEY_MODIFIERS:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_KEY_MODIFIERS");
|
||||
SpiceMsgInputsInit in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
spice.kb.modifiers = in.modifiers;
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_INPUTS_MOUSE_MOTION_ACK:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_MOUSE_MOTION_ACK");
|
||||
int sent = 0;
|
||||
while(spice.mouse.queueLen && sent < 4)
|
||||
{
|
||||
SpiceMsgcMouseMotion *msg = &spice.mouse.queue[spice.mouse.rpos];
|
||||
if (!spice_write_msg(channel, SPICE_MSGC_INPUTS_MOUSE_MOTION, msg, sizeof(SpiceMsgcMouseMotion)))
|
||||
{
|
||||
DEBUG_ERROR("failed to send post ack");
|
||||
spice.mouse.sentCount = sent;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (++spice.mouse.rpos == SPICE_MOUSE_QUEUE_SIZE)
|
||||
spice.mouse.rpos = 0;
|
||||
|
||||
++sent;
|
||||
--spice.mouse.queueLen;
|
||||
}
|
||||
|
||||
spice.mouse.sentCount = sent;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_WARN("inputs channel unhandled message type %u", header.type);
|
||||
spice_discard(channel, header.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_connect_channel(struct SpiceChannel * channel)
|
||||
{
|
||||
channel->initDone = false;
|
||||
channel->ackFrequency = 0;
|
||||
channel->ackCount = 0;
|
||||
channel->serial = 0;
|
||||
|
||||
channel->socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (channel->socket == -1)
|
||||
return false;
|
||||
|
||||
int flag = 1;
|
||||
setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
|
||||
|
||||
if (connect(channel->socket, (const struct sockaddr *)&spice.addr, sizeof(spice.addr)) == -1)
|
||||
{
|
||||
DEBUG_ERROR("socket connect failure");
|
||||
close(channel->socket);
|
||||
return false;
|
||||
}
|
||||
channel->connected = true;
|
||||
|
||||
SpiceLinkHeader header =
|
||||
{
|
||||
.magic = SPICE_MAGIC ,
|
||||
.major_version = SPICE_VERSION_MAJOR,
|
||||
.minor_version = SPICE_VERSION_MINOR,
|
||||
.size = sizeof(SpiceLinkMess)
|
||||
};
|
||||
|
||||
SpiceLinkMess message =
|
||||
{
|
||||
.connection_id = spice.sessionID,
|
||||
.channel_type = channel->channelType,
|
||||
.channel_id = spice.channelID,
|
||||
.num_common_caps = 0,
|
||||
.num_channel_caps = 0,
|
||||
.caps_offset = sizeof(SpiceLinkMess)
|
||||
};
|
||||
|
||||
spice_write(channel, &header , sizeof(header ));
|
||||
spice_write(channel, &message, sizeof(message));
|
||||
|
||||
if (!spice_read(channel, &header, sizeof(header)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkHeader");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.magic != SPICE_MAGIC || header.major_version != SPICE_VERSION_MAJOR)
|
||||
{
|
||||
DEBUG_ERROR("invalid or unsupported protocol version");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.size < sizeof(SpiceLinkReply))
|
||||
{
|
||||
DEBUG_ERROR("reported data size too small");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceLinkReply reply;
|
||||
if (!spice_read(channel, &reply, sizeof(reply)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkReply");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reply.error != SPICEC_ERROR_CODE_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("server replied with error %u", reply.error);
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t capsCommon [reply.num_common_caps ];
|
||||
uint32_t capsChannel[reply.num_channel_caps];
|
||||
spice_read(channel, &capsCommon , sizeof(capsCommon ));
|
||||
spice_read(channel, &capsChannel, sizeof(capsChannel));
|
||||
|
||||
BIO *bioKey = BIO_new(BIO_s_mem());
|
||||
if (!bioKey)
|
||||
{
|
||||
DEBUG_ERROR("failed to allocate bioKey");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO_write(bioKey, reply.pub_key, SPICE_TICKET_PUBKEY_BYTES);
|
||||
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
|
||||
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
|
||||
|
||||
char enc[RSA_size(rsa)];
|
||||
if (RSA_public_encrypt(
|
||||
strlen(spice.password) + 1,
|
||||
(uint8_t*)spice.password,
|
||||
(uint8_t*)enc,
|
||||
rsa,
|
||||
RSA_PKCS1_OAEP_PADDING
|
||||
) <= 0)
|
||||
{
|
||||
DEBUG_ERROR("rsa public encrypt failed");
|
||||
spice_disconnect_channel(channel);
|
||||
EVP_PKEY_free(rsaKey);
|
||||
BIO_free(bioKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t rsaSize = RSA_size(rsa);
|
||||
EVP_PKEY_free(rsaKey);
|
||||
BIO_free(bioKey);
|
||||
|
||||
if (!spice_write(channel, enc, rsaSize))
|
||||
{
|
||||
DEBUG_ERROR("failed to write encrypted data");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t linkResult;
|
||||
if (!spice_read(channel, &linkResult, sizeof(linkResult)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkResult");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (linkResult != SPICE_LINK_ERR_OK)
|
||||
{
|
||||
DEBUG_ERROR("connect code error %u", linkResult);
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void spice_disconnect_channel(struct SpiceChannel * channel)
|
||||
{
|
||||
if (channel->connected)
|
||||
close(channel->socket);
|
||||
channel->connected = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
ssize_t spice_write(const struct SpiceChannel * channel, const void * buffer, const ssize_t size)
|
||||
{
|
||||
if (!channel->connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
DEBUG_ERROR("invalid buffer argument supplied");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t len = send(channel->socket, buffer, size, 0);
|
||||
if (len != size)
|
||||
DEBUG_WARN("incomplete write");
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size)
|
||||
{
|
||||
SpiceDataHeader header;
|
||||
header.serial = channel->serial++;
|
||||
header.type = type;
|
||||
header.size = size;
|
||||
header.sub_list = 0;
|
||||
|
||||
if (spice_write(channel, &header, sizeof(header)) != sizeof(header))
|
||||
{
|
||||
DEBUG_ERROR("failed to write message header");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spice_write(channel, buffer, size) != size)
|
||||
{
|
||||
DEBUG_ERROR("failed to write message body");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_read(const struct SpiceChannel * channel, void * buffer, const ssize_t size)
|
||||
{
|
||||
if (!channel->connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
DEBUG_ERROR("invalid buffer argument supplied");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t len = read(channel->socket, buffer, size);
|
||||
if (len != size)
|
||||
{
|
||||
DEBUG_ERROR("incomplete write");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_discard(const struct SpiceChannel * channel, ssize_t size)
|
||||
{
|
||||
while(size)
|
||||
{
|
||||
char c[8192];
|
||||
size_t len = read(channel->socket, c, size > sizeof(c) ? sizeof(c) : size);
|
||||
if (len <= 0)
|
||||
return false;
|
||||
|
||||
size -= len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_key_down(uint32_t code)
|
||||
{
|
||||
DEBUG_KEYBOARD("%u", code);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code > 0x100)
|
||||
code = 0xe0 | ((code - 0x100) << 8);
|
||||
|
||||
SpiceMsgcKeyDown msg;
|
||||
msg.code = code;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_DOWN, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_key_up(uint32_t code)
|
||||
{
|
||||
DEBUG_KEYBOARD("%u", code);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code < 0x100)
|
||||
code |= 0x80;
|
||||
else
|
||||
code = 0x80e0 | ((code - 0x100) << 8);
|
||||
|
||||
SpiceMsgcKeyDown msg;
|
||||
msg.code = code;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_UP, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_mode(bool server)
|
||||
{
|
||||
DEBUG_MOUSE("%s", server ? "server" : "client");
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcMainMouseModeRequest msg;
|
||||
msg.mouse_mode = server ? SPICE_MOUSE_MODE_SERVER : SPICE_MOUSE_MODE_CLIENT;
|
||||
|
||||
return spice_write_msg(&spice.scMain, SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_position(uint32_t x, uint32_t y)
|
||||
{
|
||||
DEBUG_MOUSE("x=%u, y=%u", x, y);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcMousePosition msg;
|
||||
msg.x = x;
|
||||
msg.y = y;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
msg.display_id = 0;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_POSITION, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_motion(int32_t x, int32_t y)
|
||||
{
|
||||
DEBUG_MOUSE("x=%d, y=%d", x, y);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spice.mouse.sentCount == 4)
|
||||
{
|
||||
if (spice.mouse.queueLen == SPICE_MOUSE_QUEUE_SIZE)
|
||||
{
|
||||
DEBUG_ERROR("mouse motion ringbuffer full!");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcMouseMotion *msg =
|
||||
&spice.mouse.queue[spice.mouse.wpos++];
|
||||
msg->x = x;
|
||||
msg->y = y;
|
||||
msg->button_state = spice.mouse.buttonState;
|
||||
|
||||
if (spice.mouse.wpos == SPICE_MOUSE_QUEUE_SIZE)
|
||||
spice.mouse.wpos = 0;
|
||||
|
||||
++spice.mouse.queueLen;
|
||||
return true;
|
||||
}
|
||||
|
||||
SpiceMsgcMouseMotion msg;
|
||||
msg.x = x;
|
||||
msg.y = y;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
++spice.mouse.sentCount;
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_MOTION, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_press(uint32_t button)
|
||||
{
|
||||
DEBUG_MOUSE("%u", button);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(button)
|
||||
{
|
||||
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
|
||||
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
|
||||
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
|
||||
}
|
||||
|
||||
SpiceMsgcMousePress msg;
|
||||
msg.button = button;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_PRESS, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_release(uint32_t button)
|
||||
{
|
||||
DEBUG_MOUSE("%u", button);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(button)
|
||||
{
|
||||
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
|
||||
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
|
||||
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
|
||||
}
|
||||
|
||||
SpiceMsgcMouseRelease msg;
|
||||
msg.button = button;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_RELEASE, &msg, sizeof(msg));
|
||||
}
|
||||
75
client/src/app.c
Normal file
75
client/src/app.c
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "main.h"
|
||||
#include "common/debug.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...)
|
||||
{
|
||||
if (!state.lgr || !params.showAlerts)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
const int length = vsnprintf(NULL, 0, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
char *buffer = malloc(length + 1);
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, length + 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
state.lgr->on_alert(
|
||||
state.lgrData,
|
||||
type,
|
||||
buffer,
|
||||
NULL
|
||||
);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque)
|
||||
{
|
||||
// don't allow duplicate binds
|
||||
if (state.bindings[key])
|
||||
{
|
||||
DEBUG_INFO("Key already bound");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
KeybindHandle handle = (KeybindHandle)malloc(sizeof(struct KeybindHandle));
|
||||
handle->key = key;
|
||||
handle->callback = callback;
|
||||
handle->opaque = opaque;
|
||||
|
||||
state.bindings[key] = handle;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void app_release_keybind(KeybindHandle * handle)
|
||||
{
|
||||
if (!*handle)
|
||||
return;
|
||||
|
||||
state.bindings[(*handle)->key] = NULL;
|
||||
free(*handle);
|
||||
*handle = NULL;
|
||||
}
|
||||
586
client/src/config.c
Normal file
586
client/src/config.c
Normal file
@@ -0,0 +1,586 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "main.h"
|
||||
#include "config.h"
|
||||
#include "common/option.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/stringutils.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// forwards
|
||||
static bool optRendererParse (struct Option * opt, const char * str);
|
||||
static StringList optRendererValues (struct Option * opt);
|
||||
static char * optRendererToString(struct Option * opt);
|
||||
static bool optPosParse (struct Option * opt, const char * str);
|
||||
static StringList optPosValues (struct Option * opt);
|
||||
static char * optPosToString (struct Option * opt);
|
||||
static bool optSizeParse (struct Option * opt, const char * str);
|
||||
static StringList optSizeValues (struct Option * opt);
|
||||
static char * optSizeToString (struct Option * opt);
|
||||
static char * optScancodeToString(struct Option * opt);
|
||||
|
||||
static void doLicense();
|
||||
|
||||
static struct Option options[] =
|
||||
{
|
||||
// app options
|
||||
{
|
||||
.module = "app",
|
||||
.name = "configFile",
|
||||
.description = "A file to read additional configuration from",
|
||||
.shortopt = 'C',
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = NULL,
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "renderer",
|
||||
.description = "Specify the renderer to use",
|
||||
.shortopt = 'g',
|
||||
.type = OPTION_TYPE_CUSTOM,
|
||||
.parser = optRendererParse,
|
||||
.getValues = optRendererValues,
|
||||
.toString = optRendererToString
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "license",
|
||||
.description = "Show the license for this application and then terminate",
|
||||
.shortopt = 'l',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "cursorPollInterval",
|
||||
.description = "How often to check for a cursor update in microseconds",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 1000
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "framePollInterval",
|
||||
.description = "How often to check for a frame update in microseconds",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 1000
|
||||
},
|
||||
|
||||
// window options
|
||||
{
|
||||
.module = "win",
|
||||
.name = "title",
|
||||
.description = "The window title",
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = "Looking Glass (client)"
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "position",
|
||||
.description = "Initial window position at startup",
|
||||
.type = OPTION_TYPE_CUSTOM,
|
||||
.parser = optPosParse,
|
||||
.getValues = optPosValues,
|
||||
.toString = optPosToString
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "size",
|
||||
.description = "Initial window size at startup",
|
||||
.type = OPTION_TYPE_CUSTOM,
|
||||
.parser = optSizeParse,
|
||||
.getValues = optSizeValues,
|
||||
.toString = optSizeToString
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "autoResize",
|
||||
.description = "Auto resize the window to the guest",
|
||||
.shortopt = 'a',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "allowResize",
|
||||
.description = "Allow the window to be manually resized",
|
||||
.shortopt = 'n',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "keepAspect",
|
||||
.description = "Maintain the correct aspect ratio",
|
||||
.shortopt = 'r',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "forceAspect",
|
||||
.description = "Force the window to maintain the aspect ratio",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "borderless",
|
||||
.description = "Borderless mode",
|
||||
.shortopt = 'd',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "fullScreen",
|
||||
.description = "Launch in fullscreen borderless mode",
|
||||
.shortopt = 'F',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "maximize",
|
||||
.description = "Launch window maximized",
|
||||
.shortopt = 'T',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "minimizeOnFocusLoss",
|
||||
.description = "Minimize window on focus loss",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "fpsMin",
|
||||
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)",
|
||||
.shortopt = 'K',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = -1,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "showFPS",
|
||||
.description = "Enable the FPS & UPS display",
|
||||
.shortopt = 'k',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "ignoreQuit",
|
||||
.description = "Ignore requests to quit (ie: Alt+F4)",
|
||||
.shortopt = 'Q',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "noScreensaver",
|
||||
.description = "Prevent the screensaver from starting",
|
||||
.shortopt = 'S',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "alerts",
|
||||
.description = "Show on screen alert messages",
|
||||
.shortopt = 'q',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
|
||||
// input options
|
||||
{
|
||||
.module = "input",
|
||||
.name = "grabKeyboard",
|
||||
.description = "Grab the keyboard in capture mode",
|
||||
.shortopt = 'G',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "escapeKey",
|
||||
.description = "Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values",
|
||||
.shortopt = 'm',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = SDL_SCANCODE_SCROLLLOCK,
|
||||
.toString = optScancodeToString
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "hideCursor",
|
||||
.description = "Hide the local mouse cursor",
|
||||
.shortopt = 'M',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "mouseSens",
|
||||
.description = "Initial mouse sensitivity when in capture mode (-9 to 9)",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 0,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "mouseRedraw",
|
||||
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
|
||||
// spice options
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "enable",
|
||||
.description = "Enable the built in SPICE client for input and/or clipboard support",
|
||||
.shortopt = 's',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "host",
|
||||
.description = "The SPICE server host or UNIX socket",
|
||||
.shortopt = 'c',
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = "127.0.0.1"
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "port",
|
||||
.description = "The SPICE server port (0 = unix socket)",
|
||||
.shortopt = 'p',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 5900
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "input",
|
||||
.description = "Use SPICE to send keyboard and mouse input events to the guest",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "clipboard",
|
||||
.description = "Use SPICE to syncronize the clipboard contents with the guest",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "clipboardToVM",
|
||||
.description = "Allow the clipboard to be syncronized TO the VM",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "clipboardToLocal",
|
||||
.description = "Allow the clipboard to be syncronized FROM the VM",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "scaleCursor",
|
||||
.description = "Scale cursor input position to screen size when up/down scaled",
|
||||
.shortopt = 'j',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "captureOnStart",
|
||||
.description = "Capture mouse and keyboard on start",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
void config_init()
|
||||
{
|
||||
params.center = true;
|
||||
params.w = 1024;
|
||||
params.h = 768;
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
bool config_load(int argc, char * argv[])
|
||||
{
|
||||
// load any global options first
|
||||
struct stat st;
|
||||
if (stat("/etc/looking-glass-client.ini", &st) >= 0)
|
||||
{
|
||||
DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini");
|
||||
if (!option_load("/etc/looking-glass-client.ini"))
|
||||
return false;
|
||||
}
|
||||
|
||||
// load user's local options
|
||||
struct passwd * pw = getpwuid(getuid());
|
||||
char * localFile;
|
||||
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
|
||||
if (stat(localFile, &st) >= 0)
|
||||
{
|
||||
DEBUG_INFO("Loading config from: %s", localFile);
|
||||
if (!option_load(localFile))
|
||||
{
|
||||
free(localFile);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
free(localFile);
|
||||
|
||||
// parse the command line arguments
|
||||
if (!option_parse(argc, argv))
|
||||
return false;
|
||||
|
||||
// if a file was specified to also load, do it
|
||||
const char * configFile = option_get_string("app", "configFile");
|
||||
if (configFile)
|
||||
{
|
||||
DEBUG_INFO("Loading config from: %s", configFile);
|
||||
if (!option_load(configFile))
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate the values are sane
|
||||
if (!option_validate())
|
||||
return false;
|
||||
|
||||
if (option_get_bool("app", "license"))
|
||||
{
|
||||
doLicense();
|
||||
return false;
|
||||
}
|
||||
|
||||
// setup the application params for the basic types
|
||||
params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
|
||||
params.framePollInterval = option_get_int ("app", "framePollInterval" );
|
||||
|
||||
params.windowTitle = option_get_string("win", "title" );
|
||||
params.autoResize = option_get_bool ("win", "autoResize" );
|
||||
params.allowResize = option_get_bool ("win", "allowResize" );
|
||||
params.keepAspect = option_get_bool ("win", "keepAspect" );
|
||||
params.forceAspect = option_get_bool ("win", "forceAspect" );
|
||||
params.borderless = option_get_bool ("win", "borderless" );
|
||||
params.fullscreen = option_get_bool ("win", "fullScreen" );
|
||||
params.maximize = option_get_bool ("win", "maximize" );
|
||||
params.fpsMin = option_get_int ("win", "fpsMin" );
|
||||
params.showFPS = option_get_bool ("win", "showFPS" );
|
||||
params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
|
||||
params.noScreensaver = option_get_bool ("win", "noScreensaver");
|
||||
params.showAlerts = option_get_bool ("win", "alerts" );
|
||||
|
||||
params.grabKeyboard = option_get_bool ("input", "grabKeyboard");
|
||||
params.escapeKey = option_get_int ("input", "escapeKey" );
|
||||
params.hideMouse = option_get_bool ("input", "hideCursor" );
|
||||
params.mouseSens = option_get_int ("input", "mouseSens" );
|
||||
params.mouseRedraw = option_get_bool ("input", "mouseRedraw" );
|
||||
|
||||
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
||||
|
||||
if (option_get_bool("spice", "enable"))
|
||||
{
|
||||
params.spiceHost = option_get_string("spice", "host");
|
||||
params.spicePort = option_get_int ("spice", "port");
|
||||
|
||||
params.useSpiceInput = option_get_bool("spice", "input" );
|
||||
params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
||||
|
||||
if (params.useSpiceClipboard)
|
||||
{
|
||||
params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
||||
params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
||||
|
||||
if (!params.clipboardToVM && !params.clipboardToLocal)
|
||||
params.useSpiceClipboard = false;
|
||||
}
|
||||
|
||||
params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
|
||||
params.captureOnStart = option_get_bool("spice", "captureOnStart");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void config_free()
|
||||
{
|
||||
option_free();
|
||||
}
|
||||
|
||||
static void doLicense()
|
||||
{
|
||||
fprintf(stderr,
|
||||
"\n"
|
||||
"Looking Glass - KVM FrameRelay (KVMFR) Client\n"
|
||||
"Copyright(C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>\n"
|
||||
"https://looking-glass.hostfission.com\n"
|
||||
"\n"
|
||||
"This program is free software; you can redistribute it and / or modify it under\n"
|
||||
"the terms of the GNU General Public License as published by the Free Software\n"
|
||||
"Foundation; either version 2 of the License, or (at your option) any later\n"
|
||||
"version.\n"
|
||||
"\n"
|
||||
"This program is distributed in the hope that it will be useful, but WITHOUT ANY\n"
|
||||
"WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n"
|
||||
"PARTICULAR PURPOSE.See the GNU General Public License for more details.\n"
|
||||
"\n"
|
||||
"You should have received a copy of the GNU General Public License along with\n"
|
||||
"this program; if not, write to the Free Software Foundation, Inc., 59 Temple\n"
|
||||
"Place, Suite 330, Boston, MA 02111 - 1307 USA\n"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
static bool optRendererParse(struct Option * opt, const char * str)
|
||||
{
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (strcasecmp(str, "auto") == 0)
|
||||
{
|
||||
params.forceRenderer = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
||||
if (strcasecmp(str, LG_Renderers[i]->get_name()) == 0)
|
||||
{
|
||||
params.forceRenderer = true;
|
||||
params.forceRendererIndex = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static StringList optRendererValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
|
||||
// this typecast is safe as the stringlist doesn't own the values
|
||||
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
||||
stringlist_push(sl, (char *)LG_Renderers[i]->get_name());
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
static char * optRendererToString(struct Option * opt)
|
||||
{
|
||||
if (!params.forceRenderer)
|
||||
return strdup("auto");
|
||||
|
||||
if (params.forceRendererIndex >= LG_RENDERER_COUNT)
|
||||
return NULL;
|
||||
|
||||
return strdup(LG_Renderers[params.forceRendererIndex]->get_name());
|
||||
}
|
||||
|
||||
static bool optPosParse(struct Option * opt, const char * str)
|
||||
{
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (strcmp(str, "center") == 0)
|
||||
{
|
||||
params.center = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sscanf(str, "%dx%d", ¶ms.x, ¶ms.y) == 2)
|
||||
{
|
||||
params.center = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static StringList optPosValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
stringlist_push(sl, "center");
|
||||
stringlist_push(sl, "<left>x<top>, ie: 100x100");
|
||||
return sl;
|
||||
}
|
||||
|
||||
static char * optPosToString(struct Option * opt)
|
||||
{
|
||||
if (params.center)
|
||||
return strdup("center");
|
||||
|
||||
int len = snprintf(NULL, 0, "%dx%d", params.x, params.y);
|
||||
char * str = malloc(len + 1);
|
||||
sprintf(str, "%dx%d", params.x, params.y);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool optSizeParse(struct Option * opt, const char * str)
|
||||
{
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (sscanf(str, "%dx%d", ¶ms.w, ¶ms.h) == 2)
|
||||
{
|
||||
if (params.w < 1 || params.h < 1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static StringList optSizeValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
stringlist_push(sl, "<left>x<top>, ie: 100x100");
|
||||
return sl;
|
||||
}
|
||||
|
||||
static char * optSizeToString(struct Option * opt)
|
||||
{
|
||||
int len = snprintf(NULL, 0, "%dx%d", params.w, params.h);
|
||||
char * str = malloc(len + 1);
|
||||
sprintf(str, "%dx%d", params.w, params.h);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char * optScancodeToString(struct Option * opt)
|
||||
{
|
||||
char * str;
|
||||
alloc_sprintf(&str, "%d = %s", opt->value.x_int, SDL_GetScancodeName(opt->value.x_int));
|
||||
return str;
|
||||
}
|
||||
24
client/src/config.h
Normal file
24
client/src/config.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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>
|
||||
|
||||
void config_init();
|
||||
bool config_load(int argc, char * argv[]);
|
||||
void config_free();
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,8 +17,7 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "lg-renderer.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
bool LG_RendererValidatorBool(const char * value)
|
||||
160
client/src/ll.c
Normal file
160
client/src/ll.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "ll.h"
|
||||
|
||||
#include "common/locking.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
struct ll_item
|
||||
{
|
||||
void * data;
|
||||
struct ll_item * next;
|
||||
};
|
||||
|
||||
struct ll
|
||||
{
|
||||
struct ll_item * head;
|
||||
struct ll_item * tail;
|
||||
struct ll_item * pos;
|
||||
unsigned int count;
|
||||
LG_Lock lock;
|
||||
};
|
||||
|
||||
struct ll * ll_new()
|
||||
{
|
||||
struct ll * list = malloc(sizeof(struct ll));
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
list->pos = NULL;
|
||||
LG_LOCK_INIT(list->lock);
|
||||
return list;
|
||||
}
|
||||
|
||||
void ll_free(struct ll * list)
|
||||
{
|
||||
// never free a list with items in it!
|
||||
assert(!list->head);
|
||||
|
||||
LG_LOCK_FREE(list->lock);
|
||||
free(list);
|
||||
}
|
||||
|
||||
void ll_push(struct ll * list, void * data)
|
||||
{
|
||||
struct ll_item * item = malloc(sizeof(struct ll_item));
|
||||
item->data = data;
|
||||
item->next = NULL;
|
||||
|
||||
LG_LOCK(list->lock);
|
||||
if (!list->head)
|
||||
{
|
||||
list->head = item;
|
||||
list->tail = item;
|
||||
LG_UNLOCK(list->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
++list->count;
|
||||
list->tail->next = item;
|
||||
list->tail = item;
|
||||
LG_UNLOCK(list->lock);
|
||||
}
|
||||
|
||||
bool ll_shift(struct ll * list, void ** data)
|
||||
{
|
||||
LG_LOCK(list->lock);
|
||||
if (!list->head)
|
||||
{
|
||||
LG_UNLOCK(list->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
--list->count;
|
||||
struct ll_item * item = list->head;
|
||||
list->head = item->next;
|
||||
|
||||
list->pos = NULL;
|
||||
LG_UNLOCK(list->lock);
|
||||
|
||||
if (data)
|
||||
*data = item->data;
|
||||
|
||||
free(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ll_peek_head(struct ll * list, void ** data)
|
||||
{
|
||||
LG_LOCK(list->lock);
|
||||
if (!list->head)
|
||||
{
|
||||
LG_UNLOCK(list->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
*data = list->head->data;
|
||||
LG_UNLOCK(list->lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int ll_count(struct ll * list)
|
||||
{
|
||||
return list->count;
|
||||
}
|
||||
|
||||
void ll_reset (struct ll * list)
|
||||
{
|
||||
LG_LOCK(list->lock);
|
||||
list->pos = NULL;
|
||||
LG_UNLOCK(list->lock);
|
||||
}
|
||||
|
||||
|
||||
bool ll_walk(struct ll * list, void ** data)
|
||||
{
|
||||
LG_LOCK(list->lock);
|
||||
|
||||
if (!list->pos)
|
||||
{
|
||||
if (!list->head)
|
||||
{
|
||||
LG_UNLOCK(list->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
list->pos = list->head;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!list->pos->next)
|
||||
{
|
||||
LG_UNLOCK(list->lock);
|
||||
return false;
|
||||
}
|
||||
list->pos = list->pos->next;
|
||||
}
|
||||
|
||||
*data = list->pos->data;
|
||||
LG_UNLOCK(list->lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
1700
client/src/main.c
Normal file
1700
client/src/main.c
Normal file
File diff suppressed because it is too large
Load Diff
182
client/src/main.h
Normal file
182
client/src/main.h
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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>
|
||||
#include <stdatomic.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "interface/app.h"
|
||||
#include "dynamic/renderers.h"
|
||||
#include "dynamic/clipboards.h"
|
||||
#include "common/ivshmem.h"
|
||||
|
||||
#include "spice/spice.h"
|
||||
#include <lgmp/client.h>
|
||||
|
||||
enum RunState
|
||||
{
|
||||
APP_STATE_RUNNING,
|
||||
APP_STATE_RESTART,
|
||||
APP_STATE_SHUTDOWN
|
||||
};
|
||||
|
||||
struct CursorInfo
|
||||
{
|
||||
int x , y;
|
||||
int hx, hy;
|
||||
};
|
||||
|
||||
enum WarpState
|
||||
{
|
||||
WARP_STATE_ARMED,
|
||||
WARP_STATE_ON,
|
||||
WARP_STATE_ACTIVE,
|
||||
WARP_STATE_OFF
|
||||
};
|
||||
|
||||
struct AppState
|
||||
{
|
||||
enum RunState state;
|
||||
bool ignoreInput;
|
||||
bool escapeActive;
|
||||
SDL_Scancode escapeAction;
|
||||
KeybindHandle bindings[SDL_NUM_SCANCODES];
|
||||
bool keyDown[SDL_NUM_SCANCODES];
|
||||
|
||||
bool haveSrcSize;
|
||||
int windowW, windowH;
|
||||
SDL_Point srcSize;
|
||||
LG_RendererRect dstRect;
|
||||
struct CursorInfo cursor;
|
||||
bool cursorVisible;
|
||||
|
||||
bool serverMode;
|
||||
bool haveCursorPos;
|
||||
bool drawCursor;
|
||||
bool cursorInView;
|
||||
bool updateCursor;
|
||||
bool initialCursorSync;
|
||||
float scaleX, scaleY;
|
||||
float accX, accY;
|
||||
int curLastX;
|
||||
int curLastY;
|
||||
bool haveCurLocal;
|
||||
int curLocalX;
|
||||
int curLocalY;
|
||||
bool haveAligned;
|
||||
|
||||
enum WarpState warpState;
|
||||
int warpFromX, warpFromY;
|
||||
int warpToX , warpToY;
|
||||
|
||||
const LG_Renderer * lgr;
|
||||
void * lgrData;
|
||||
bool lgrResize;
|
||||
|
||||
const LG_Clipboard * lgc;
|
||||
SpiceDataType cbType;
|
||||
struct ll * cbRequestList;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_Window * window;
|
||||
|
||||
struct IVSHMEM shm;
|
||||
PLGMPClient lgmp;
|
||||
PLGMPClientQueue frameQueue;
|
||||
PLGMPClientQueue pointerQueue;
|
||||
|
||||
atomic_uint_least64_t frameTime;
|
||||
uint64_t lastFrameTime;
|
||||
uint64_t renderTime;
|
||||
uint64_t frameCount;
|
||||
uint64_t renderCount;
|
||||
|
||||
|
||||
uint64_t resizeTimeout;
|
||||
bool resizeDone;
|
||||
|
||||
KeybindHandle kbFS;
|
||||
KeybindHandle kbInput;
|
||||
KeybindHandle kbQuit;
|
||||
KeybindHandle kbMouseSensInc;
|
||||
KeybindHandle kbMouseSensDec;
|
||||
KeybindHandle kbCtrlAltFn[12];
|
||||
|
||||
int mouseSens;
|
||||
float sensX, sensY;
|
||||
};
|
||||
|
||||
struct AppParams
|
||||
{
|
||||
bool autoResize;
|
||||
bool allowResize;
|
||||
bool keepAspect;
|
||||
bool forceAspect;
|
||||
bool borderless;
|
||||
bool fullscreen;
|
||||
bool maximize;
|
||||
bool minimizeOnFocusLoss;
|
||||
bool center;
|
||||
int x, y;
|
||||
unsigned int w, h;
|
||||
unsigned int fpsMin;
|
||||
bool showFPS;
|
||||
bool useSpiceInput;
|
||||
bool useSpiceClipboard;
|
||||
const char * spiceHost;
|
||||
unsigned int spicePort;
|
||||
bool clipboardToVM;
|
||||
bool clipboardToLocal;
|
||||
bool scaleMouseInput;
|
||||
bool hideMouse;
|
||||
bool ignoreQuit;
|
||||
bool noScreensaver;
|
||||
bool grabKeyboard;
|
||||
SDL_Scancode escapeKey;
|
||||
bool showAlerts;
|
||||
bool captureOnStart;
|
||||
|
||||
unsigned int cursorPollInterval;
|
||||
unsigned int framePollInterval;
|
||||
|
||||
bool forceRenderer;
|
||||
unsigned int forceRendererIndex;
|
||||
|
||||
const char * windowTitle;
|
||||
int mouseSens;
|
||||
bool mouseRedraw;
|
||||
};
|
||||
|
||||
struct CBRequest
|
||||
{
|
||||
SpiceDataType type;
|
||||
LG_ClipboardReplyFn replyFn;
|
||||
void * opaque;
|
||||
};
|
||||
|
||||
struct KeybindHandle
|
||||
{
|
||||
SDL_Scancode key;
|
||||
SuperEventFn callback;
|
||||
void * opaque;
|
||||
};
|
||||
|
||||
// forwards
|
||||
extern struct AppState state;
|
||||
extern struct AppParams params;
|
||||
70
client/src/utils.c
Normal file
70
client/src/utils.c
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "utils.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
bool file_get_contents(const char * filename, char ** buffer, size_t * length)
|
||||
{
|
||||
FILE * fh = fopen(filename, "r");
|
||||
if (!fh)
|
||||
{
|
||||
DEBUG_ERROR("Failed to open the file: %s", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(fh, 0, SEEK_END) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to seek");
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
long fsize = ftell(fh);
|
||||
if (fseek(fh, 0, SEEK_SET) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to seek");
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
*buffer = malloc(fsize + 1);
|
||||
if (!*buffer)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate buffer of %lu bytes", fsize + 1);
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fread(*buffer, 1, fsize, fh) != fsize)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read the entire file");
|
||||
fclose(fh);
|
||||
free(*buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(fh);
|
||||
buffer[fsize] = 0;
|
||||
*length = fsize;
|
||||
return true;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static inline uint64_t microtime()
|
||||
{
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
|
||||
return ((uint64_t)time.tv_sec * 1000000) + (time.tv_nsec / 1000);
|
||||
}
|
||||
|
||||
static inline uint64_t nanotime()
|
||||
{
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
|
||||
return ((uint64_t)time.tv_sec * 1e9) + time.tv_nsec;
|
||||
}
|
||||
|
||||
static inline void nsleep(uint64_t ns)
|
||||
{
|
||||
const struct timespec ts =
|
||||
{
|
||||
.tv_sec = ns / 1e9,
|
||||
.tv_nsec = ns - ((ns / 1e9) * 1e9)
|
||||
};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
#ifdef ATOMIC_LOCKING
|
||||
#define LG_LOCK_MODE "Atomic"
|
||||
typedef volatile int LG_Lock;
|
||||
#define LG_LOCK_INIT(x) (x) = 0
|
||||
#define LG_LOCK(x) while(__sync_lock_test_and_set(&(x), 1)) {nsleep(100);}
|
||||
#define LG_UNLOCK(x) __sync_lock_release(&x)
|
||||
#define LG_LOCK_FREE(x)
|
||||
#else
|
||||
#define LG_LOCK_MODE "Mutex"
|
||||
typedef SDL_mutex * LG_Lock;
|
||||
#define LG_LOCK_INIT(x) (x = SDL_CreateMutex())
|
||||
#define LG_LOCK(x) SDL_LockMutex(x)
|
||||
#define LG_UNLOCK(x) SDL_UnlockMutex(x)
|
||||
#define LG_LOCK_FREE(x) SDL_DestroyMutex(x)
|
||||
#endif
|
||||
31
common/CMakeLists.txt
Normal file
31
common/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(lg_common LANGUAGES C)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
add_definitions(-D_GNU_SOURCE)
|
||||
|
||||
if(ENABLE_BACKTRACE)
|
||||
add_definitions(-DENABLE_BACKTRACE)
|
||||
endif()
|
||||
|
||||
add_subdirectory(src/platform)
|
||||
|
||||
set(COMMON_SOURCES
|
||||
src/stringutils.c
|
||||
src/stringlist.c
|
||||
src/option.c
|
||||
src/framebuffer.c
|
||||
)
|
||||
|
||||
add_library(lg_common STATIC ${COMMON_SOURCES})
|
||||
target_link_libraries(lg_common lg_common_platform)
|
||||
|
||||
target_include_directories(lg_common
|
||||
INTERFACE
|
||||
include
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR)
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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>
|
||||
|
||||
#define KVMFR_HEADER_MAGIC "[[KVMFR]]"
|
||||
#define KVMFR_HEADER_VERSION 5
|
||||
|
||||
typedef enum FrameType
|
||||
{
|
||||
FRAME_TYPE_INVALID ,
|
||||
FRAME_TYPE_ARGB , // ABGR interleaved: A,R,G,B 32bpp
|
||||
FRAME_TYPE_RGB , // RGB interleaved : R,G,B 24bpp
|
||||
FRAME_TYPE_MAX , // sentinel value
|
||||
}
|
||||
FrameType;
|
||||
|
||||
typedef enum CursorType
|
||||
{
|
||||
CURSOR_TYPE_COLOR ,
|
||||
CURSOR_TYPE_MONOCHROME ,
|
||||
CURSOR_TYPE_MASKED_COLOR
|
||||
}
|
||||
CursorType;
|
||||
|
||||
#define KVMFR_CURSOR_FLAG_VISIBLE 1 // cursor is visible
|
||||
#define KVMFR_CURSOR_FLAG_SHAPE 2 // shape updated
|
||||
#define KVMFR_CURSOR_FLAG_POS 4 // position updated
|
||||
|
||||
typedef struct KVMFRCursor
|
||||
{
|
||||
uint8_t flags; // KVMFR_CURSOR_FLAGS
|
||||
int16_t x, y; // cursor x & y position
|
||||
|
||||
uint32_t version; // shape version
|
||||
CursorType type; // shape buffer data type
|
||||
uint32_t width; // width of the shape
|
||||
uint32_t height; // height of the shape
|
||||
uint32_t pitch; // row length in bytes of the shape
|
||||
uint64_t dataPos; // offset to the shape data
|
||||
}
|
||||
KVMFRCursor;
|
||||
|
||||
typedef struct KVMFRFrame
|
||||
{
|
||||
FrameType type; // the frame data type
|
||||
uint32_t width; // the width
|
||||
uint32_t height; // the height
|
||||
uint32_t stride; // the row stride (zero if cursor data)
|
||||
uint32_t pitch; // the row pitch (stride in bytes)
|
||||
uint64_t dataPos; // offset to the frame
|
||||
}
|
||||
KVMFRFrame;
|
||||
|
||||
#define KVMFR_HEADER_FLAG_FRAME 1 // frame update available
|
||||
#define KVMFR_HEADER_FLAG_CURSOR 2 // cursor update available
|
||||
#define KVMFR_HEADER_FLAG_RESTART 4 // restart signal from client
|
||||
#define KVMFR_HEADER_FLAG_READY 8 // ready signal from client
|
||||
|
||||
typedef struct KVMFRDetail
|
||||
{
|
||||
KVMFRFrame frame; // the frame information
|
||||
KVMFRCursor cursor; // the cursor information
|
||||
}
|
||||
KVMFRDetail;
|
||||
|
||||
typedef struct KVMFRHeader
|
||||
{
|
||||
char magic[sizeof(KVMFR_HEADER_MAGIC)];
|
||||
uint32_t version; // version of this structure
|
||||
uint8_t flags; // KVMFR_HEADER_FLAGS
|
||||
KVMFRDetail detail; // details
|
||||
}
|
||||
KVMFRHeader;
|
||||
84
common/include/common/KVMFR.h
Normal file
84
common/include/common/KVMFR.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR)
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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>
|
||||
|
||||
#define LGMP_Q_POINTER 1
|
||||
#define LGMP_Q_FRAME 2
|
||||
|
||||
typedef enum FrameType
|
||||
{
|
||||
FRAME_TYPE_INVALID ,
|
||||
FRAME_TYPE_BGRA , // BGRA interleaved: B,G,R,A 32bpp
|
||||
FRAME_TYPE_RGBA , // RGBA interleaved: R,G,B,A 32bpp
|
||||
FRAME_TYPE_RGBA10 , // RGBA interleaved: R,G,B,A 10,10,10,2 bpp
|
||||
FRAME_TYPE_YUV420 , // YUV420
|
||||
FRAME_TYPE_MAX , // sentinel value
|
||||
}
|
||||
FrameType;
|
||||
|
||||
enum
|
||||
{
|
||||
CURSOR_FLAG_POSITION = 0x1,
|
||||
CURSOR_FLAG_VISIBLE = 0x2,
|
||||
CURSOR_FLAG_SHAPE = 0x4
|
||||
};
|
||||
typedef uint32_t KVMFRCursorFlags;
|
||||
|
||||
typedef enum CursorType
|
||||
{
|
||||
CURSOR_TYPE_COLOR ,
|
||||
CURSOR_TYPE_MONOCHROME ,
|
||||
CURSOR_TYPE_MASKED_COLOR
|
||||
}
|
||||
CursorType;
|
||||
|
||||
#define KVMFR_MAGIC "KVMFR---"
|
||||
#define KVMFR_VERSION 3
|
||||
|
||||
typedef struct KVMFR
|
||||
{
|
||||
char magic[8];
|
||||
uint32_t version;
|
||||
char hostver[32];
|
||||
}
|
||||
KVMFR;
|
||||
|
||||
typedef struct KVMFRCursor
|
||||
{
|
||||
int16_t x, y; // cursor x & y position
|
||||
CursorType type; // shape buffer data type
|
||||
int8_t hx, hy; // shape hotspot x & y
|
||||
uint32_t width; // width of the shape
|
||||
uint32_t height; // height of the shape
|
||||
uint32_t pitch; // row length in bytes of the shape
|
||||
}
|
||||
KVMFRCursor;
|
||||
|
||||
typedef struct KVMFRFrame
|
||||
{
|
||||
FrameType type; // the frame data type
|
||||
uint32_t width; // the width
|
||||
uint32_t height; // the height
|
||||
uint32_t stride; // the row stride (zero if compressed data)
|
||||
uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size)
|
||||
uint32_t offset; // offset from the start of this header to the FrameBuffer header
|
||||
}
|
||||
KVMFRFrame;
|
||||
22
common/include/common/crash.h
Normal file
22
common/include/common/crash.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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>
|
||||
|
||||
bool installCrashHandler(const char * exe);
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,9 +17,15 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#if _WIN32
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include "time.h"
|
||||
|
||||
#if defined(_WIN32) && !defined(__GNUC__)
|
||||
#define DIRECTORY_SEPARATOR '\\'
|
||||
#else
|
||||
#define DIRECTORY_SEPARATOR '/'
|
||||
@@ -47,8 +53,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \
|
||||
sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s))
|
||||
|
||||
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, type " %20s:%-4u | %-30s | " fmt "\n", STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
|
||||
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%12" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
|
||||
|
||||
#define DEBUG_BREAK() DEBUG_PRINT("[ ]", "%s", "================================================================================")
|
||||
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)
|
||||
#define DEBUG_WARN(fmt, ...) DEBUG_PRINT("[W]", fmt, ##__VA_ARGS__)
|
||||
#define DEBUG_ERROR(fmt, ...) DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
|
||||
42
common/include/common/event.h
Normal file
42
common/include/common/event.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <time.h>
|
||||
|
||||
#define TIMEOUT_INFINITE ((unsigned int)~0)
|
||||
|
||||
typedef struct LGEvent LGEvent;
|
||||
|
||||
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime);
|
||||
void lgFreeEvent (LGEvent * handle);
|
||||
bool lgWaitEvent (LGEvent * handle, unsigned int timeout);
|
||||
bool lgWaitEvents (LGEvent * handles[], int count, bool waitAll, unsigned int timeout);
|
||||
bool lgSignalEvent(LGEvent * handle);
|
||||
bool lgResetEvent (LGEvent * handle);
|
||||
|
||||
// os specific method to wrap/convert a native event into a LGEvent
|
||||
// for windows this is an event HANDLE
|
||||
LGEvent * lgWrapEvent(void * handle);
|
||||
|
||||
// Posix specific, not implmented/possible in windows
|
||||
bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts);
|
||||
bool lgWaitEventNS (LGEvent * handle, unsigned int timeout);
|
||||
60
common/include/common/framebuffer.h
Normal file
60
common/include/common/framebuffer.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct stFrameBuffer FrameBuffer;
|
||||
|
||||
typedef bool (*FrameBufferReadFn)(void * opaque, const void * src, size_t size);
|
||||
|
||||
/**
|
||||
* The size of the FrameBuffer struct
|
||||
*/
|
||||
extern const size_t FrameBufferStructSize;
|
||||
|
||||
/**
|
||||
* Wait for the framebuffer to fill to the specified size
|
||||
*/
|
||||
void framebuffer_wait(const FrameBuffer * frame, size_t size);
|
||||
|
||||
/**
|
||||
* Read data from the KVMFRFrame into the dst buffer
|
||||
*/
|
||||
bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch,
|
||||
size_t height, size_t width, size_t bpp, size_t pitch);
|
||||
|
||||
/**
|
||||
* Read data from the KVMFRFrame using a callback
|
||||
*/
|
||||
bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
|
||||
size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque);
|
||||
|
||||
/**
|
||||
* Prepare the framebuffer for writing
|
||||
*/
|
||||
void framebuffer_prepare(FrameBuffer * frame);
|
||||
|
||||
/**
|
||||
* Write data from the src buffer into the KVMFRFrame
|
||||
*/
|
||||
bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size);
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,24 +17,20 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool ivshmem_connect(const char * unix_socket);
|
||||
void ivshmem_disconnect();
|
||||
bool ivshmem_process();
|
||||
|
||||
uint16_t ivshmem_get_id();
|
||||
void * ivshmem_get_map();
|
||||
size_t ivshmem_get_map_size();
|
||||
|
||||
enum IVSHMEMWaitResult
|
||||
struct IVSHMEM
|
||||
{
|
||||
IVSHMEM_WAIT_RESULT_OK,
|
||||
IVSHMEM_WAIT_RESULT_TIMEOUT,
|
||||
IVSHMEM_WAIT_RESULT_ERROR
|
||||
unsigned int size;
|
||||
void * mem;
|
||||
|
||||
// internal use
|
||||
void * opaque;
|
||||
};
|
||||
|
||||
enum IVSHMEMWaitResult ivshmem_wait_irq(uint16_t vector, unsigned int timeout);
|
||||
bool ivshmem_kick_irq(uint16_t clientID, uint16_t vector);
|
||||
void ivshmemOptionsInit();
|
||||
bool ivshmemOpen(struct IVSHMEM * dev);
|
||||
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev);
|
||||
void ivshmemClose(struct IVSHMEM * dev);
|
||||
40
common/include/common/locking.h
Normal file
40
common/include/common/locking.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "time.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define LG_LOCK_MODE "Atomic"
|
||||
typedef atomic_flag LG_Lock;
|
||||
#define LG_LOCK_INIT(x) atomic_flag_clear(&(x))
|
||||
#define LG_LOCK(x) \
|
||||
while(atomic_flag_test_and_set_explicit(&(x), memory_order_acquire)) { ; }
|
||||
#define LG_UNLOCK(x) \
|
||||
atomic_flag_clear_explicit(&(x), memory_order_release);
|
||||
#define LG_LOCK_FREE(x)
|
||||
|
||||
#define INTERLOCKED_INC(x) atomic_fetch_add((x), 1)
|
||||
#define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1)
|
||||
|
||||
#define INTERLOCKED_SECTION(lock, x) \
|
||||
LG_LOCK(lock) \
|
||||
x \
|
||||
LG_UNLOCK(lock)
|
||||
135
common/include/common/memcpySSE.h
Normal file
135
common/include/common/memcpySSE.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 <stdint.h>
|
||||
#include <string.h>
|
||||
#include <tmmintrin.h>
|
||||
#include <immintrin.h>
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
#if defined(NATIVE_MEMCPY)
|
||||
#define memcpySSE memcpy
|
||||
#elif defined(_MSC_VER)
|
||||
extern "C" void * memcpySSE(void *dst, const void * src, size_t length);
|
||||
#elif (defined(__GNUC__) || defined(__GNUG__)) && defined(__i386__)
|
||||
inline static void * memcpySSE(void *dst, const void * src, size_t length)
|
||||
{
|
||||
if (length == 0 || dst == src)
|
||||
return;
|
||||
|
||||
// copies under 1MB are faster with the inlined memcpy
|
||||
// tell the dev to use that instead
|
||||
if (length < 1048576)
|
||||
{
|
||||
static bool smallBufferWarn = false;
|
||||
if (!smallBufferWarn)
|
||||
{
|
||||
DEBUG_WARN("Do not use memcpySSE for copies under 1MB in size!");
|
||||
smallBufferWarn = true;
|
||||
}
|
||||
memcpy(dst, src, length);
|
||||
return;
|
||||
}
|
||||
|
||||
const void * end = dst + (length & ~0x7F);
|
||||
const size_t off = (7 - ((length & 0x7F) >> 4)) * 9;
|
||||
|
||||
__asm__ __volatile__ (
|
||||
"cmp %[dst],%[end] \n\t"
|
||||
"je Remain_%= \n\t"
|
||||
|
||||
// perform SIMD block copy
|
||||
"loop_%=: \n\t"
|
||||
"movaps 0x00(%[src]),%%xmm0 \n\t"
|
||||
"movaps 0x10(%[src]),%%xmm1 \n\t"
|
||||
"movaps 0x20(%[src]),%%xmm2 \n\t"
|
||||
"movaps 0x30(%[src]),%%xmm3 \n\t"
|
||||
"movaps 0x40(%[src]),%%xmm4 \n\t"
|
||||
"movaps 0x50(%[src]),%%xmm5 \n\t"
|
||||
"movaps 0x60(%[src]),%%xmm6 \n\t"
|
||||
"movaps 0x70(%[src]),%%xmm7 \n\t"
|
||||
"movntdq %%xmm0 ,0x00(%[dst]) \n\t"
|
||||
"movntdq %%xmm1 ,0x10(%[dst]) \n\t"
|
||||
"movntdq %%xmm2 ,0x20(%[dst]) \n\t"
|
||||
"movntdq %%xmm3 ,0x30(%[dst]) \n\t"
|
||||
"movntdq %%xmm4 ,0x40(%[dst]) \n\t"
|
||||
"movntdq %%xmm5 ,0x50(%[dst]) \n\t"
|
||||
"movntdq %%xmm6 ,0x60(%[dst]) \n\t"
|
||||
"movntdq %%xmm7 ,0x70(%[dst]) \n\t"
|
||||
"add $0x80,%[dst] \n\t"
|
||||
"add $0x80,%[src] \n\t"
|
||||
"cmp %[dst],%[end] \n\t"
|
||||
"jne loop_%= \n\t"
|
||||
|
||||
"Remain_%=: \n\t"
|
||||
|
||||
// copy any remaining 16 byte blocks
|
||||
"call GetPC_%=\n\t"
|
||||
"Offset_%=:\n\t"
|
||||
"add $(BlockTable_%= - Offset_%=), %%eax \n\t"
|
||||
"add %[off],%%eax \n\t"
|
||||
"jmp *%%eax \n\t"
|
||||
|
||||
"GetPC_%=:\n\t"
|
||||
"mov (%%esp), %%eax \n\t"
|
||||
"ret \n\t"
|
||||
|
||||
"BlockTable_%=:\n\t"
|
||||
"movaps 0x60(%[src]),%%xmm6 \n\t"
|
||||
"movntdq %%xmm6 ,0x60(%[dst]) \n\t"
|
||||
"movaps 0x50(%[src]),%%xmm5 \n\t"
|
||||
"movntdq %%xmm5 ,0x50(%[dst]) \n\t"
|
||||
"movaps 0x40(%[src]),%%xmm4 \n\t"
|
||||
"movntdq %%xmm4 ,0x40(%[dst]) \n\t"
|
||||
"movaps 0x30(%[src]),%%xmm3 \n\t"
|
||||
"movntdq %%xmm3 ,0x30(%[dst]) \n\t"
|
||||
"movaps 0x20(%[src]),%%xmm2 \n\t"
|
||||
"movntdq %%xmm2 ,0x20(%[dst]) \n\t"
|
||||
"movaps 0x10(%[src]),%%xmm1 \n\t"
|
||||
"movntdq %%xmm1 ,0x10(%[dst]) \n\t"
|
||||
"movaps 0x00(%[src]),%%xmm0 \n\t"
|
||||
"movntdq %%xmm0 ,0x00(%[dst]) \n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
|
||||
: [dst]"+r" (dst),
|
||||
[src]"+r" (src)
|
||||
: [off]"r" (off),
|
||||
[end]"r" (end)
|
||||
: "eax",
|
||||
"xmm0",
|
||||
"xmm1",
|
||||
"xmm2",
|
||||
"xmm3",
|
||||
"xmm4",
|
||||
"xmm5",
|
||||
"xmm6",
|
||||
"xmm7",
|
||||
"memory"
|
||||
);
|
||||
|
||||
//copy any remaining bytes
|
||||
memcpy(dst, src, length & 0xF);
|
||||
}
|
||||
#else
|
||||
#define memcpySSE memcpy
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user