Compare commits
1505 Commits
0.5.3
...
b6cedd0f8f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6cedd0f8f | ||
|
|
f11e159c44 | ||
|
|
f9c14db284 | ||
|
|
2a13cb9e68 | ||
|
|
bfb66906b7 | ||
|
|
7872799eb8 | ||
|
|
4b8730e2a6 | ||
|
|
316e80d7e5 | ||
|
|
29bd67638c | ||
|
|
03a3113d34 | ||
|
|
9c8ccf7a9f | ||
|
|
cc6189e162 | ||
|
|
97a58c2988 | ||
|
|
3651ce2659 | ||
|
|
cc1a6debb1 | ||
|
|
fcdfd34914 | ||
|
|
9807da71dc | ||
|
|
08d6d60f57 | ||
|
|
574058bec2 | ||
|
|
b382b5a204 | ||
|
|
e51781ee1d | ||
|
|
84233e46cd | ||
|
|
8d26f5d78a | ||
|
|
137f110e1d | ||
|
|
25119a555c | ||
|
|
f569aa6e99 | ||
|
|
9823f3683c | ||
|
|
abe2930647 | ||
|
|
0a9143f00a | ||
|
|
819b46cc01 | ||
|
|
a3ce9e3b20 | ||
|
|
7526a43cf0 | ||
|
|
17699297f3 | ||
|
|
4460fc4308 | ||
|
|
8f6adbd77f | ||
|
|
a048b1aa21 | ||
|
|
4f84675024 | ||
|
|
6b7f4477b0 | ||
|
|
ec9a4ef88a | ||
|
|
2f7bd3a3d1 | ||
|
|
08114e2cbe | ||
|
|
0ef06e400d | ||
|
|
bdc313cd1f | ||
|
|
9b432f3fc9 | ||
|
|
7fd8bc5633 | ||
|
|
cc3f0226ef | ||
|
|
1f10a359b5 | ||
|
|
16e6437dc4 | ||
|
|
ed21ac0efb | ||
|
|
0e11072070 | ||
|
|
232dcf3a29 | ||
|
|
65329ccc49 | ||
|
|
7d38ab6b0e | ||
|
|
5b8a0f4ef2 | ||
|
|
ba05aaa3ef | ||
|
|
0317262f85 | ||
|
|
1f202e12fc | ||
|
|
c9ab557aaa | ||
|
|
d1facbbcd4 | ||
|
|
a6840ade89 | ||
|
|
57b89bef0a | ||
|
|
28943adac8 | ||
|
|
337174617f | ||
|
|
816f66a726 | ||
|
|
38e80f2ef9 | ||
|
|
c1924c1f8b | ||
|
|
12b7590041 | ||
|
|
d084f13edf | ||
|
|
4a51e86fd5 | ||
|
|
bbcc5ee5d1 | ||
|
|
0af197fa98 | ||
|
|
9f208ee7d2 | ||
|
|
15eb546c3d | ||
|
|
1787739128 | ||
|
|
6d5c55dd2b | ||
|
|
f9463d1149 | ||
|
|
d01366d069 | ||
|
|
ea4615ea65 | ||
|
|
928e798d86 | ||
|
|
0dd5f73508 | ||
|
|
72f444ffc5 | ||
|
|
10fc130dbc | ||
|
|
65395f8af2 | ||
|
|
8048e91117 | ||
|
|
19375e55b8 | ||
|
|
4549665e08 | ||
|
|
f8b42408e2 | ||
|
|
077cd0b9fa | ||
|
|
8a336b3a6e | ||
|
|
ab7a84c40f | ||
|
|
d92e43d215 | ||
|
|
974618fc18 | ||
|
|
88c2c3e12b | ||
|
|
26d35504c8 | ||
|
|
24eec2687c | ||
|
|
e06bb0f9f3 | ||
|
|
691cf421e3 | ||
|
|
0161126153 | ||
|
|
729cf52b68 | ||
|
|
0bf89c18f9 | ||
|
|
40d3f83719 | ||
|
|
a36a7fe4b2 | ||
|
|
717a8f2771 | ||
|
|
ca0389a7b6 | ||
|
|
25cad0bedf | ||
|
|
76a96e39bd | ||
|
|
29ef6c134e | ||
|
|
fc1e011246 | ||
|
|
dbe92512ae | ||
|
|
d8a771e24f | ||
|
|
8a47e86685 | ||
|
|
fc91a74d05 | ||
|
|
22d9c41357 | ||
|
|
0289f4c06e | ||
|
|
21ff75b92e | ||
|
|
df0913727c | ||
|
|
fb99136cc1 | ||
|
|
b68a380e35 | ||
|
|
6027fc61dd | ||
|
|
edbc6190e4 | ||
|
|
be55de91c1 | ||
|
|
7c55bab17b | ||
|
|
dec07ff3a5 | ||
|
|
5742b20fa3 | ||
|
|
ce4e964f09 | ||
|
|
d28a82ff73 | ||
|
|
ff39f8c458 | ||
|
|
f24cb8dc96 | ||
|
|
d273a7fcb4 | ||
|
|
4e813c2540 | ||
|
|
2dd298f772 | ||
|
|
4d9063f16c | ||
|
|
136b1561c1 | ||
|
|
5a933beb16 | ||
|
|
7e2a34a825 | ||
|
|
b4600a289d | ||
|
|
e74ce1f4cc | ||
|
|
ba75777c0d | ||
|
|
92c0b4493a | ||
|
|
803231c558 | ||
|
|
120909f8d6 | ||
|
|
f0f23cbd0b | ||
|
|
939a35ee97 | ||
|
|
18e1785e57 | ||
|
|
bef2709834 | ||
|
|
c063e2fc30 | ||
|
|
6e62ce15b5 | ||
|
|
19221e1fcc | ||
|
|
c25d6cb551 | ||
|
|
86920a3083 | ||
|
|
68c39290ec | ||
|
|
563f56d2a2 | ||
|
|
4174333b40 | ||
|
|
a74445f9e4 | ||
|
|
859d8cc86c | ||
|
|
d8d2f1c801 | ||
|
|
85308462eb | ||
|
|
d84f8b14c4 | ||
|
|
0f4889923d | ||
|
|
2093816f2d | ||
|
|
7de751f882 | ||
|
|
6da476aebb | ||
|
|
7140d25a87 | ||
|
|
cb413a99f0 | ||
|
|
e6d21518d2 | ||
|
|
19e647ae84 | ||
|
|
494f5c71bb | ||
|
|
c4ad80f3b9 | ||
|
|
460fa6c8e6 | ||
|
|
5a62ffe178 | ||
|
|
c867d7bdf2 | ||
|
|
167cf7c659 | ||
|
|
73a3ec3f63 | ||
|
|
f8981248dc | ||
|
|
b90b2469b5 | ||
|
|
2de9ca144d | ||
|
|
f1a89a1c55 | ||
|
|
3909aa4ab7 | ||
|
|
10e95f5388 | ||
|
|
21bb2af7ea | ||
|
|
9b7b4071bf | ||
|
|
2ec267fc4f | ||
|
|
e6b954e9e8 | ||
|
|
b2ce0f3934 | ||
|
|
01d3a91e40 | ||
|
|
cf0b87298d | ||
|
|
02c69c1686 | ||
|
|
9a53ffa6d0 | ||
|
|
d22e23937f | ||
|
|
7b820e10e0 | ||
|
|
d795fee579 | ||
|
|
80418162d4 | ||
|
|
32c83a6eee | ||
|
|
d0b8cf0275 | ||
|
|
74fa6da8c2 | ||
|
|
f199f96f7b | ||
|
|
bfc13549e9 | ||
|
|
7660b07fa8 | ||
|
|
b72054c2f7 | ||
|
|
9dcbc38231 | ||
|
|
dbb50cf580 | ||
|
|
da0782d036 | ||
|
|
778801992d | ||
|
|
f410ff0ed2 | ||
|
|
23685253a8 | ||
|
|
21ad2cab07 | ||
|
|
c5a14422ee | ||
|
|
c12deb4b56 | ||
|
|
792d98dfd5 | ||
|
|
12aa3b19d5 | ||
|
|
4b3f782ece | ||
|
|
0016a41474 | ||
|
|
e115981b9f | ||
|
|
94fbe58fb7 | ||
|
|
90962d0391 | ||
|
|
7092de8809 | ||
|
|
81a0a724ab | ||
|
|
34297feed4 | ||
|
|
4407f8ac68 | ||
|
|
3de691fa0d | ||
|
|
e7f388999d | ||
|
|
8d66913a8e | ||
|
|
b30b5b6474 | ||
|
|
19b8412d67 | ||
|
|
24d2555c5e | ||
|
|
322564bc42 | ||
|
|
12bff7aa9e | ||
|
|
d87a84db85 | ||
|
|
b38017eb1c | ||
|
|
2d99870ec2 | ||
|
|
6f7a4aa234 | ||
|
|
49add7e0f8 | ||
|
|
c2708078db | ||
|
|
a46e68f145 | ||
|
|
a4f8d46d69 | ||
|
|
94d6bc4bca | ||
|
|
ec78631691 | ||
|
|
cd8f9792ab | ||
|
|
fd9c868c40 | ||
|
|
6282500305 | ||
|
|
7b5bc6d236 | ||
|
|
3e45ec7353 | ||
|
|
5051f4e38c | ||
|
|
5532e3349a | ||
|
|
4ea7a09583 | ||
|
|
affba5a29f | ||
|
|
d486055fc8 | ||
|
|
52b7cd68c1 | ||
|
|
afdf4bf108 | ||
|
|
a33dbac984 | ||
|
|
ff48107b1a | ||
|
|
3afaaed1fb | ||
|
|
7ca6299fba | ||
|
|
cba7f5b801 | ||
|
|
d75ed8aeee | ||
|
|
b9b2984235 | ||
|
|
c2b7892e6c | ||
|
|
6ce07c0a4b | ||
|
|
008e217a79 | ||
|
|
5847e52fbc | ||
|
|
e9baa25b46 | ||
|
|
b6213e1ed8 | ||
|
|
59ac3a0bb8 | ||
|
|
9b63972878 | ||
|
|
4955e24f12 | ||
|
|
16fb654b98 | ||
|
|
eda88b3078 | ||
|
|
2435277e49 | ||
|
|
7caded7c95 | ||
|
|
df6cdf9a80 | ||
|
|
25be63ef2c | ||
|
|
b0a042369f | ||
|
|
d8d68dcf71 | ||
|
|
133fb661b3 | ||
|
|
d5f2b375a6 | ||
|
|
f280d3b64c | ||
|
|
e20261fa66 | ||
|
|
b6a41d417d | ||
|
|
80f3052c2b | ||
|
|
62060e0c04 | ||
|
|
f3f7c57f10 | ||
|
|
82d61909f6 | ||
|
|
03c68d1dc3 | ||
|
|
05f593e40c | ||
|
|
5719c6cfc2 | ||
|
|
1ba8ff06e1 | ||
|
|
fae5d09001 | ||
|
|
3eecb412c6 | ||
|
|
b022be49a1 | ||
|
|
7238eccfd2 | ||
|
|
e3b8ccff9e | ||
|
|
c1aa3c9d4d | ||
|
|
00345c94a3 | ||
|
|
20001e6a26 | ||
|
|
c06fdc7760 | ||
|
|
8e28eba959 | ||
|
|
47ff1a2dd8 | ||
|
|
00ff0f532f | ||
|
|
2e0732c75b | ||
|
|
842247de54 | ||
|
|
52840ce8ae | ||
|
|
f640d2574b | ||
|
|
8598aad9e2 | ||
|
|
5dc82aadc7 | ||
|
|
2f8411a658 | ||
|
|
fcce9b62d5 | ||
|
|
5a077d2f52 | ||
|
|
36f8ea8df0 | ||
|
|
fe623d93a1 | ||
|
|
d41b8a7c24 | ||
|
|
f016a82a32 | ||
|
|
d872aa4a6c | ||
|
|
2c7262ced4 | ||
|
|
01e21ea212 | ||
|
|
0eae04ab83 | ||
|
|
e42afcb434 | ||
|
|
fad4be419a | ||
|
|
9768d17b9b | ||
|
|
e36bae2ab6 | ||
|
|
f8b5b1db9c | ||
|
|
9ccf1a31bc | ||
|
|
36d23b5dc2 | ||
|
|
9d27335d7d | ||
|
|
a913cee7a4 | ||
|
|
eeeba3d2b7 | ||
|
|
b8a56776da | ||
|
|
0e9fe58c06 | ||
|
|
6cf7635005 | ||
|
|
6c7ea3a16d | ||
|
|
4ded0d03d0 | ||
|
|
ac45ad740d | ||
|
|
f010c840e6 | ||
|
|
90b59908c6 | ||
|
|
13c6693cdd | ||
|
|
cd56c960a6 | ||
|
|
a51423a4d6 | ||
|
|
80a25ec71c | ||
|
|
51209179d1 | ||
|
|
b3937caa10 | ||
|
|
8927ba7c73 | ||
|
|
5f4c867618 | ||
|
|
fced4178be | ||
|
|
da0944c814 | ||
|
|
7aa14219b8 | ||
|
|
09a3494183 | ||
|
|
8924d81e0a | ||
|
|
b599fbf88d | ||
|
|
578a72f560 | ||
|
|
6b1e298d43 | ||
|
|
698665c4cc | ||
|
|
caa3e4d07a | ||
|
|
7606baa20b | ||
|
|
fde6be3f97 | ||
|
|
abdc70121d | ||
|
|
879a0524fc | ||
|
|
a529b34f99 | ||
|
|
e8daeb5f30 | ||
|
|
f610e8ba1e | ||
|
|
b3245c967b | ||
|
|
0af9da2214 | ||
|
|
db68f80048 | ||
|
|
b0efd80e42 | ||
|
|
3e12e0b84d | ||
|
|
5465a45dc6 | ||
|
|
53bd7d6ae2 | ||
|
|
d66751b6ac | ||
|
|
95de6d0afc | ||
|
|
147daa7681 | ||
|
|
ece925858a | ||
|
|
657b05d077 | ||
|
|
cf3bceeb46 | ||
|
|
6735e5eaaa | ||
|
|
7b9e71df40 | ||
|
|
fae61f3d87 | ||
|
|
a57419150e | ||
|
|
378257b7bf | ||
|
|
885b92a0a1 | ||
|
|
58371c36d3 | ||
|
|
d39064b209 | ||
|
|
f497da7967 | ||
|
|
03931cb232 | ||
|
|
4eeb02d9d4 | ||
|
|
8ff5ae3555 | ||
|
|
053a5e9dbe | ||
|
|
5a1a88bf33 | ||
|
|
f19b18d806 | ||
|
|
f6e4701d6b | ||
|
|
c3d686e472 | ||
|
|
6c7ff870e8 | ||
|
|
38bac83ddd | ||
|
|
4d8c77cf3e | ||
|
|
0a147697d2 | ||
|
|
21837e7464 | ||
|
|
d747962e24 | ||
|
|
49f09d1b3a | ||
|
|
783d6e69b1 | ||
|
|
6452ff78c0 | ||
|
|
a430d39849 | ||
|
|
33c222555f | ||
|
|
00f7e0fe62 | ||
|
|
44a31ede74 | ||
|
|
fb3ee2aa8c | ||
|
|
58e5dd9186 | ||
|
|
27ec46c64e | ||
|
|
12a5d777e5 | ||
|
|
276a94f9e8 | ||
|
|
ccc3c86900 | ||
|
|
124415363f | ||
|
|
de850b39f2 | ||
|
|
379f73b6ca | ||
|
|
159b778fa9 | ||
|
|
007f7a0ce1 | ||
|
|
54fa642693 | ||
|
|
2fbc6f9193 | ||
|
|
262c6fd8ab | ||
|
|
7fcb53c7d0 | ||
|
|
d0e1101bfb | ||
|
|
a8bb4ae6d1 | ||
|
|
d743307e59 | ||
|
|
e92e9eb45c | ||
|
|
aaefa356ae | ||
|
|
3a7da2bc98 | ||
|
|
94b211e3b7 | ||
|
|
9ff5b90605 | ||
|
|
a033b74f7f | ||
|
|
bb4e81b00a | ||
|
|
5f2e0b79cd | ||
|
|
64a7fef7c1 | ||
|
|
09dbc5c84e | ||
|
|
e71e0791bc | ||
|
|
2a4be39c9a | ||
|
|
3cde191afb | ||
|
|
02a8d9a243 | ||
|
|
7f4e313a78 | ||
|
|
44e3d90dcb | ||
|
|
945eb3ccc6 | ||
|
|
793008852a | ||
|
|
7e791ee5e4 | ||
|
|
1413490579 | ||
|
|
57154e5d0b | ||
|
|
57a1556e23 | ||
|
|
e248e92ca1 | ||
|
|
fa5adcf08b | ||
|
|
87a51afd99 | ||
|
|
4efaa1f350 | ||
|
|
1d329600af | ||
|
|
c2a0e51984 | ||
|
|
ebd59e38ce | ||
|
|
f75b384c17 | ||
|
|
5419622c74 | ||
|
|
ab40011954 | ||
|
|
1b658e9b40 | ||
|
|
49605f9c23 | ||
|
|
d9b6b808f7 | ||
|
|
1607dd329d | ||
|
|
74337b2699 | ||
|
|
d6a684bbe7 | ||
|
|
feeb997f62 | ||
|
|
26da3bf9a8 | ||
|
|
82046afd9f | ||
|
|
97704deea0 | ||
|
|
64b2a18ff3 | ||
|
|
cf16edceec | ||
|
|
f6008737d1 | ||
|
|
61de8c4717 | ||
|
|
83ad83e656 | ||
|
|
2afb13c580 | ||
|
|
e48a963503 | ||
|
|
c846a0626f | ||
|
|
a6b1d09ff1 | ||
|
|
3603e146cc | ||
|
|
9ab597c0e9 | ||
|
|
3251e9f845 | ||
|
|
3e7b24a3de | ||
|
|
6989cd4d40 | ||
|
|
c7205a512e | ||
|
|
c0befa0f49 | ||
|
|
2223024383 | ||
|
|
216d9a1686 | ||
|
|
1f9bff6182 | ||
|
|
0f7d42ab83 | ||
|
|
60334f24f1 | ||
|
|
d7d099d2d7 | ||
|
|
514d4170be | ||
|
|
6b1c1853b8 | ||
|
|
ced318637f | ||
|
|
c2c7c37ef6 | ||
|
|
7e5041eac5 | ||
|
|
4b7db87444 | ||
|
|
22aba74ed9 | ||
|
|
5283dd2f9e | ||
|
|
8bc69ba0a4 | ||
|
|
90207c6184 | ||
|
|
678590fc4f | ||
|
|
9170aba906 | ||
|
|
59a92bb172 | ||
|
|
f12cf8a6ce | ||
|
|
26cb75f93f | ||
|
|
e8b05cd7c2 | ||
|
|
4930c07e9c | ||
|
|
39d4002491 | ||
|
|
e095b139ae | ||
|
|
088cf97ebf | ||
|
|
be90b30b8f | ||
|
|
0ff3041e89 | ||
|
|
9d2c2c0e47 | ||
|
|
b35f2b7cb4 | ||
|
|
1988c5fe9a | ||
|
|
aad99ce3a4 | ||
|
|
1d7209693a | ||
|
|
4017732d71 | ||
|
|
c09f2a1121 | ||
|
|
9467a0700a | ||
|
|
ef74faeef4 | ||
|
|
2891ad7efe | ||
|
|
364ffc4659 | ||
|
|
8aa82274ff | ||
|
|
71ca037ca8 | ||
|
|
341aac8ff7 | ||
|
|
99ccfb8e94 | ||
|
|
4ed884de55 | ||
|
|
f9caa75aa7 | ||
|
|
37a7bc43fd | ||
|
|
cdd6ac1d8b | ||
|
|
235e0efc09 | ||
|
|
2f4f93d3ba | ||
|
|
f25a1b70f3 | ||
|
|
6ec8edd5e2 | ||
|
|
8beeced7c2 | ||
|
|
f4add84a13 | ||
|
|
c507d0ed7e | ||
|
|
3ff193f42d | ||
|
|
8104050658 | ||
|
|
b53f0b4c1b | ||
|
|
7698c05dd5 | ||
|
|
18734345a1 | ||
|
|
ad54ee4bda | ||
|
|
f8a4b7b1db | ||
|
|
e6966df9ac | ||
|
|
8e17fc0edb | ||
|
|
2cdb8c022d | ||
|
|
04822346a6 | ||
|
|
6fcfc2da34 | ||
|
|
62c7b8cdc1 | ||
|
|
d2d899a8d0 | ||
|
|
921e09227f | ||
|
|
39c274a515 | ||
|
|
5db65b6e6e | ||
|
|
c71fc5575f | ||
|
|
2ebf264feb | ||
|
|
aa20cf5138 | ||
|
|
5f96a8991a | ||
|
|
47e0d6ae19 | ||
|
|
e3e2d8d2f6 | ||
|
|
f57e057f2a | ||
|
|
dc0df8cc61 | ||
|
|
c30d7a2089 | ||
|
|
03bffb3319 | ||
|
|
65dc93ed37 | ||
|
|
8ed04b9c6e | ||
|
|
e6a9327bae | ||
|
|
658c506653 | ||
|
|
ad4a4281b4 | ||
|
|
6b0070ec56 | ||
|
|
c9cf6baf4b | ||
|
|
85a25a0a39 | ||
|
|
e192d254f9 | ||
|
|
9e2b9d7cf4 | ||
|
|
cd545213e4 | ||
|
|
935d2ff02c | ||
|
|
b7d6218b89 | ||
|
|
d86754b0e6 | ||
|
|
b97674a404 | ||
|
|
f91522fc14 | ||
|
|
9b4ef00278 | ||
|
|
43270c7763 | ||
|
|
9a98fb399c | ||
|
|
c255b0249f | ||
|
|
b0e71f6f18 | ||
|
|
7e59377daf | ||
|
|
73aa369e6d | ||
|
|
731d7e5d6b | ||
|
|
bc5e1c7df9 | ||
|
|
1a15f9b581 | ||
|
|
136d6052d8 | ||
|
|
811b270bae | ||
|
|
7870e3cfce | ||
|
|
4874af1d5a | ||
|
|
1b7ead0479 | ||
|
|
ac97984314 | ||
|
|
8c778e8afb | ||
|
|
2b6bbce1d9 | ||
|
|
b8e0a9c1dd | ||
|
|
4c3c3de065 | ||
|
|
a74801a0af | ||
|
|
cbe8217790 | ||
|
|
e1cdf34955 | ||
|
|
fefc20c52a | ||
|
|
ad1e574cfe | ||
|
|
c25d4b7dad | ||
|
|
207aa88daf | ||
|
|
3be08a3e63 | ||
|
|
7d91ffbafa | ||
|
|
128ba084ad | ||
|
|
0defff8f7c | ||
|
|
6e3497e4c4 | ||
|
|
1c309b2c89 | ||
|
|
35507e7fbb | ||
|
|
bc439829cf | ||
|
|
93f433f388 | ||
|
|
f9e99e2a33 | ||
|
|
94dcf7c3f3 | ||
|
|
c3bb29182e | ||
|
|
b11f6d5b3b | ||
|
|
d2b900f7c3 | ||
|
|
bf2b5d8882 | ||
|
|
e7878bdb8f | ||
|
|
0670550521 | ||
|
|
f5df923c51 | ||
|
|
f1f62816b5 | ||
|
|
db462690b3 | ||
|
|
c28685c700 | ||
|
|
1f1780597c | ||
|
|
1590251dad | ||
|
|
d25c17342b | ||
|
|
e105022185 | ||
|
|
16931917b7 | ||
|
|
e2e316a079 | ||
|
|
1dd2151a20 | ||
|
|
c7367ad46a | ||
|
|
21045411e7 | ||
|
|
128e8834e8 | ||
|
|
7433772606 | ||
|
|
8967f5f090 | ||
|
|
3b51a6e2a9 | ||
|
|
c5cb635b4e | ||
|
|
1e9f8d707e | ||
|
|
69a0aa4bd8 | ||
|
|
581fa88055 | ||
|
|
f991ae5aed | ||
|
|
44825ece04 | ||
|
|
eac2c5c020 | ||
|
|
a593e97227 | ||
|
|
ecad786f50 | ||
|
|
9012d33c05 | ||
|
|
268751815f | ||
|
|
6529b170e6 | ||
|
|
5ce465cb30 | ||
|
|
48f1df2fd6 | ||
|
|
3e6ddf560a | ||
|
|
0051533ac8 | ||
|
|
e69ac7ca28 | ||
|
|
9c8abb8edf | ||
|
|
9ee434f275 | ||
|
|
4826d9fbf0 | ||
|
|
3fd7b8ed3c | ||
|
|
ebc8d483d9 | ||
|
|
9821d3595a | ||
|
|
f21be9d10c | ||
|
|
d221036cde | ||
|
|
1dbff48ebb | ||
|
|
43e56fc433 | ||
|
|
b3b562f4bb | ||
|
|
784ac996d3 | ||
|
|
fb6a95078d | ||
|
|
189e7b8bc9 | ||
|
|
55967ad27c | ||
|
|
e2c82af4d6 | ||
|
|
8712923bec | ||
|
|
416635179b | ||
|
|
c1b635e036 | ||
|
|
2860d8f1de | ||
|
|
c1fb07b4c7 | ||
|
|
5036c2231c | ||
|
|
c848666e17 | ||
|
|
55aef98a30 | ||
|
|
7cbbf799dc | ||
|
|
30a4e0297c | ||
|
|
5f68f51693 | ||
|
|
8e6aaf29e0 | ||
|
|
cde0e74a2e | ||
|
|
2a573cbab3 | ||
|
|
941275a1b9 | ||
|
|
c5884ec498 | ||
|
|
da06cf52ec | ||
|
|
07e8f489c1 | ||
|
|
baed2ac031 | ||
|
|
cdab138b2f | ||
|
|
06524edfb3 | ||
|
|
0e40550427 | ||
|
|
e08b6ade9e | ||
|
|
36bc4944f9 | ||
|
|
a0be95d634 | ||
|
|
faf8d4c4ad | ||
|
|
8af740caa8 | ||
|
|
69fa3521f9 | ||
|
|
991a96d3dc | ||
|
|
b10bf06305 | ||
|
|
7f54b30fbe | ||
|
|
ee79043536 | ||
|
|
c9e6611b92 | ||
|
|
73d128d89a | ||
|
|
5a240acd86 | ||
|
|
7dc4a5cf87 | ||
|
|
6d835297b2 | ||
|
|
b44c29e235 | ||
|
|
8da9a9cf27 | ||
|
|
a3617626f7 | ||
|
|
e44832ea9e | ||
|
|
f841e78dcf | ||
|
|
e7a8d48cca | ||
|
|
69e8d0e32f | ||
|
|
830d1a6bf9 | ||
|
|
e9b72b442a | ||
|
|
77ac7eca18 | ||
|
|
86d4198ffd | ||
|
|
37dfe31ac2 | ||
|
|
584e1c48e6 | ||
|
|
15766ceb97 | ||
|
|
6e79f28b69 | ||
|
|
e9632d206b | ||
|
|
a0f55bfcb5 | ||
|
|
3c32f1da6e | ||
|
|
7e13c1b22a | ||
|
|
2fc52e673f | ||
|
|
48001a660b | ||
|
|
5a6daf79b6 | ||
|
|
ae4fd9f7df | ||
|
|
7dfa8776fd | ||
|
|
a836796fcc | ||
|
|
fb131972d4 | ||
|
|
140ce358fa | ||
|
|
dd92f2dccb | ||
|
|
d62004eadf | ||
|
|
abc21e9692 | ||
|
|
a13b0d5d91 | ||
|
|
12f8c75c2d | ||
|
|
bdc1920166 | ||
|
|
8cb0d57ffe | ||
|
|
7344689263 | ||
|
|
f521ca1118 | ||
|
|
6712f1383e | ||
|
|
d756fd4a29 | ||
|
|
e070ef1b7f | ||
|
|
ea7786a002 | ||
|
|
3eec07fac1 | ||
|
|
46afa76af8 | ||
|
|
6cc1ba64d8 | ||
|
|
225596481f | ||
|
|
4ef3b3b332 | ||
|
|
e7ac768b5b | ||
|
|
b1e8833daa | ||
|
|
155d71dc80 | ||
|
|
94ebf17134 | ||
|
|
d6fe2edf0d | ||
|
|
867cd7e583 | ||
|
|
1a2471a32d | ||
|
|
057dd9c01d | ||
|
|
5a2c0e15e9 | ||
|
|
ae5ae24f6f | ||
|
|
7a2f3fe840 | ||
|
|
b46788c81a | ||
|
|
3c1f37e5f9 | ||
|
|
d8d4322b2e | ||
|
|
1613e9ce46 | ||
|
|
aee3c8db1b | ||
|
|
becaeedff1 | ||
|
|
c9c6651368 | ||
|
|
d77371912b | ||
|
|
93cb12be89 | ||
|
|
4ac2bd8e92 | ||
|
|
34d52c975e | ||
|
|
85194c7f4f | ||
|
|
af009e03a0 | ||
|
|
cef7379c07 | ||
|
|
f1e75d8593 | ||
|
|
c5a194e98c | ||
|
|
7858033628 | ||
|
|
f2405e02f6 | ||
|
|
452221daa5 | ||
|
|
91f551c2d8 | ||
|
|
2ca1763280 | ||
|
|
da4a8333f7 | ||
|
|
c0b08f3219 | ||
|
|
c273e9125c | ||
|
|
7244b95844 | ||
|
|
a298b6587d | ||
|
|
d87f6b74f3 | ||
|
|
1f0b145740 | ||
|
|
f6d528d36d | ||
|
|
ea6502a282 | ||
|
|
8a36a94e73 | ||
|
|
1604eaa239 | ||
|
|
fec2fb7ce6 | ||
|
|
d2e3b854aa | ||
|
|
f8e8d33c61 | ||
|
|
dd76135efd | ||
|
|
d7be7a69ab | ||
|
|
fd942b28c6 | ||
|
|
7c850b0405 | ||
|
|
e3bb95b3dd | ||
|
|
662d8bcfda | ||
|
|
3c0b2c64e1 | ||
|
|
ed7d42cf6d | ||
|
|
8852914ceb | ||
|
|
944778175a | ||
|
|
81704c08c9 | ||
|
|
810059e6da | ||
|
|
8d95bd16fd | ||
|
|
4600253d1e | ||
|
|
d695aa9f57 | ||
|
|
7867aac55f | ||
|
|
e26563c3d6 | ||
|
|
07ad262857 | ||
|
|
cb0abd51db | ||
|
|
cc69baf0dd | ||
|
|
852a8d04c9 | ||
|
|
ffdacb3850 | ||
|
|
062e8357fa | ||
|
|
07dece9cd7 | ||
|
|
d628a513d9 | ||
|
|
6012ad9b1e | ||
|
|
c3e618de36 | ||
|
|
6b9795fe96 | ||
|
|
f7da3a347d | ||
|
|
cf020d06c6 | ||
|
|
137b5ca4f9 | ||
|
|
82be9326a8 | ||
|
|
222282dced | ||
|
|
c7a2086850 | ||
|
|
168ac5065d | ||
|
|
158ea1984f | ||
|
|
f66d73e385 | ||
|
|
252ed1c6f2 | ||
|
|
69e35167bc | ||
|
|
2140075133 | ||
|
|
80469ead18 | ||
|
|
77dc563219 | ||
|
|
9d7f0b22f7 | ||
|
|
56b17116e3 | ||
|
|
c71791b649 | ||
|
|
c8f8fcf9d3 | ||
|
|
d41a85f4a1 | ||
|
|
3c465f9a7a | ||
|
|
c25427cf4a | ||
|
|
1c8575e40c | ||
|
|
e512eab1e8 | ||
|
|
4dd8ceb245 | ||
|
|
07e2bd4bcf | ||
|
|
05eb62bb35 | ||
|
|
0df3342757 | ||
|
|
0615f38a26 | ||
|
|
1fe63b68ee | ||
|
|
1e7dbb5331 | ||
|
|
0ddc03b7c0 | ||
|
|
becc3d0953 | ||
|
|
48b1bfaebd | ||
|
|
c043026764 | ||
|
|
0aed9fc306 | ||
|
|
739559783b | ||
|
|
0f4076acab | ||
|
|
9692ac3f4d | ||
|
|
365a333b1d | ||
|
|
f039755bde | ||
|
|
d314f1bae2 | ||
|
|
aae1915519 | ||
|
|
220df8918c | ||
|
|
3e41655902 | ||
|
|
54a23f5ae7 | ||
|
|
edffcf8902 | ||
|
|
dd55f41264 | ||
|
|
8fef0052a3 | ||
|
|
f2db10d29a | ||
|
|
33756c775c | ||
|
|
ef65dd8cc6 | ||
|
|
74d0d851ca | ||
|
|
49ebc17334 | ||
|
|
548aba5b7c | ||
|
|
dc1f1e02a1 | ||
|
|
d10b809687 | ||
|
|
ad438ef339 | ||
|
|
8a7a6ed4f5 | ||
|
|
c6097e0397 | ||
|
|
0f3e856438 | ||
|
|
5b6ec81cee | ||
|
|
e888bfbc8d | ||
|
|
b04ab65258 | ||
|
|
46b707f246 | ||
|
|
f2a6073829 | ||
|
|
e5926a5371 | ||
|
|
86c4581a50 | ||
|
|
a80074dc21 | ||
|
|
3effdd1408 | ||
|
|
b927c55216 | ||
|
|
49c590a9b5 | ||
|
|
8df6a2dd83 | ||
|
|
fe66c95a29 | ||
|
|
c080959f64 | ||
|
|
f27a88787d | ||
|
|
5853495125 | ||
|
|
f49d94948d | ||
|
|
82dad0fad3 | ||
|
|
fea99498af | ||
|
|
5582ee8ed8 | ||
|
|
f4b6db9404 | ||
|
|
5c92362aae | ||
|
|
667ea9fa64 | ||
|
|
b710750035 | ||
|
|
491ff01c79 | ||
|
|
4981145dd8 | ||
|
|
e9497b03f4 | ||
|
|
d76e518db1 | ||
|
|
a2906c6aa5 | ||
|
|
e0676c66a0 | ||
|
|
0b12c5a169 | ||
|
|
39b61fe331 | ||
|
|
83e77681d9 | ||
|
|
95a69937bd | ||
|
|
c355bd7569 | ||
|
|
559a186cd9 | ||
|
|
7e81ef37d7 | ||
|
|
2e167d260d | ||
|
|
aa50d818ec | ||
|
|
b9dd0a3877 | ||
|
|
e03ef7e214 | ||
|
|
caa05d739f | ||
|
|
f580673dea | ||
|
|
297d271e63 | ||
|
|
a28e4be5a3 | ||
|
|
6b672acdc7 | ||
|
|
26f5368264 | ||
|
|
fc5b967973 | ||
|
|
3cf497fa91 | ||
|
|
f50411e9db | ||
|
|
74eca2e527 | ||
|
|
6200e78e93 | ||
|
|
904c122ee0 | ||
|
|
741afaea18 | ||
|
|
6cf86d80e2 | ||
|
|
886305ec13 | ||
|
|
ab040f5268 | ||
|
|
8404e0f670 | ||
|
|
3605ae14b5 | ||
|
|
bce372bd79 | ||
|
|
715c48b7eb | ||
|
|
eb9a3c2ad1 | ||
|
|
b261829aea | ||
|
|
595be6b7b8 | ||
|
|
9ed76ae4da | ||
|
|
2ae0ef40d4 | ||
|
|
f5f7d3c154 | ||
|
|
a9ab4dbe38 | ||
|
|
5c3c26851c | ||
|
|
2b3696aab1 | ||
|
|
35ec0c9bcf | ||
|
|
4f915d6708 | ||
|
|
f4eb8b246b | ||
|
|
1a80e52241 | ||
|
|
afa27a04fe | ||
|
|
b82c83de5e | ||
|
|
625e2305ba | ||
|
|
6b4781b7d5 | ||
|
|
7acffabdd8 | ||
|
|
e6f2aa2399 | ||
|
|
ece7e04c7c | ||
|
|
0b4d273e08 | ||
|
|
81e8d6d99c | ||
|
|
e96444671e | ||
|
|
e52785a8b4 | ||
|
|
ddccc5ff6b | ||
|
|
8bbcef585f | ||
|
|
e58041227b | ||
|
|
3678e8fb27 | ||
|
|
1146a9125b | ||
|
|
aab0c055ed | ||
|
|
685082e212 | ||
|
|
1bb8b636b9 | ||
|
|
43c51c3b82 | ||
|
|
b9ed64fba2 | ||
|
|
ceaac03adf | ||
|
|
3085a837c8 | ||
|
|
2e5e2c8430 | ||
|
|
48df5c0eb1 | ||
|
|
fcad6766c3 | ||
|
|
c91a4670de | ||
|
|
5e7c325874 | ||
|
|
6b8a1428d7 | ||
|
|
5bd81db37e | ||
|
|
9834baedfa | ||
|
|
0b4c42859d | ||
|
|
a92b45ae26 | ||
|
|
1e2096e691 | ||
|
|
3df6ffe280 | ||
|
|
d4f370c071 | ||
|
|
b894ea866a | ||
|
|
3a300a4ca3 | ||
|
|
0ad844d10e | ||
|
|
d310a45f72 | ||
|
|
57c31804b4 | ||
|
|
5fb70a9b9a | ||
|
|
d402143989 | ||
|
|
ad8d799cf6 | ||
|
|
cec705264d | ||
|
|
28a2e61361 | ||
|
|
6017215ada | ||
|
|
8f3128e4b3 | ||
|
|
109e6f590a | ||
|
|
ac86d75e10 | ||
|
|
8f0491ea57 | ||
|
|
6403e51ba7 | ||
|
|
e0522608a4 | ||
|
|
4b9a230803 | ||
|
|
fc5246efaa | ||
|
|
4eebfbb89f | ||
|
|
353d2c4744 | ||
|
|
2718e83132 | ||
|
|
7547d1179d | ||
|
|
61e89286bc | ||
|
|
53ae715816 | ||
|
|
8cb37dba36 | ||
|
|
4426f70de7 | ||
|
|
f0ca13150f | ||
|
|
780a20689c | ||
|
|
28f9ed1d8d | ||
|
|
2b5b8ad02c | ||
|
|
d5092b1765 | ||
|
|
fda62314f9 | ||
|
|
17018c137f | ||
|
|
8838c19c39 | ||
|
|
f02a99a4e2 | ||
|
|
7b26b0f23e | ||
|
|
0e9984827a | ||
|
|
033d41863a | ||
|
|
bc540044ac | ||
|
|
76a2535da3 | ||
|
|
ff48877d16 | ||
|
|
4b8efabc5f | ||
|
|
c79be090df | ||
|
|
626eab7e8f | ||
|
|
c103b7d883 | ||
|
|
cde055e29b | ||
|
|
1cb448c42e | ||
|
|
3d05444f30 | ||
|
|
7a551189d9 | ||
|
|
b885e70fed | ||
|
|
0a27c14041 | ||
|
|
eb8e33e311 | ||
|
|
2d44a60b90 | ||
|
|
135b6a5702 | ||
|
|
706c72fda8 | ||
|
|
fb7bdba388 | ||
|
|
733d08638d | ||
|
|
cb3cca8a64 | ||
|
|
a3a581794e | ||
|
|
f921e7610c | ||
|
|
8deb364025 | ||
|
|
3a9c6f56bf | ||
|
|
a612154123 | ||
|
|
bbc5e50491 | ||
|
|
6e3dd8165e | ||
|
|
a7689a8f54 | ||
|
|
731a1af1a6 | ||
|
|
f53ebd4389 | ||
|
|
a2d61cc30a | ||
|
|
baa6ddb401 | ||
|
|
4455aa6709 | ||
|
|
315a8534d5 | ||
|
|
d77bd4034d | ||
|
|
fa71797ed2 | ||
|
|
8c63552573 | ||
|
|
a18d53c637 | ||
|
|
ffe05368e8 | ||
|
|
923d84f378 | ||
|
|
e5334aae0a | ||
|
|
4675be7e2a | ||
|
|
bf3fc61ef7 | ||
|
|
ebd9fab312 | ||
|
|
d359cf33d1 | ||
|
|
14bab1e299 | ||
|
|
4c4ad144b9 | ||
|
|
1157c0b1c5 | ||
|
|
68b2c5e0c1 | ||
|
|
b97fd06f2a | ||
|
|
51f0f5bd66 | ||
|
|
66f445997d | ||
|
|
addd199407 | ||
|
|
73d4f7c1ea | ||
|
|
25fc5562db | ||
|
|
4d52bcb5b3 | ||
|
|
3d2de560b0 | ||
|
|
809e30d906 | ||
|
|
ef9e41f20d | ||
|
|
1b4849f214 | ||
|
|
26cc67cd41 | ||
|
|
e123d139e4 | ||
|
|
a8abd52afb | ||
|
|
091e99f21b | ||
|
|
7b7875e23f | ||
|
|
b6593c2a83 | ||
|
|
5ac7887360 | ||
|
|
ed7627af6f | ||
|
|
1ea9fc54b2 | ||
|
|
3819571ec0 | ||
|
|
94f131fc57 | ||
|
|
3c20314aab | ||
|
|
1506d36407 | ||
|
|
aa4b2967c7 | ||
|
|
a42881d31f | ||
|
|
6a3ff2f235 | ||
|
|
fc4e3dc362 | ||
|
|
d6c689c5bb | ||
|
|
8676e9b900 | ||
|
|
c271cad9aa | ||
|
|
8e3bf786c0 | ||
|
|
a6ba694fbd | ||
|
|
790ccc320e | ||
|
|
26e951e59b | ||
|
|
a97581f5d7 | ||
|
|
5bf280ca4d | ||
|
|
fe00baa701 | ||
|
|
d3cb1d7f42 | ||
|
|
593363732a | ||
|
|
d00f4cf715 | ||
|
|
cac31dbb21 | ||
|
|
eb1fa6ca04 | ||
|
|
0ac515ea5a | ||
|
|
190e2a4952 | ||
|
|
0857b1bab6 | ||
|
|
f3e42fdc95 | ||
|
|
d617f3308a | ||
|
|
27cec85443 | ||
|
|
63f7cab508 | ||
|
|
ce0ac1bee1 | ||
|
|
2c2584c8df | ||
|
|
14fd4d96c3 | ||
|
|
dd7a63413c | ||
|
|
6d0c2301c1 | ||
|
|
8bf8f05add | ||
|
|
bd773d54c6 | ||
|
|
f4c52b7ed3 | ||
|
|
5b1504c8f6 | ||
|
|
06187b9a1a | ||
|
|
7fb6e57829 | ||
|
|
5ec954dbb5 | ||
|
|
a6bc30cf62 | ||
|
|
df165252fa | ||
|
|
da9c94f675 | ||
|
|
031cef6357 | ||
|
|
ef5f3efd2e | ||
|
|
bc8c4e3c7b | ||
|
|
f5da4c8bc2 | ||
|
|
644741a1ab | ||
|
|
09f46e7a27 | ||
|
|
db4e1d214f | ||
|
|
79433dd45c | ||
|
|
fe72c9b829 | ||
|
|
b37d22ba47 | ||
|
|
7fb08e618f | ||
|
|
63c6f1169b | ||
|
|
0eff8fd24d | ||
|
|
818e554d35 | ||
|
|
5a18dbaf37 | ||
|
|
ddd9bb4e99 | ||
|
|
38a2aa90e0 | ||
|
|
139137770c | ||
|
|
789bf1bd00 | ||
|
|
563e762dde | ||
|
|
28da5f8f39 | ||
|
|
5c42061fd9 | ||
|
|
843224ca35 | ||
|
|
e47e76962b | ||
|
|
2f0e4e3212 | ||
|
|
fb3e1d0d25 | ||
|
|
e9b7e55570 | ||
|
|
8c206898f0 | ||
|
|
a9c9683b8b | ||
|
|
4f43ddf088 | ||
|
|
a9c8b67975 | ||
|
|
d1b7073ff9 | ||
|
|
58afcacab9 | ||
|
|
5eddfcf196 | ||
|
|
9a87764949 | ||
|
|
fc4e40fba3 | ||
|
|
e8acfc1c26 | ||
|
|
eaadc210ae | ||
|
|
8002f3164c | ||
|
|
48f92bc52b | ||
|
|
d1e833e0a1 | ||
|
|
c5f0be2b32 | ||
|
|
dbcf3bb0ea | ||
|
|
e7a79f6cdc | ||
|
|
63b043dc4b | ||
|
|
d2a576c99c | ||
|
|
fc91e7cbdd | ||
|
|
0555361a57 | ||
|
|
c923815a01 | ||
|
|
7a4d2ac027 | ||
|
|
442e1096be | ||
|
|
6eaf8852ae | ||
|
|
3612fca707 | ||
|
|
4736d403a1 | ||
|
|
a18fd3177c | ||
|
|
5930b0f8fe | ||
|
|
1c7c64db59 | ||
|
|
9bc780bcda | ||
|
|
b3f89e0464 | ||
|
|
1de4822c67 | ||
|
|
c846e4072a | ||
|
|
c4f26bd500 | ||
|
|
041c01135a | ||
|
|
aa904f26ad | ||
|
|
ff99d37eb6 | ||
|
|
e8a500dc99 | ||
|
|
9f4f247cd2 | ||
|
|
e1ac930dd6 | ||
|
|
c20ed94f46 | ||
|
|
4efe754a8d | ||
|
|
bb83f7fcb7 | ||
|
|
79fa6082b0 | ||
|
|
f2ecc88955 | ||
|
|
7253c1ec1a | ||
|
|
cf32c9fc12 | ||
|
|
3086735be1 | ||
|
|
5a54e84dd8 | ||
|
|
1ef26c0c95 | ||
|
|
887142079b | ||
|
|
b75ea00c0d | ||
|
|
319fbfa84d | ||
|
|
3874252797 | ||
|
|
5dfc45af5f | ||
|
|
1f203801db | ||
|
|
291410a2b3 | ||
|
|
dfc4937688 | ||
|
|
a0b763ab71 | ||
|
|
ad36ac5cd9 | ||
|
|
cd40d6d7e8 | ||
|
|
ec4214ebf8 | ||
|
|
a403d40b6c | ||
|
|
7dcfc97f33 | ||
|
|
5ea056a483 | ||
|
|
cd1702bb53 | ||
|
|
6ff266581a | ||
|
|
6b7d108407 | ||
|
|
473e24bcd7 | ||
|
|
1f5056bf15 | ||
|
|
61fa062794 | ||
|
|
0e48cf4505 | ||
|
|
b606f479e9 | ||
|
|
2ccbf1ec12 | ||
|
|
ac6e84bb1c | ||
|
|
11d12c1f29 | ||
|
|
c9707e7335 | ||
|
|
2c0be68a3c | ||
|
|
2add317106 | ||
|
|
60ec11982a | ||
|
|
b2284cf1b4 | ||
|
|
e87ef2774b | ||
|
|
7a9fcaefd6 | ||
|
|
2333a7a11a | ||
|
|
2a7857c499 | ||
|
|
e892dc1eb5 | ||
|
|
9c8d1f31f6 | ||
|
|
048db7a44b | ||
|
|
1d94d494b6 | ||
|
|
c6ac35addb | ||
|
|
9ec279754b | ||
|
|
bdcf28c5da | ||
|
|
edf0f8074a | ||
|
|
c83a946cbd | ||
|
|
8604babeb6 | ||
|
|
7252b74539 | ||
|
|
08fbaa039f | ||
|
|
30f9fb50eb | ||
|
|
e3578df8a0 | ||
|
|
37445b8857 | ||
|
|
99ea14fab0 | ||
|
|
1717c143b2 | ||
|
|
53223d0876 | ||
|
|
096bcb4132 | ||
|
|
05cc70bdbd | ||
|
|
45653b52b5 | ||
|
|
d1841f2863 | ||
|
|
f85de11711 | ||
|
|
9d81a105ee | ||
|
|
deb6af9dea | ||
|
|
ab512b76aa | ||
|
|
bcbeee7247 | ||
|
|
bcdc94c3b9 | ||
|
|
6ebcf49758 | ||
|
|
a936ab6851 | ||
|
|
95378660dd | ||
|
|
e49bdac3e8 | ||
|
|
48380fab7e | ||
|
|
ce92529a84 | ||
|
|
8e29a555c8 | ||
|
|
a1b6ec066b | ||
|
|
fb59bf491f | ||
|
|
86aec7d2ba | ||
|
|
579f7d5609 | ||
|
|
c87b1ac363 | ||
|
|
543648112b | ||
|
|
daa3b9e978 | ||
|
|
fb8da181da | ||
|
|
6cc5a8af9e | ||
|
|
1a1956962a | ||
|
|
e422a1b403 | ||
|
|
295ece79ae | ||
|
|
08f8ee159a | ||
|
|
e2d416b3fb | ||
|
|
16ed97b4cb | ||
|
|
82739702bd | ||
|
|
0010c9e3d5 | ||
|
|
f59d4af92b | ||
|
|
a7df619a05 | ||
|
|
f3759c2ef5 | ||
|
|
d547f3a8a8 | ||
|
|
583e53d8a8 | ||
|
|
a27bf08ce0 | ||
|
|
060fb5ad2d | ||
|
|
ffc00d9035 | ||
|
|
83a9d281c2 | ||
|
|
b6c853c308 | ||
|
|
552c09d377 | ||
|
|
61164e627b | ||
|
|
3c71ee1ff2 | ||
|
|
07f610e84a | ||
|
|
faf344bc03 | ||
|
|
e9482a3dfc | ||
|
|
d4a7ce3487 | ||
|
|
3ef2737d82 | ||
|
|
582b67f4e1 | ||
|
|
5dd6c0af78 | ||
|
|
893e4f4723 | ||
|
|
d137e87f0e | ||
|
|
7f152077e5 | ||
|
|
74a9b229d0 | ||
|
|
80cf57a979 | ||
|
|
19ba943075 | ||
|
|
ba0e25a272 | ||
|
|
575c487fa6 | ||
|
|
1e91b2aa29 | ||
|
|
e10cf40f38 | ||
|
|
23defd2117 | ||
|
|
1227b87565 | ||
|
|
424ef16831 | ||
|
|
87059df28a | ||
|
|
520ff24ac2 | ||
|
|
920304773b | ||
|
|
e55059c82c | ||
|
|
6a96c91fe1 | ||
|
|
f995e1cf72 | ||
|
|
3be09553cf | ||
|
|
ee28a03a6c | ||
|
|
d56700a9d5 | ||
|
|
250df8b651 | ||
|
|
e071ffe590 | ||
|
|
737b45a18c | ||
|
|
1d2b7b17e8 | ||
|
|
bd71a4b581 | ||
|
|
451ff64b6f | ||
|
|
5e0dd60adb | ||
|
|
a05cd380fb | ||
|
|
fd3102c0d3 | ||
|
|
1bdd52232c | ||
|
|
381f68aaae | ||
|
|
058fde19ce | ||
|
|
63544c603d | ||
|
|
92cd8f945e | ||
|
|
5244941d9b | ||
|
|
877fb09fa3 | ||
|
|
34827ab068 | ||
|
|
9bbe875690 | ||
|
|
c7f09fb12d | ||
|
|
3f6d2bb6e3 | ||
|
|
bd2500314f | ||
|
|
a28f46a9eb | ||
|
|
7b7bd763c9 | ||
|
|
1118e4bfe0 | ||
|
|
b0bc88e9c2 | ||
|
|
038fea3539 | ||
|
|
ee20718c51 | ||
|
|
f46ffc004a | ||
|
|
d2f0f42c2d | ||
|
|
be36662efc | ||
|
|
c7d6d37a8e | ||
|
|
7ba87fcee8 | ||
|
|
c3713c9ce7 | ||
|
|
98861cea6c | ||
|
|
d89290f9b2 | ||
|
|
1b6b2ecd4d | ||
|
|
0e3575c1ca | ||
|
|
055f5c4c4e | ||
|
|
129d5445c3 | ||
|
|
6e202def1e | ||
|
|
0c51b16353 | ||
|
|
c5ee77e969 | ||
|
|
c7962abfbb | ||
|
|
d371f6ae8e | ||
|
|
8336f6a595 | ||
|
|
c3a24a6d7f | ||
|
|
f643e80de7 | ||
|
|
5473c0b38b | ||
|
|
eb54b67caa | ||
|
|
97e7e5f4a1 | ||
|
|
da64172848 | ||
|
|
e2d2e6ddd8 | ||
|
|
896cae2d07 | ||
|
|
409870dddb | ||
|
|
c19acda62d | ||
|
|
a50a6e129c | ||
|
|
d68987be0f | ||
|
|
bb64579fa5 | ||
|
|
2392d844d9 | ||
|
|
c6d01c1420 | ||
|
|
8389010002 | ||
|
|
31eb13e16d | ||
|
|
7cafbc032b | ||
|
|
cc752050f8 | ||
|
|
c90bec36c5 | ||
|
|
c1415b021a | ||
|
|
1081b4a54d | ||
|
|
ebb5fd16bb | ||
|
|
2d4a6d1fe6 | ||
|
|
11df7c28b4 | ||
|
|
17d2a63132 | ||
|
|
268486b652 | ||
|
|
f5af24b384 | ||
|
|
6d522876ad | ||
|
|
2814763b97 | ||
|
|
765133a3bd | ||
|
|
ab41a0c5d8 | ||
|
|
eee9f49c05 | ||
|
|
d7102809fc | ||
|
|
e792c119ea | ||
|
|
6e4ced8dcb | ||
|
|
e69be79aed | ||
|
|
87dc282fb7 | ||
|
|
38f7fb7c16 | ||
|
|
6173e8279f | ||
|
|
4f661c5c05 | ||
|
|
28172430dc | ||
|
|
5a9d4dd55e | ||
|
|
b3880ad380 | ||
|
|
b45c846a85 | ||
|
|
788fa693fd | ||
|
|
1d45ab1d20 | ||
|
|
ae1c3d85ab | ||
|
|
c22df2eb2a | ||
|
|
d3a130d9ba | ||
|
|
8a57b52fcc | ||
|
|
dbc9803f9e | ||
|
|
3e376eb166 | ||
|
|
12aca05aef | ||
|
|
ce1de27618 | ||
|
|
66d5148e3a | ||
|
|
d2b4a825eb | ||
|
|
f443720319 | ||
|
|
b4627a1613 | ||
|
|
ceea9c10d5 | ||
|
|
a143360497 | ||
|
|
163757cb69 | ||
|
|
4537b986ca | ||
|
|
aafac2a7a8 | ||
|
|
7344258b2f | ||
|
|
c59994b7e5 | ||
|
|
b089f59e2a | ||
|
|
649d2b7ef7 | ||
|
|
8bef575e8b | ||
|
|
559a036e6d | ||
|
|
0b845591f9 | ||
|
|
a8ef68ed59 | ||
|
|
7293b8b9dd | ||
|
|
8eef5465c9 | ||
|
|
c08cb95dc1 | ||
|
|
010166e7b0 | ||
|
|
4c7fb94616 | ||
|
|
46f486a367 | ||
|
|
ea195dcf11 | ||
|
|
e6979acded | ||
|
|
43b4fc81b9 | ||
|
|
d4ce6ebee6 | ||
|
|
1c9a5ece83 | ||
|
|
8a75664264 | ||
|
|
6687bdd258 | ||
|
|
e525ecad36 | ||
|
|
e8b5dc0649 | ||
|
|
3a0f96c1ec | ||
|
|
e4c06ba1bb | ||
|
|
bc4eadfd08 | ||
|
|
ee2ce3802f | ||
|
|
43d44634f7 | ||
|
|
41bd7fc958 | ||
|
|
1e458a642c | ||
|
|
b255e41091 | ||
|
|
bc22832ad6 | ||
|
|
f59ac505b7 | ||
|
|
e15b962221 | ||
|
|
28d4d4acc4 | ||
|
|
5f800f3723 | ||
|
|
2537ca03c8 | ||
|
|
1860402452 | ||
|
|
bec9385c68 | ||
|
|
798139e097 | ||
|
|
6683d76222 | ||
|
|
6d2d16b644 |
50
.github/workflows/jekyll-gh-pages.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||
name: Deploy Jekyll with GitHub Pages dependencies preinstalled
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["master"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v2
|
||||
- name: Build with Jekyll
|
||||
uses: actions/jekyll-build-pages@v1
|
||||
with:
|
||||
source: ./docs
|
||||
destination: ./_site
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
18
.gitignore
vendored
@@ -18,3 +18,21 @@ rules.ninja
|
||||
/CMakeLists.txt.user.*
|
||||
|
||||
rsc/shaders/paint.fs
|
||||
|
||||
osx/.DS_Store
|
||||
|
||||
.DS_Store
|
||||
|
||||
osx/runvimix
|
||||
|
||||
*.autosave
|
||||
|
||||
flatpak/.flatpak-builder
|
||||
|
||||
flatpak/repo/
|
||||
|
||||
flatpak/build/
|
||||
|
||||
build/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
9
.gitmodules
vendored
@@ -13,9 +13,12 @@
|
||||
[submodule "ext/Dirent"]
|
||||
path = ext/Dirent
|
||||
url = https://github.com/tronkko/dirent.git
|
||||
[submodule "ext/tfd"]
|
||||
path = ext/tfd
|
||||
url = https://github.com/native-toolkit/tinyfiledialogs.git
|
||||
[submodule "ext/glm"]
|
||||
path = ext/glm
|
||||
url = https://github.com/g-truc/glm.git
|
||||
[submodule "ext/link"]
|
||||
path = ext/link
|
||||
url = https://github.com/Ableton/link.git
|
||||
[submodule "ext/tfd"]
|
||||
path = ext/tfd
|
||||
url = https://git.code.sf.net/p/tinyfiledialogs/code
|
||||
@@ -1,247 +0,0 @@
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Log.h"
|
||||
#include "View.h"
|
||||
#include "Mixer.h"
|
||||
#include "MixingGroup.h"
|
||||
#include "tinyxml2Toolkit.h"
|
||||
#include "SessionSource.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include "ActionManager.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define ACTION_DEBUG
|
||||
#endif
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
Action::Action(): step_(0), max_step_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Action::clear()
|
||||
{
|
||||
// clean the history
|
||||
xmlDoc_.Clear();
|
||||
step_ = 0;
|
||||
max_step_ = 0;
|
||||
|
||||
// start fresh
|
||||
store("Session start");
|
||||
}
|
||||
|
||||
void Action::store(const std::string &label)
|
||||
{
|
||||
// ignore if locked or if no label is given
|
||||
if (locked_ || label.empty())
|
||||
return;
|
||||
|
||||
// incremental naming of history nodes
|
||||
step_++;
|
||||
std::string nodename = "H" + std::to_string(step_);
|
||||
|
||||
// erase future
|
||||
for (uint e = step_; e <= max_step_; e++) {
|
||||
std::string name = "H" + std::to_string(e);
|
||||
XMLElement *node = xmlDoc_.FirstChildElement( name.c_str() );
|
||||
if ( node )
|
||||
xmlDoc_.DeleteChild(node);
|
||||
}
|
||||
max_step_ = step_;
|
||||
|
||||
// create history node
|
||||
XMLElement *sessionNode = xmlDoc_.NewElement( nodename.c_str() );
|
||||
xmlDoc_.InsertEndChild(sessionNode);
|
||||
// label describes the action
|
||||
sessionNode->SetAttribute("label", label.c_str());
|
||||
// view indicates the view when this action occured
|
||||
sessionNode->SetAttribute("view", (int) Mixer::manager().view()->mode());
|
||||
|
||||
// get session to operate on
|
||||
Session *se = Mixer::manager().session();
|
||||
|
||||
// save all sources using source visitor
|
||||
SessionVisitor sv(&xmlDoc_, sessionNode);
|
||||
for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
|
||||
(*iter)->accept(sv);
|
||||
|
||||
// debug
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Action stored %s '%s'", nodename.c_str(), label.c_str());
|
||||
// XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Action::undo()
|
||||
{
|
||||
// not possible to go to 1 -1 = 0
|
||||
if (step_ <= 1)
|
||||
return;
|
||||
|
||||
// restore always changes step_ to step_ - 1
|
||||
restore( step_ - 1);
|
||||
}
|
||||
|
||||
void Action::redo()
|
||||
{
|
||||
// not possible to go to max_step_ + 1
|
||||
if (step_ >= max_step_)
|
||||
return;
|
||||
|
||||
// restore always changes step_ to step_ + 1
|
||||
restore( step_ + 1);
|
||||
}
|
||||
|
||||
|
||||
void Action::stepTo(uint target)
|
||||
{
|
||||
// get reasonable target
|
||||
uint t = CLAMP(target, 1, max_step_);
|
||||
|
||||
// ignore t == step_
|
||||
if (t != step_)
|
||||
restore(t);
|
||||
}
|
||||
|
||||
std::string Action::label(uint s) const
|
||||
{
|
||||
std::string l = "";
|
||||
|
||||
if (s > 0 && s <= max_step_) {
|
||||
std::string nodename = "H" + std::to_string(s);
|
||||
const XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
|
||||
l = sessionNode->Attribute("label");
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
void Action::restore(uint target)
|
||||
{
|
||||
// lock
|
||||
locked_ = true;
|
||||
|
||||
// get history node of target step
|
||||
step_ = CLAMP(target, 1, max_step_);
|
||||
std::string nodename = "H" + std::to_string(step_);
|
||||
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
|
||||
|
||||
// ask view to refresh, and switch to action view if user prefers
|
||||
int view = Settings::application.current_view ;
|
||||
if (Settings::application.action_history_follow_view)
|
||||
sessionNode->QueryIntAttribute("view", &view);
|
||||
Mixer::manager().setView( (View::Mode) view);
|
||||
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Restore %s '%s' ", nodename.c_str(), sessionNode->Attribute("label"));
|
||||
#endif
|
||||
|
||||
//
|
||||
// compare source lists
|
||||
//
|
||||
|
||||
// we operate on the current session
|
||||
Session *se = Mixer::manager().session();
|
||||
if (se == nullptr)
|
||||
return;
|
||||
|
||||
// sessionsources contains list of ids of all sources currently in the session (before loading)
|
||||
SourceIdList session_sources = se->getIdList();
|
||||
// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++)
|
||||
// Log::Info("sessionsources id %s", std::to_string(*it).c_str());
|
||||
|
||||
// load history status:
|
||||
// - if a source exists, its attributes are updated, and that's all
|
||||
// - if a source does not exists (in current session), it is created inside the session
|
||||
SessionLoader loader( se );
|
||||
loader.load( sessionNode );
|
||||
|
||||
// loaded_sources contains map of xml ids of all sources treated by loader
|
||||
std::map< uint64_t, Source* > loaded_sources = loader.getSources();
|
||||
|
||||
// remove intersect of both lists (sources were updated by SessionLoader)
|
||||
for( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); ){
|
||||
auto ssit = std::find(session_sources.begin(), session_sources.end(), (*lsit).first);
|
||||
if ( ssit != session_sources.end() ) {
|
||||
lsit = loaded_sources.erase(lsit);
|
||||
session_sources.erase(ssit);
|
||||
}
|
||||
else
|
||||
lsit++;
|
||||
}
|
||||
|
||||
// remaining ids in list sessionsources : to remove
|
||||
while ( !session_sources.empty() ){
|
||||
Source *s = Mixer::manager().findSource( session_sources.front() );
|
||||
if (s!=nullptr) {
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Delete id %s\n", std::to_string(session_sources.front() ).c_str());
|
||||
#endif
|
||||
// remove the source from the mixer
|
||||
Mixer::manager().detach( s );
|
||||
// delete source from session
|
||||
se->deleteSource( s );
|
||||
}
|
||||
session_sources.pop_front();
|
||||
}
|
||||
|
||||
// remaining sources in list loaded_sources : to add
|
||||
for ( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); lsit++)
|
||||
{
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Recreate id %s to %s\n", std::to_string((*lsit).first).c_str(), std::to_string((*lsit).second->id()).c_str());
|
||||
#endif
|
||||
// attach created source
|
||||
Mixer::manager().attach( (*lsit).second );
|
||||
|
||||
// change the history to match the new id
|
||||
replaceSourceId( (*lsit).first, (*lsit).second->id());
|
||||
}
|
||||
|
||||
//
|
||||
// compare mixing groups
|
||||
//
|
||||
|
||||
// Get the list of mixing groups in the xml loader
|
||||
std::list< SourceList > loadergroups = loader.getMixingGroups();
|
||||
|
||||
// clear all session groups
|
||||
auto group_iter = se->beginMixingGroup();
|
||||
while ( group_iter != se->endMixingGroup() )
|
||||
group_iter = se->deleteMixingGroup(group_iter);
|
||||
|
||||
// apply all changes creating or modifying groups in the session
|
||||
// (after this, new groups are created and existing groups are adjusted)
|
||||
for (auto group_loader_it = loadergroups.begin(); group_loader_it != loadergroups.end(); group_loader_it++) {
|
||||
se->link( *group_loader_it, Mixer::manager().view(View::MIXING)->scene.fg() );
|
||||
}
|
||||
|
||||
// free
|
||||
locked_ = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Action::replaceSourceId(uint64_t previousid, uint64_t newid)
|
||||
{
|
||||
// loop over every session history step
|
||||
XMLElement* historyNode = xmlDoc_.FirstChildElement("H1");
|
||||
for( ; historyNode ; historyNode = historyNode->NextSiblingElement())
|
||||
{
|
||||
// loop over every source in session history
|
||||
XMLElement* sourceNode = historyNode->FirstChildElement("Source");
|
||||
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
|
||||
{
|
||||
// check if this source node has this id
|
||||
uint64_t id_source_ = 0;
|
||||
sourceNode->QueryUnsigned64Attribute("id", &id_source_);
|
||||
if ( id_source_ == previousid )
|
||||
// change to new id
|
||||
sourceNode->SetAttribute("id", newid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#ifndef ACTIONMANAGER_H
|
||||
#define ACTIONMANAGER_H
|
||||
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
class Action
|
||||
{
|
||||
// Private Constructor
|
||||
Action();
|
||||
Action(Action const& copy); // Not Implemented
|
||||
Action& operator=(Action const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
|
||||
static Action& manager()
|
||||
{
|
||||
// The only instance
|
||||
static Action _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
void store(const std::string &label);
|
||||
|
||||
void clear();
|
||||
void undo();
|
||||
void redo();
|
||||
void stepTo(uint target);
|
||||
|
||||
inline uint current() const { return step_; }
|
||||
inline uint max() const { return max_step_; }
|
||||
|
||||
std::string label(uint s) const;
|
||||
|
||||
private:
|
||||
|
||||
void restore(uint target);
|
||||
void replaceSourceId(uint64_t previousid, uint64_t newid);
|
||||
// void replaceSourceId(tinyxml2::XMLElement* parentnode, uint64_t previousid, uint64_t newid);
|
||||
|
||||
tinyxml2::XMLDocument xmlDoc_;
|
||||
uint step_;
|
||||
uint max_step_;
|
||||
std::atomic<bool> locked_;
|
||||
};
|
||||
|
||||
#endif // ACTIONMANAGER_H
|
||||
883
CMakeLists.txt
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
113
Decorations.h
@@ -1,113 +0,0 @@
|
||||
#ifndef DECORATIONS_H
|
||||
#define DECORATIONS_H
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "Primitives.h"
|
||||
#include "Mesh.h"
|
||||
|
||||
class Frame : public Node
|
||||
{
|
||||
public:
|
||||
|
||||
typedef enum { ROUND = 0, SHARP, GROUP } CornerType;
|
||||
typedef enum { THIN = 0, LARGE } BorderType;
|
||||
typedef enum { NONE = 0, GLOW, DROP, PERSPECTIVE } ShadowType;
|
||||
|
||||
Frame(CornerType corner, BorderType border, ShadowType shadow);
|
||||
~Frame();
|
||||
|
||||
void update (float dt) override;
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
glm::vec4 color;
|
||||
|
||||
protected:
|
||||
Mesh *right_;
|
||||
Mesh *left_;
|
||||
Mesh *top_;
|
||||
Mesh *shadow_;
|
||||
LineSquare *square_;
|
||||
};
|
||||
|
||||
class Handles : public Node
|
||||
{
|
||||
public:
|
||||
typedef enum { RESIZE = 0, RESIZE_H, RESIZE_V, ROTATE, SCALE, CROP, MENU, LOCKED, UNLOCKED } Type;
|
||||
Handles(Type type);
|
||||
~Handles();
|
||||
|
||||
void update (float dt) override;
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
Type type() const { return type_; }
|
||||
Primitive *handle() const { return handle_; }
|
||||
void overlayActiveCorner(glm::vec2 v) {corner_ = v;}
|
||||
|
||||
glm::vec4 color;
|
||||
|
||||
protected:
|
||||
Mesh *handle_;
|
||||
Mesh *shadow_;
|
||||
glm::vec2 corner_;
|
||||
Type type_;
|
||||
};
|
||||
|
||||
class Symbol : public Node
|
||||
{
|
||||
public:
|
||||
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, CUBE, SHARE,
|
||||
DOTS, BUSY, LOCK, UNLOCK, EYE, EYESLASH, VECTORSLASH, ARROWS, ROTATION, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
|
||||
Symbol(Type t = CIRCLE_POINT, glm::vec3 pos = glm::vec3(0.f));
|
||||
~Symbol();
|
||||
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox bbox() const { return symbol_->bbox(); }
|
||||
|
||||
Type type() const { return type_; }
|
||||
glm::vec4 color;
|
||||
|
||||
protected:
|
||||
Mesh *symbol_;
|
||||
Mesh *shadow_;
|
||||
Type type_;
|
||||
};
|
||||
|
||||
class Disk : public Node
|
||||
{
|
||||
public:
|
||||
Disk();
|
||||
~Disk();
|
||||
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
glm::vec4 color;
|
||||
|
||||
protected:
|
||||
static Mesh *disk_;
|
||||
};
|
||||
|
||||
//class SelectionBox : public Group
|
||||
//{
|
||||
//public:
|
||||
// SelectionBox();
|
||||
|
||||
// void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
|
||||
// glm::vec4 color;
|
||||
|
||||
//protected:
|
||||
// LineSquare *square_;
|
||||
// GlmToolkit::AxisAlignedBoundingBox bbox_;
|
||||
|
||||
//};
|
||||
|
||||
|
||||
#endif // DECORATIONS_H
|
||||
573
DeviceSource.cpp
@@ -1,573 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <gst/pbutils/gstdiscoverer.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "DeviceSource.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Decorations.h"
|
||||
#include "Stream.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define DEVICE_DEBUG
|
||||
//#define GST_DEVICE_DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if defined(APPLE)
|
||||
std::string gst_plugin_device = "avfvideosrc";
|
||||
std::string gst_plugin_vidcap = "avfvideosrc capture-screen=true";
|
||||
#else
|
||||
std::string gst_plugin_device = "v4l2src";
|
||||
std::string gst_plugin_vidcap = "ximagesrc";
|
||||
#endif
|
||||
|
||||
////EXAMPLE :
|
||||
///
|
||||
//v4l2deviceprovider, udev-probed=(boolean)true,
|
||||
//device.bus_path=(string)pci-0000:00:14.0-usb-0:2:1.0,
|
||||
//sysfs.path=(string)/sys/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/video4linux/video0,
|
||||
//device.bus=(string)usb,
|
||||
//device.subsystem=(string)video4linux,
|
||||
//device.vendor.id=(string)1bcf,
|
||||
//device.vendor.name=(string)"Sunplus\\x20IT\\x20Co\\x20",
|
||||
//device.product.id=(string)2286,
|
||||
//device.product.name=(string)"AUSDOM\ FHD\ Camera:\ AUSDOM\ FHD\ C",
|
||||
//device.serial=(string)Sunplus_IT_Co_AUSDOM_FHD_Camera,
|
||||
//device.capabilities=(string):capture:,
|
||||
//device.api=(string)v4l2,
|
||||
//device.path=(string)/dev/video0,
|
||||
//v4l2.device.driver=(string)uvcvideo,
|
||||
//v4l2.device.card=(string)"AUSDOM\ FHD\ Camera:\ AUSDOM\ FHD\ C",
|
||||
//v4l2.device.bus_info=(string)usb-0000:00:14.0-2,
|
||||
//v4l2.device.version=(uint)328748,
|
||||
//v4l2.device.capabilities=(uint)2225078273,
|
||||
//v4l2.device.device_caps=(uint)69206017;
|
||||
//Device added: AUSDOM FHD Camera: AUSDOM FHD C - v4l2src device=/dev/video0
|
||||
|
||||
//v4l2deviceprovider, udev-probed=(boolean)true,
|
||||
//device.bus_path=(string)pci-0000:00:14.0-usb-0:4:1.0,
|
||||
//sysfs.path=(string)/sys/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0/video4linux/video2,
|
||||
//device.bus=(string)usb,
|
||||
//device.subsystem=(string)video4linux,
|
||||
//device.vendor.id=(string)046d,
|
||||
//device.vendor.name=(string)046d,
|
||||
//device.product.id=(string)080f,
|
||||
//device.product.name=(string)"UVC\ Camera\ \(046d:080f\)",
|
||||
//device.serial=(string)046d_080f_3EA77580,
|
||||
//device.capabilities=(string):capture:,
|
||||
//device.api=(string)v4l2,
|
||||
//device.path=(string)/dev/video2,
|
||||
//v4l2.device.driver=(string)uvcvideo,
|
||||
//v4l2.device.card=(string)"UVC\ Camera\ \(046d:080f\)",
|
||||
//v4l2.device.bus_info=(string)usb-0000:00:14.0-4,
|
||||
//v4l2.device.version=(uint)328748,
|
||||
//v4l2.device.capabilities=(uint)2225078273,
|
||||
//v4l2.device.device_caps=(uint)69206017; // decimal of hexadecimal v4l code Device Caps : 0x04200001
|
||||
//Device added: UVC Camera (046d:080f) - v4l2src device=/dev/video2
|
||||
|
||||
std::string pipelineForDevice(GstDevice *device, uint index)
|
||||
{
|
||||
std::ostringstream pipe;
|
||||
const gchar *str = gst_structure_get_string(gst_device_get_properties(device), "device.api");
|
||||
|
||||
if (str && gst_plugin_device.find(str) != std::string::npos)
|
||||
{
|
||||
pipe << gst_plugin_device;
|
||||
|
||||
#if defined(APPLE)
|
||||
pipe << " device-index=" << index;
|
||||
#else
|
||||
str = gst_structure_get_string(gst_device_get_properties(device), "device.path");
|
||||
if (str)
|
||||
pipe << " device=" << str;
|
||||
#endif
|
||||
}
|
||||
|
||||
return pipe.str();
|
||||
}
|
||||
|
||||
gboolean
|
||||
Device::callback_device_monitor (GstBus *, GstMessage * message, gpointer )
|
||||
{
|
||||
GstDevice *device;
|
||||
gchar *name;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_DEVICE_ADDED: {
|
||||
gst_message_parse_device_added (message, &device);
|
||||
name = gst_device_get_display_name (device);
|
||||
|
||||
// ignore if already in the list
|
||||
if ( std::find(manager().src_name_.begin(), manager().src_name_.end(), name) != manager().src_name_.end())
|
||||
break;
|
||||
|
||||
manager().src_name_.push_back(name);
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
|
||||
g_print("\nDevice %s plugged : %s\n", name, stru);
|
||||
g_free (stru);
|
||||
#endif
|
||||
g_free (name);
|
||||
|
||||
std::string p = pipelineForDevice(device, manager().src_description_.size());
|
||||
manager().src_description_.push_back(p);
|
||||
|
||||
DeviceConfigSet confs = getDeviceConfigs(p);
|
||||
manager().src_config_.push_back(confs);
|
||||
|
||||
manager().list_uptodate_ = false;
|
||||
|
||||
gst_object_unref (device);
|
||||
}
|
||||
break;
|
||||
case GST_MESSAGE_DEVICE_REMOVED: {
|
||||
gst_message_parse_device_removed (message, &device);
|
||||
name = gst_device_get_display_name (device);
|
||||
manager().remove(name);
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
g_print("\nDevice %s unplugged\n", name);
|
||||
#endif
|
||||
g_free (name);
|
||||
|
||||
manager().list_uptodate_ = false;
|
||||
|
||||
gst_object_unref (device);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
void Device::remove(const char *device)
|
||||
{
|
||||
std::vector< std::string >::iterator nameit = src_name_.begin();
|
||||
std::vector< std::string >::iterator descit = src_description_.begin();
|
||||
std::vector< DeviceConfigSet >::iterator coit = src_config_.begin();
|
||||
while (nameit != src_name_.end()){
|
||||
|
||||
if ( (*nameit).compare(device) == 0 )
|
||||
{
|
||||
src_name_.erase(nameit);
|
||||
src_description_.erase(descit);
|
||||
src_config_.erase(coit);
|
||||
break;
|
||||
}
|
||||
|
||||
nameit++;
|
||||
descit++;
|
||||
coit++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Device::Device()
|
||||
{
|
||||
GstBus *bus;
|
||||
GstCaps *caps;
|
||||
|
||||
// create GStreamer device monitor to capture
|
||||
// when a device is plugged in or out
|
||||
monitor_ = gst_device_monitor_new ();
|
||||
|
||||
bus = gst_device_monitor_get_bus (monitor_);
|
||||
gst_bus_add_watch (bus, callback_device_monitor, NULL);
|
||||
gst_object_unref (bus);
|
||||
|
||||
caps = gst_caps_new_empty_simple ("video/x-raw");
|
||||
gst_device_monitor_add_filter (monitor_, "Video/Source", caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
gst_device_monitor_set_show_all_devices(monitor_, true);
|
||||
gst_device_monitor_start (monitor_);
|
||||
|
||||
// initial fill of the list
|
||||
GList *devices = gst_device_monitor_get_devices(monitor_);
|
||||
GList *tmp;
|
||||
for (tmp = devices; tmp ; tmp = tmp->next ) {
|
||||
|
||||
GstDevice *device = (GstDevice *) tmp->data;
|
||||
|
||||
gchar *name = gst_device_get_display_name (device);
|
||||
src_name_.push_back(name);
|
||||
g_free (name);
|
||||
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
|
||||
g_print("\nDevice %s already plugged : %s", name, stru);
|
||||
g_free (stru);
|
||||
#endif
|
||||
|
||||
std::string p = pipelineForDevice(device, src_description_.size());
|
||||
src_description_.push_back(p);
|
||||
|
||||
DeviceConfigSet confs = getDeviceConfigs(p);
|
||||
src_config_.push_back(confs);
|
||||
}
|
||||
g_list_free(devices);
|
||||
|
||||
// Add config for plugged screen
|
||||
src_name_.push_back("Screen capture");
|
||||
src_description_.push_back(gst_plugin_vidcap);
|
||||
|
||||
// Try to auto find resolution
|
||||
DeviceConfigSet confs = getDeviceConfigs(gst_plugin_vidcap);
|
||||
if (!confs.empty()) {
|
||||
// fix the framerate (otherwise at 1 FPS
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
DeviceConfigSet confscreen;
|
||||
best.fps_numerator = 15;
|
||||
confscreen.insert(best);
|
||||
src_config_.push_back(confscreen);
|
||||
}
|
||||
|
||||
// TODO Use lib glfw to get monitors
|
||||
// TODO Detect auto removal of monitors
|
||||
|
||||
list_uptodate_ = true;
|
||||
}
|
||||
|
||||
|
||||
int Device::numDevices() const
|
||||
{
|
||||
return src_name_.size();
|
||||
}
|
||||
|
||||
bool Device::exists(const std::string &device) const
|
||||
{
|
||||
std::vector< std::string >::const_iterator d = std::find(src_name_.begin(), src_name_.end(), device);
|
||||
return d != src_name_.end();
|
||||
}
|
||||
|
||||
struct hasDevice: public std::unary_function<DeviceSource*, bool>
|
||||
{
|
||||
inline bool operator()(const DeviceSource* elem) const {
|
||||
return (elem && elem->device() == _d);
|
||||
}
|
||||
hasDevice(std::string d) : _d(d) { }
|
||||
private:
|
||||
std::string _d;
|
||||
};
|
||||
|
||||
Source *Device::createSource(const std::string &device) const
|
||||
{
|
||||
Source *s = nullptr;
|
||||
|
||||
// find if a DeviceSource with this device is already registered
|
||||
std::list< DeviceSource *>::const_iterator d = std::find_if(device_sources_.begin(), device_sources_.end(), hasDevice(device));
|
||||
|
||||
// if already registered, clone the device source
|
||||
if ( d != device_sources_.end()) {
|
||||
CloneSource *cs = (*d)->clone();
|
||||
s = cs;
|
||||
}
|
||||
// otherwise, we are free to create a new device source
|
||||
else {
|
||||
DeviceSource *ds = new DeviceSource();
|
||||
ds->setDevice(device);
|
||||
s = ds;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
bool Device::unplugged(const std::string &device) const
|
||||
{
|
||||
if (list_uptodate_)
|
||||
return false;
|
||||
return !exists(device);
|
||||
}
|
||||
|
||||
std::string Device::name(int index) const
|
||||
{
|
||||
if (index > -1 && index < (int) src_name_.size())
|
||||
return src_name_[index];
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string Device::description(int index) const
|
||||
{
|
||||
if (index > -1 && index < (int) src_description_.size())
|
||||
return src_description_[index];
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
DeviceConfigSet Device::config(int index) const
|
||||
{
|
||||
if (index > -1 && index < (int) src_config_.size())
|
||||
return src_config_[index];
|
||||
else
|
||||
return DeviceConfigSet();
|
||||
}
|
||||
|
||||
int Device::index(const std::string &device) const
|
||||
{
|
||||
int i = -1;
|
||||
std::vector< std::string >::const_iterator p = std::find(src_name_.begin(), src_name_.end(), device);
|
||||
if (p != src_name_.end())
|
||||
i = std::distance(src_name_.begin(), p);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
DeviceSource::DeviceSource() : StreamSource()
|
||||
{
|
||||
// create stream
|
||||
stream_ = new Stream;
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::CAMERA, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
DeviceSource::~DeviceSource()
|
||||
{
|
||||
// unregister this device source
|
||||
Device::manager().device_sources_.remove(this);
|
||||
}
|
||||
|
||||
void DeviceSource::setDevice(const std::string &devicename)
|
||||
{
|
||||
device_ = devicename;
|
||||
Log::Notify("Creating Source with device '%s'", device_.c_str());
|
||||
|
||||
int index = Device::manager().index(device_);
|
||||
if (index > -1) {
|
||||
|
||||
// register this device source
|
||||
Device::manager().device_sources_.push_back(this);
|
||||
|
||||
// start filling in the gstreamer pipeline
|
||||
std::ostringstream pipeline;
|
||||
pipeline << Device::manager().description(index);
|
||||
|
||||
// test the device and get config
|
||||
DeviceConfigSet confs = Device::manager().config(index);
|
||||
#ifdef DEVICE_DEBUG
|
||||
Log::Info("Device %s supported configs:", devicename.c_str());
|
||||
for( DeviceConfigSet::iterator it = confs.begin(); it != confs.end(); it++ ){
|
||||
float fps = static_cast<float>((*it).fps_numerator) / static_cast<float>((*it).fps_denominator);
|
||||
Log::Info(" - %s %s %d x %d %.1f fps", (*it).stream.c_str(), (*it).format.c_str(), (*it).width, (*it).height, fps);
|
||||
}
|
||||
#endif
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
|
||||
Log::Info("Device %s selected its optimal config: %s %s %dx%d@%.1ffps", device_.c_str(), best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
|
||||
|
||||
pipeline << " ! " << best.stream;
|
||||
if (!best.format.empty())
|
||||
pipeline << ",format=" << best.format;
|
||||
pipeline << ",framerate=" << best.fps_numerator << "/" << best.fps_denominator;
|
||||
pipeline << ",width=" << best.width;
|
||||
pipeline << ",height=" << best.height;
|
||||
|
||||
if ( best.stream.find("jpeg") != std::string::npos )
|
||||
pipeline << " ! jpegdec";
|
||||
|
||||
if ( device_.find("Screen") != std::string::npos )
|
||||
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue max-size-buffers=3";
|
||||
|
||||
pipeline << " ! videoconvert";
|
||||
|
||||
stream_->open( pipeline.str(), best.width, best.height);
|
||||
stream_->play(true);
|
||||
}
|
||||
else
|
||||
Log::Warning("No such device '%s'", device_.c_str());
|
||||
}
|
||||
|
||||
void DeviceSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
bool DeviceSource::failed() const
|
||||
{
|
||||
return stream_->failed() || Device::manager().unplugged(device_);
|
||||
}
|
||||
|
||||
DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
|
||||
{
|
||||
DeviceConfigSet configs;
|
||||
|
||||
// create dummy pipeline to be tested
|
||||
std::string description = src_description;
|
||||
description += " name=devsrc ! fakesink name=sink";
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
GstElement *pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("DeviceSource Could not construct test pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
return configs;
|
||||
}
|
||||
|
||||
// get the pipeline element named "devsrc" from the Device class
|
||||
GstElement *elem = gst_bin_get_by_name (GST_BIN (pipeline_), "devsrc");
|
||||
if (elem) {
|
||||
|
||||
// initialize the pipeline
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PAUSED);
|
||||
if (ret != GST_STATE_CHANGE_FAILURE) {
|
||||
|
||||
// get the first pad and its content
|
||||
GstIterator *iter = gst_element_iterate_src_pads(elem);
|
||||
GValue vPad = G_VALUE_INIT;
|
||||
GstPad* ret = NULL;
|
||||
if (gst_iterator_next(iter, &vPad) == GST_ITERATOR_OK)
|
||||
{
|
||||
ret = GST_PAD(g_value_get_object(&vPad));
|
||||
GstCaps *device_caps = gst_pad_query_caps (ret, NULL);
|
||||
|
||||
// loop over all caps offered by the pad
|
||||
int C = gst_caps_get_size(device_caps);
|
||||
for (int c = 0; c < C; ++c) {
|
||||
// get GST cap
|
||||
GstStructure *decice_cap_struct = gst_caps_get_structure (device_caps, c);
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
gchar *capstext = gst_structure_to_string (decice_cap_struct);
|
||||
g_print("\nDevice caps: %s", capstext);
|
||||
g_free(capstext);
|
||||
#endif
|
||||
|
||||
// fill our config
|
||||
DeviceConfig config;
|
||||
|
||||
// not managing opengl texture-target types
|
||||
// TODO: support input devices texture-target video/x-raw(memory:GLMemory) for improved pipeline
|
||||
if ( gst_structure_has_field (decice_cap_struct, "texture-target"))
|
||||
continue;
|
||||
|
||||
// NAME : typically video/x-raw or image/jpeg
|
||||
config.stream = gst_structure_get_name (decice_cap_struct);
|
||||
|
||||
// FORMAT : typically BGRA or YUVY
|
||||
if ( gst_structure_has_field (decice_cap_struct, "format")) {
|
||||
// get generic value
|
||||
const GValue *val = gst_structure_get_value(decice_cap_struct, "format");
|
||||
|
||||
// if its a list of format string
|
||||
if ( GST_VALUE_HOLDS_LIST(val)) {
|
||||
int N = gst_value_list_get_size(val);
|
||||
for (int n = 0; n < N; n++ ){
|
||||
std::string f = gst_value_serialize( gst_value_list_get_value(val, n) );
|
||||
|
||||
// preference order : 1) RGBx, 2) JPEG, 3) ALL OTHER
|
||||
// select f if it contains R (e.g. for RGBx) and not already RGB in config
|
||||
if ( (f.find("R") != std::string::npos) && (config.format.find("R") == std::string::npos ) ) {
|
||||
config.format = f;
|
||||
break;
|
||||
}
|
||||
// default, take at least one if nothing yet in config
|
||||
else if ( config.format.empty() )
|
||||
config.format = f;
|
||||
}
|
||||
|
||||
}
|
||||
// single format
|
||||
else {
|
||||
config.format = gst_value_serialize(val);
|
||||
}
|
||||
}
|
||||
|
||||
// FRAMERATE : can be a fraction of a list of fractions
|
||||
if ( gst_structure_has_field (decice_cap_struct, "framerate")) {
|
||||
|
||||
// get generic value
|
||||
const GValue *val = gst_structure_get_value(decice_cap_struct, "framerate");
|
||||
// if its a single fraction
|
||||
if ( GST_VALUE_HOLDS_FRACTION(val)) {
|
||||
config.fps_numerator = gst_value_get_fraction_numerator(val);
|
||||
config.fps_denominator= gst_value_get_fraction_denominator(val);
|
||||
}
|
||||
// if its a range of fraction; take the max
|
||||
else if ( GST_VALUE_HOLDS_FRACTION_RANGE(val)) {
|
||||
config.fps_numerator = gst_value_get_fraction_numerator(gst_value_get_fraction_range_max(val));
|
||||
config.fps_denominator= gst_value_get_fraction_denominator(gst_value_get_fraction_range_max(val));
|
||||
}
|
||||
// deal otherwise with a list of fractions; find the max
|
||||
else if ( GST_VALUE_HOLDS_LIST(val)) {
|
||||
gdouble fps_max = 1.0;
|
||||
// loop over all fractions
|
||||
int N = gst_value_list_get_size(val);
|
||||
for (int n = 0; n < N; n++ ){
|
||||
const GValue *frac = gst_value_list_get_value(val, n);
|
||||
// read one fraction in the list
|
||||
if ( GST_VALUE_HOLDS_FRACTION(frac)) {
|
||||
int n = gst_value_get_fraction_numerator(frac);
|
||||
int d = gst_value_get_fraction_denominator(frac);
|
||||
// keep only the higher FPS
|
||||
gdouble f = 1.0;
|
||||
gst_util_fraction_to_double( n, d, &f );
|
||||
if ( f > fps_max ) {
|
||||
config.fps_numerator = n;
|
||||
config.fps_denominator = d;
|
||||
fps_max = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WIDTH and HEIGHT
|
||||
if ( gst_structure_has_field (decice_cap_struct, "width"))
|
||||
gst_structure_get_int (decice_cap_struct, "width", &config.width);
|
||||
if ( gst_structure_has_field (decice_cap_struct, "height"))
|
||||
gst_structure_get_int (decice_cap_struct, "height", &config.height);
|
||||
|
||||
|
||||
// add this config
|
||||
configs.insert(config);
|
||||
}
|
||||
|
||||
}
|
||||
gst_iterator_free(iter);
|
||||
|
||||
// terminate pipeline
|
||||
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
}
|
||||
|
||||
g_object_unref (elem);
|
||||
}
|
||||
|
||||
gst_object_unref (pipeline_);
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
|
||||
glm::ivec2 DeviceSource::icon() const
|
||||
{
|
||||
if ( device_.find("Screen") != std::string::npos )
|
||||
return glm::ivec2(19, 1);
|
||||
else
|
||||
return glm::ivec2(2, 14);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
130
DeviceSource.h
@@ -1,130 +0,0 @@
|
||||
#ifndef DEVICESOURCE_H
|
||||
#define DEVICESOURCE_H
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "GstToolkit.h"
|
||||
#include "StreamSource.h"
|
||||
|
||||
class DeviceSource : public StreamSource
|
||||
{
|
||||
public:
|
||||
DeviceSource();
|
||||
~DeviceSource();
|
||||
|
||||
// Source interface
|
||||
bool failed() const override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
// StreamSource interface
|
||||
Stream *stream() const override { return stream_; }
|
||||
|
||||
// specific interface
|
||||
void setDevice(const std::string &devicename);
|
||||
inline std::string device() const { return device_; }
|
||||
|
||||
glm::ivec2 icon() const override;
|
||||
|
||||
private:
|
||||
std::string device_;
|
||||
|
||||
};
|
||||
|
||||
struct DeviceConfig {
|
||||
gint width;
|
||||
gint height;
|
||||
gint fps_numerator;
|
||||
gint fps_denominator;
|
||||
std::string stream;
|
||||
std::string format;
|
||||
|
||||
DeviceConfig() {
|
||||
width = 0;
|
||||
height = 0;
|
||||
fps_numerator = 1;
|
||||
fps_denominator = 1;
|
||||
stream = "";
|
||||
format = "";
|
||||
}
|
||||
|
||||
inline DeviceConfig& operator = (const DeviceConfig& b)
|
||||
{
|
||||
if (this != &b) {
|
||||
this->width = b.width;
|
||||
this->height = b.height;
|
||||
this->fps_numerator = b.fps_numerator;
|
||||
this->fps_denominator = b.fps_denominator;
|
||||
this->stream = b.stream;
|
||||
this->format = b.format;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool operator < (const DeviceConfig b) const
|
||||
{
|
||||
int formatscore = this->format.find("R") != std::string::npos ? 2 : 1; // best score for RGBx
|
||||
int b_formatscore = b.format.find("R") != std::string::npos ? 2 : 1;
|
||||
float fps = static_cast<float>(this->fps_numerator) / static_cast<float>(this->fps_denominator);
|
||||
float b_fps = static_cast<float>(b.fps_numerator) / static_cast<float>(b.fps_denominator);
|
||||
return ( fps * static_cast<float>(this->height * formatscore) < b_fps * static_cast<float>(b.height * b_formatscore));
|
||||
}
|
||||
};
|
||||
|
||||
struct better_device_comparator
|
||||
{
|
||||
inline bool operator () (const DeviceConfig a, const DeviceConfig b) const
|
||||
{
|
||||
return (a < b);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::set<DeviceConfig, better_device_comparator> DeviceConfigSet;
|
||||
|
||||
|
||||
class Device
|
||||
{
|
||||
friend class DeviceSource;
|
||||
|
||||
Device();
|
||||
Device(Device const& copy); // Not Implemented
|
||||
Device& operator=(Device const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
|
||||
static Device& manager()
|
||||
{
|
||||
// The only instance
|
||||
static Device _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
int numDevices () const;
|
||||
std::string name (int index) const;
|
||||
std::string description (int index) const;
|
||||
DeviceConfigSet config (int index) const;
|
||||
|
||||
int index (const std::string &device) const;
|
||||
bool exists (const std::string &device) const;
|
||||
bool unplugged (const std::string &device) const;
|
||||
|
||||
Source *createSource(const std::string &device) const;
|
||||
|
||||
static gboolean callback_device_monitor (GstBus *, GstMessage *, gpointer);
|
||||
static DeviceConfigSet getDeviceConfigs(const std::string &src_description);
|
||||
|
||||
private:
|
||||
|
||||
void remove(const char *device);
|
||||
|
||||
std::vector< std::string > src_name_;
|
||||
std::vector< std::string > src_description_;
|
||||
std::vector< DeviceConfigSet > src_config_;
|
||||
bool list_uptodate_;
|
||||
GstDeviceMonitor *monitor_;
|
||||
|
||||
std::list< DeviceSource * > device_sources_;
|
||||
};
|
||||
|
||||
|
||||
#endif // DEVICESOURCE_H
|
||||
100
DrawVisitor.cpp
@@ -1,100 +0,0 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "defines.h"
|
||||
#include "DrawVisitor.h"
|
||||
#include "Scene.h"
|
||||
|
||||
|
||||
DrawVisitor::DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force): force_(force)
|
||||
{
|
||||
targets_.push_back(nodetodraw);
|
||||
modelview_ = glm::identity<glm::mat4>();
|
||||
projection_ = projection;
|
||||
num_duplicat_ = 1;
|
||||
transform_duplicat_ = glm::identity<glm::mat4>();
|
||||
}
|
||||
|
||||
DrawVisitor::DrawVisitor(std::vector<Node *> nodestodraw, glm::mat4 projection, bool force): force_(force)
|
||||
{
|
||||
targets_ = nodestodraw;
|
||||
modelview_ = glm::identity<glm::mat4>();
|
||||
projection_ = projection;
|
||||
num_duplicat_ = 1;
|
||||
transform_duplicat_ = glm::identity<glm::mat4>();
|
||||
}
|
||||
|
||||
void DrawVisitor::loop(int num, glm::mat4 transform)
|
||||
{
|
||||
num_duplicat_ = CLAMP(num, 1, 10000);
|
||||
transform_duplicat_ = transform;
|
||||
}
|
||||
|
||||
void DrawVisitor::visit(Node &n)
|
||||
{
|
||||
// force visible status if required
|
||||
bool v = n.visible_;
|
||||
if (force_)
|
||||
n.visible_ = true;
|
||||
|
||||
// find the node with this id
|
||||
std::vector<Node *>::iterator it = std::find_if(targets_.begin(), targets_.end(), hasId(n.id()));
|
||||
|
||||
// found this node in the list of targets: draw it
|
||||
if (it != targets_.end()) {
|
||||
|
||||
targets_.erase(it);
|
||||
|
||||
for (int i = 0; i < num_duplicat_; ++i) {
|
||||
// draw multiple copies if requested
|
||||
n.draw(modelview_, projection_);
|
||||
modelview_ *= transform_duplicat_;
|
||||
}
|
||||
}
|
||||
|
||||
// restore visibility
|
||||
n.visible_ = v;
|
||||
|
||||
if (targets_.empty()) return;
|
||||
|
||||
// update transform
|
||||
modelview_ *= n.transform_;
|
||||
}
|
||||
|
||||
|
||||
void DrawVisitor::visit(Group &n)
|
||||
{
|
||||
// no need to traverse deeper if this node was drawn already
|
||||
if (targets_.empty()) return;
|
||||
|
||||
// traverse children
|
||||
glm::mat4 mv = modelview_;
|
||||
for (NodeSet::iterator node = n.begin(); !targets_.empty() && node != n.end(); node++) {
|
||||
if ( (*node)->visible_ || force_)
|
||||
(*node)->accept(*this);
|
||||
modelview_ = mv;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawVisitor::visit(Scene &n)
|
||||
{
|
||||
modelview_ = glm::identity<glm::mat4>();
|
||||
n.root()->accept(*this);
|
||||
}
|
||||
|
||||
void DrawVisitor::visit(Switch &n)
|
||||
{
|
||||
// no need to traverse deeper if this node was drawn already
|
||||
if (targets_.empty()) return;
|
||||
|
||||
// traverse acive child
|
||||
glm::mat4 mv = modelview_;
|
||||
if ( n.activeChild()->visible_ || force_)
|
||||
n.activeChild()->accept(*this);
|
||||
modelview_ = mv;
|
||||
}
|
||||
|
||||
void DrawVisitor::visit(Primitive &n)
|
||||
{
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef DRAWVISITOR_H
|
||||
#define DRAWVISITOR_H
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
#include "Visitor.h"
|
||||
|
||||
class DrawVisitor : public Visitor
|
||||
{
|
||||
glm::mat4 modelview_;
|
||||
glm::mat4 projection_;
|
||||
std::vector<Node *> targets_;
|
||||
bool force_;
|
||||
int num_duplicat_;
|
||||
glm::mat4 transform_duplicat_;
|
||||
|
||||
public:
|
||||
DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force = false);
|
||||
DrawVisitor(std::vector<Node *> nodestodraw, glm::mat4 projection, bool force = false);
|
||||
|
||||
void loop(int num, glm::mat4 transform);
|
||||
|
||||
void visit(Scene& n) override;
|
||||
void visit(Node& n) override;
|
||||
void visit(Primitive& n) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
};
|
||||
|
||||
#endif // DRAWVISITOR_H
|
||||
1213
FileDialog.cpp
112
FileDialog.h
@@ -1,112 +0,0 @@
|
||||
#ifndef __IMGUI_FILE_DIALOG_H_
|
||||
#define __IMGUI_FILE_DIALOG_H_
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <future>
|
||||
#include <functional>
|
||||
|
||||
#define MAX_FILE_DIALOG_NAME_BUFFER 1024
|
||||
|
||||
struct FileInfoStruct
|
||||
{
|
||||
char type = ' ';
|
||||
std::string filePath;
|
||||
std::string fileName;
|
||||
std::string ext;
|
||||
};
|
||||
|
||||
class FileDialog
|
||||
{
|
||||
private:
|
||||
std::vector<FileInfoStruct> m_FileList;
|
||||
std::map<std::string, ImVec4> m_FilterColor;
|
||||
std::string m_SelectedFileName;
|
||||
std::string m_SelectedExt;
|
||||
std::string m_CurrentPath;
|
||||
std::vector<std::string> m_CurrentPath_Decomposition;
|
||||
std::string m_Name;
|
||||
bool m_ShowDialog;
|
||||
bool m_ShowDrives;
|
||||
|
||||
public:
|
||||
static char FileNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER];
|
||||
static char DirectoryNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER];
|
||||
static char SearchBuffer[MAX_FILE_DIALOG_NAME_BUFFER];
|
||||
static int FilterIndex;
|
||||
bool IsOk;
|
||||
bool m_AnyWindowsHovered;
|
||||
bool m_CreateDirectoryMode;
|
||||
|
||||
private:
|
||||
std::string dlg_key;
|
||||
std::string dlg_name;
|
||||
const char *dlg_filters;
|
||||
std::string dlg_path;
|
||||
std::string dlg_defaultFileName;
|
||||
std::string dlg_defaultExt;
|
||||
std::function<void(std::string, bool*)> dlg_optionsPane;
|
||||
size_t dlg_optionsPaneWidth;
|
||||
std::string searchTag;
|
||||
std::string dlg_userString;
|
||||
|
||||
public:
|
||||
static FileDialog* Instance()
|
||||
{
|
||||
static FileDialog *_instance = new FileDialog();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static void RenderCurrent();
|
||||
// Open an Open File dialog for TEXT file
|
||||
static void SetCurrentOpenText();
|
||||
// Open an Open File dialog for IMAGE file
|
||||
static void SetCurrentOpenImage();
|
||||
// Open an Open File dialog for MEDIA file
|
||||
static void SetCurrentOpenMedia();
|
||||
|
||||
protected:
|
||||
|
||||
FileDialog(); // Prevent construction
|
||||
FileDialog(const FileDialog&) {} // Prevent construction by copying
|
||||
FileDialog& operator =(const FileDialog&) { return *this; } // Prevent assignment
|
||||
~FileDialog(); // Prevent unwanted destruction
|
||||
|
||||
public:
|
||||
void OpenDialog(const std::string& vKey, const char* vName, const char* vFilters,
|
||||
const std::string& vPath, const std::string& vDefaultFileName,
|
||||
std::function<void(std::string, bool*)> vOptionsPane, size_t vOptionsPaneWidth = 250, const std::string& vUserString = "");
|
||||
void OpenDialog(const std::string& vKey, const char* vName, const char* vFilters,
|
||||
const std::string& vDefaultFileName,
|
||||
std::function<void(std::string, bool*)> vOptionsPane, size_t vOptionsPaneWidth = 250, const std::string& vUserString = "");
|
||||
void OpenDialog(const std::string& vKey, const char* vName, const char* vFilters,
|
||||
const std::string& vPath, const std::string& vDefaultFileName, const std::string& vUserString = "");
|
||||
void OpenDialog(const std::string& vKey, const char* vName, const char* vFilters,
|
||||
const std::string& vFilePathName, const std::string& vUserString = "");
|
||||
|
||||
void CloseDialog(const std::string& vKey);
|
||||
bool Render(const std::string& vKey, ImVec2 geometry);
|
||||
std::string GetFilepathName();
|
||||
std::string GetCurrentPath();
|
||||
std::string GetCurrentFileName();
|
||||
std::string GetCurrentFilter();
|
||||
std::string GetUserString();
|
||||
|
||||
void SetFilterColor(std::string vFilter, ImVec4 vColor);
|
||||
bool GetFilterColor(std::string vFilter, ImVec4 *vColor);
|
||||
void ClearFilterColor();
|
||||
|
||||
private:
|
||||
void SetPath(const std::string& vPath);
|
||||
void ScanDir(const std::string& vPath);
|
||||
void SetCurrentDir(const std::string& vPath);
|
||||
bool CreateDir(const std::string& vPath);
|
||||
void ComposeNewPath(std::vector<std::string>::iterator vIter);
|
||||
void GetDrives();
|
||||
};
|
||||
|
||||
|
||||
#endif // __IMGUI_FILE_DIALOG_H_
|
||||
384
FrameBuffer.cpp
@@ -1,384 +0,0 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "FrameBuffer.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Settings.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
const char* FrameBuffer::aspect_ratio_name[5] = { "4:3", "3:2", "16:10", "16:9", "21:9" };
|
||||
glm::vec2 FrameBuffer::aspect_ratio_size[5] = { glm::vec2(4.f,3.f), glm::vec2(3.f,2.f), glm::vec2(16.f,10.f), glm::vec2(16.f,9.f) , glm::vec2(21.f,9.f) };
|
||||
const char* FrameBuffer::resolution_name[4] = { "720", "1080", "1440", "2160" };
|
||||
float FrameBuffer::resolution_height[4] = { 720.f, 1080.f, 1440.f, 2160.f };
|
||||
|
||||
|
||||
glm::vec3 FrameBuffer::getResolutionFromParameters(int ar, int h)
|
||||
{
|
||||
float width = aspect_ratio_size[ar].x * resolution_height[h] / aspect_ratio_size[ar].y;
|
||||
glm::vec3 res = glm::vec3( width, resolution_height[h] , 0.f);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
glm::ivec2 FrameBuffer::getParametersFromResolution(glm::vec3 res)
|
||||
{
|
||||
glm::ivec2 p = glm::ivec2(-1);
|
||||
|
||||
// get aspect ratio parameter
|
||||
static int num_ar = ((int)(sizeof(FrameBuffer::aspect_ratio_size) / sizeof(*FrameBuffer::aspect_ratio_size)));
|
||||
float myratio = res.x / res.y;
|
||||
for(int ar = 0; ar < num_ar; ar++) {
|
||||
if ( myratio - (FrameBuffer::aspect_ratio_size[ar].x / FrameBuffer::aspect_ratio_size[ar].y ) < EPSILON){
|
||||
p.x = ar;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// get height parameter
|
||||
static int num_height = ((int)(sizeof(FrameBuffer::resolution_height) / sizeof(*FrameBuffer::resolution_height)));
|
||||
for(int h = 0; h < num_height; h++) {
|
||||
if ( res.y - FrameBuffer::resolution_height[h] < 1){
|
||||
p.y = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
FrameBuffer::FrameBuffer(glm::vec3 resolution, bool useAlpha, bool multiSampling):
|
||||
textureid_(0), intermediate_textureid_(0), framebufferid_(0), intermediate_framebufferid_(0),
|
||||
use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
|
||||
{
|
||||
attrib_.viewport = glm::ivec2(resolution);
|
||||
setProjectionArea(glm::vec2(1.f, 1.f));
|
||||
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, 0.f);
|
||||
}
|
||||
|
||||
FrameBuffer::FrameBuffer(uint width, uint height, bool useAlpha, bool multiSampling):
|
||||
textureid_(0), intermediate_textureid_(0), framebufferid_(0), intermediate_framebufferid_(0),
|
||||
use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
|
||||
{
|
||||
attrib_.viewport = glm::ivec2(width, height);
|
||||
setProjectionArea(glm::vec2(1.f, 1.f));
|
||||
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, 0.f);
|
||||
}
|
||||
|
||||
void FrameBuffer::init()
|
||||
{
|
||||
// generate texture
|
||||
glGenTextures(1, &textureid_);
|
||||
glBindTexture(GL_TEXTURE_2D, textureid_);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, use_alpha_ ? GL_RGBA8 : GL_RGB8, attrib_.viewport.x, attrib_.viewport.y);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
// create a framebuffer object
|
||||
glGenFramebuffers(1, &framebufferid_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebufferid_);
|
||||
|
||||
// take settings into account: no multisampling for level 0
|
||||
use_multi_sampling_ &= Settings::application.render.multisampling > 0;
|
||||
|
||||
if (use_multi_sampling_){
|
||||
|
||||
// create a multisample texture
|
||||
glGenTextures(1, &intermediate_textureid_);
|
||||
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, intermediate_textureid_);
|
||||
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, Settings::application.render.multisampling,
|
||||
use_alpha_ ? GL_RGBA8 : GL_RGB8, attrib_.viewport.x, attrib_.viewport.y, GL_TRUE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
|
||||
|
||||
// attach the multisampled texture to FBO (framebufferid_ currently binded)
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, intermediate_textureid_, 0);
|
||||
|
||||
// create an intermediate FBO : this is the FBO to use for reading
|
||||
glGenFramebuffers(1, &intermediate_framebufferid_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, intermediate_framebufferid_);
|
||||
|
||||
// attach the 2D texture to intermediate FBO (intermediate_framebufferid_)
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
|
||||
|
||||
// Log::Info("New FBO %d Multi Sampling ", framebufferid_);
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
// direct attach the 2D texture to FBO
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
|
||||
|
||||
// Log::Info("New FBO %d Single Sampling ", framebufferid_);
|
||||
}
|
||||
|
||||
checkFramebufferStatus();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
}
|
||||
|
||||
FrameBuffer::~FrameBuffer()
|
||||
{
|
||||
if (framebufferid_)
|
||||
glDeleteFramebuffers(1, &framebufferid_);
|
||||
if (intermediate_framebufferid_)
|
||||
glDeleteFramebuffers(1, &intermediate_framebufferid_);
|
||||
if (textureid_)
|
||||
glDeleteTextures(1, &textureid_);
|
||||
if (intermediate_textureid_)
|
||||
glDeleteTextures(1, &intermediate_textureid_);
|
||||
}
|
||||
|
||||
|
||||
uint FrameBuffer::texture() const
|
||||
{
|
||||
if (framebufferid_ == 0)
|
||||
return Resource::getTextureBlack();
|
||||
|
||||
return textureid_;
|
||||
}
|
||||
|
||||
float FrameBuffer::aspectRatio() const
|
||||
{
|
||||
return static_cast<float>(attrib_.viewport.x) / static_cast<float>(attrib_.viewport.y);
|
||||
}
|
||||
|
||||
|
||||
std::string FrameBuffer::info() const
|
||||
{
|
||||
glm::ivec2 p = FrameBuffer::getParametersFromResolution(resolution());
|
||||
std::ostringstream info;
|
||||
|
||||
info << attrib_.viewport.x << "x" << attrib_.viewport.y;
|
||||
if (p.x > -1)
|
||||
info << "px, " << FrameBuffer::aspect_ratio_name[p.x];
|
||||
|
||||
return info.str();
|
||||
}
|
||||
|
||||
glm::vec3 FrameBuffer::resolution() const
|
||||
{
|
||||
return glm::vec3(attrib_.viewport.x, attrib_.viewport.y, 0.f);
|
||||
}
|
||||
|
||||
void FrameBuffer::begin(bool clear)
|
||||
{
|
||||
if (!framebufferid_)
|
||||
init();
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebufferid_);
|
||||
|
||||
Rendering::manager().pushAttrib(attrib_);
|
||||
|
||||
if (clear)
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void FrameBuffer::end()
|
||||
{
|
||||
// if multisampling frame buffer
|
||||
if (use_multi_sampling_) {
|
||||
// blit the multisample FBO into unisample FBO to generate 2D texture
|
||||
// Doing this blit will automatically resolve the multisampled FBO.
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediate_framebufferid_);
|
||||
glBlitFramebuffer(0, 0, attrib_.viewport.x, attrib_.viewport.y,
|
||||
0, 0, attrib_.viewport.x, attrib_.viewport.y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
|
||||
FrameBuffer::release();
|
||||
|
||||
Rendering::manager().popAttrib();
|
||||
}
|
||||
|
||||
void FrameBuffer::release()
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void FrameBuffer::readPixels(uint8_t *target_data)
|
||||
{
|
||||
if (!framebufferid_)
|
||||
return;
|
||||
|
||||
if (use_multi_sampling_)
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, intermediate_framebufferid_);
|
||||
else
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
|
||||
|
||||
if (use_alpha())
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
else
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
|
||||
glReadPixels(0, 0, attrib_.viewport.x, attrib_.viewport.y, (use_alpha_? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, target_data);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
bool FrameBuffer::blit(FrameBuffer *destination)
|
||||
{
|
||||
if (!framebufferid_ || !destination)
|
||||
return false;
|
||||
|
||||
if (!destination->framebufferid_)
|
||||
destination->init();
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination->framebufferid_);
|
||||
// blit to the frame buffer object
|
||||
glBlitFramebuffer(0, 0, attrib_.viewport.x, attrib_.viewport.y,
|
||||
0, 0, destination->width(), destination->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FrameBuffer::checkFramebufferStatus()
|
||||
{
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
switch (status){
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
||||
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT is returned if any of the framebuffer attachment points are framebuffer incomplete.");
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
||||
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT is returned if the framebuffer does not have at least one image attached to it.");
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
|
||||
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER is returned if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment point(s) named by GL_DRAWBUFFERi.");
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
|
||||
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER is returned if GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point named by GL_READ_BUFFER.");
|
||||
break;
|
||||
case GL_FRAMEBUFFER_UNSUPPORTED:
|
||||
Log::Warning("GL_FRAMEBUFFER_UNSUPPORTED is returned if the combination of internal formats of the attached images violates an implementation-dependent set of restrictions.");
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
|
||||
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is returned if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES.\nGL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is also returned if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not the same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures.");
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
|
||||
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS is returned if any framebuffer attachment is layered, and any populated attachment is not layered, or if all populated color attachments are not from textures of the same target.");
|
||||
break;
|
||||
case GL_FRAMEBUFFER_UNDEFINED:
|
||||
Log::Warning(" GL_FRAMEBUFFER_UNDEFINED is returned if target is the default framebuffer, but the default framebuffer does not exist.");
|
||||
break;
|
||||
case GL_FRAMEBUFFER_COMPLETE:
|
||||
#ifndef NDEBUG
|
||||
Log::Info("Framebuffer created %d x %d.", width(), height());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
glm::mat4 FrameBuffer::projection() const
|
||||
{
|
||||
return projection_;
|
||||
}
|
||||
|
||||
glm::vec2 FrameBuffer::projectionArea() const
|
||||
{
|
||||
return projection_area_;
|
||||
}
|
||||
|
||||
void FrameBuffer::setProjectionArea(glm::vec2 c)
|
||||
{
|
||||
projection_area_.x = CLAMP(c.x, 0.1f, 1.f);
|
||||
projection_area_.y = CLAMP(c.y, 0.1f, 1.f);
|
||||
projection_ = glm::ortho(-projection_area_.x, projection_area_.x, projection_area_.y, -projection_area_.y, -1.f, 1.f);
|
||||
}
|
||||
|
||||
|
||||
FrameBufferImage::FrameBufferImage(int w, int h) :
|
||||
rgb(nullptr), width(w), height(h)
|
||||
{
|
||||
if (width>0 && height>0)
|
||||
rgb = new uint8_t[width*height*3];
|
||||
}
|
||||
|
||||
FrameBufferImage::FrameBufferImage(jpegBuffer jpgimg) :
|
||||
rgb(nullptr), width(0), height(0)
|
||||
{
|
||||
int c = 0;
|
||||
if (jpgimg.buffer != nullptr && jpgimg.len >0)
|
||||
rgb = stbi_load_from_memory(jpgimg.buffer, jpgimg.len, &width, &height, &c, 3);
|
||||
}
|
||||
|
||||
FrameBufferImage::~FrameBufferImage() {
|
||||
if (rgb!=nullptr)
|
||||
delete rgb;
|
||||
}
|
||||
|
||||
FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg()
|
||||
{
|
||||
jpegBuffer jpgimg;
|
||||
|
||||
// if we hold a valid image
|
||||
if (rgb!=nullptr && width>0 && height>0) {
|
||||
|
||||
// allocate JPEG buffer
|
||||
// (NB: JPEG will need less than this but we can't know before...)
|
||||
jpgimg.buffer = (unsigned char *) malloc( width * height * 3 * sizeof(unsigned char));
|
||||
|
||||
stbi_write_jpg_to_func( [](void *context, void *data, int size)
|
||||
{
|
||||
memcpy(((FrameBufferImage::jpegBuffer*)context)->buffer + ((FrameBufferImage::jpegBuffer*)context)->len, data, size);
|
||||
((FrameBufferImage::jpegBuffer*)context)->len += size;
|
||||
}
|
||||
,&jpgimg, width, height, 3, rgb, FBI_JPEG_QUALITY);
|
||||
}
|
||||
|
||||
return jpgimg;
|
||||
}
|
||||
|
||||
FrameBufferImage *FrameBuffer::image(){
|
||||
|
||||
FrameBufferImage *img = nullptr;
|
||||
|
||||
// not ready
|
||||
if (!framebufferid_)
|
||||
return img;
|
||||
|
||||
// allocate image
|
||||
img = new FrameBufferImage(attrib_.viewport.x, attrib_.viewport.y);
|
||||
|
||||
// get pixels into image
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // set buffer target readpixel
|
||||
readPixels(img->rgb);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
bool FrameBuffer::fill(FrameBufferImage *image)
|
||||
{
|
||||
if (!framebufferid_)
|
||||
init();
|
||||
|
||||
// not compatible for RGB
|
||||
if (use_alpha_ || use_multi_sampling_)
|
||||
return false;
|
||||
|
||||
// invalid image
|
||||
if ( image == nullptr ||
|
||||
image->rgb==nullptr ||
|
||||
image->width !=attrib_.viewport.x ||
|
||||
image->height!=attrib_.viewport.y )
|
||||
return false;
|
||||
|
||||
// fill texture with image
|
||||
glBindTexture(GL_TEXTURE_2D, textureid_);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height,
|
||||
GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
357
FrameGrabber.cpp
@@ -1,357 +0,0 @@
|
||||
#include <algorithm>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// gstreamer
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "FrameBuffer.h"
|
||||
|
||||
#include "FrameGrabber.h"
|
||||
|
||||
|
||||
|
||||
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(nullptr)
|
||||
{
|
||||
pbo_[0] = 0;
|
||||
pbo_[1] = 0;
|
||||
}
|
||||
|
||||
FrameGrabbing::~FrameGrabbing()
|
||||
{
|
||||
// stop and delete all frame grabbers
|
||||
clearAll();
|
||||
|
||||
// cleanup
|
||||
if (caps_!=nullptr)
|
||||
gst_caps_unref (caps_);
|
||||
if (pbo_[0])
|
||||
glDeleteBuffers(2, pbo_);
|
||||
}
|
||||
|
||||
void FrameGrabbing::add(FrameGrabber *rec)
|
||||
{
|
||||
if (rec != nullptr)
|
||||
grabbers_.push_back(rec);
|
||||
}
|
||||
|
||||
FrameGrabber *FrameGrabbing::front()
|
||||
{
|
||||
if (grabbers_.empty())
|
||||
return nullptr;
|
||||
else
|
||||
return grabbers_.front();
|
||||
}
|
||||
|
||||
struct fgId: public std::unary_function<FrameGrabber*, bool>
|
||||
{
|
||||
inline bool operator()(const FrameGrabber* elem) const {
|
||||
return (elem && elem->id() == _id);
|
||||
}
|
||||
fgId(uint64_t id) : _id(id) { }
|
||||
private:
|
||||
uint64_t _id;
|
||||
};
|
||||
|
||||
FrameGrabber *FrameGrabbing::get(uint64_t id)
|
||||
{
|
||||
if (id > 0 && grabbers_.size() > 0 )
|
||||
{
|
||||
std::list<FrameGrabber *>::iterator iter = std::find_if(grabbers_.begin(), grabbers_.end(), fgId(id));
|
||||
if (iter != grabbers_.end())
|
||||
return (*iter);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FrameGrabbing::stopAll()
|
||||
{
|
||||
std::list<FrameGrabber *>::iterator iter;
|
||||
for (iter=grabbers_.begin(); iter != grabbers_.end(); iter++ )
|
||||
(*iter)->stop();
|
||||
}
|
||||
|
||||
void FrameGrabbing::clearAll()
|
||||
{
|
||||
std::list<FrameGrabber *>::iterator iter;
|
||||
for (iter=grabbers_.begin(); iter != grabbers_.end(); )
|
||||
{
|
||||
FrameGrabber *rec = *iter;
|
||||
rec->stop();
|
||||
iter = grabbers_.erase(iter);
|
||||
delete rec;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
|
||||
{
|
||||
if (frame_buffer == nullptr)
|
||||
return;
|
||||
|
||||
// if different frame buffer from previous frame
|
||||
if ( frame_buffer->width() != width_ ||
|
||||
frame_buffer->height() != height_ ||
|
||||
frame_buffer->use_alpha() != use_alpha_) {
|
||||
|
||||
// define stream properties
|
||||
width_ = frame_buffer->width();
|
||||
height_ = frame_buffer->height();
|
||||
use_alpha_ = frame_buffer->use_alpha();
|
||||
size_ = width_ * height_ * (use_alpha_ ? 4 : 3);
|
||||
|
||||
// first time initialization
|
||||
if ( pbo_[0] == 0 )
|
||||
glGenBuffers(2, pbo_);
|
||||
|
||||
// re-affect pixel buffer object
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[1]);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
||||
|
||||
// reset indices
|
||||
pbo_index_ = 0;
|
||||
pbo_next_index_ = 0;
|
||||
|
||||
// new caps
|
||||
if (caps_!=nullptr)
|
||||
gst_caps_unref (caps_);
|
||||
caps_ = gst_caps_new_simple ("video/x-raw",
|
||||
"format", G_TYPE_STRING, use_alpha_ ? "RGBA" : "RGB",
|
||||
"width", G_TYPE_INT, width_,
|
||||
"height", G_TYPE_INT, height_,
|
||||
"framerate", GST_TYPE_FRACTION, 30, 1,
|
||||
NULL);
|
||||
}
|
||||
|
||||
// fill a frame in buffer
|
||||
if (!grabbers_.empty() && size_ > 0) {
|
||||
|
||||
GstBuffer *buffer = nullptr;
|
||||
|
||||
// set buffer target for writing in a new frame
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_index_]);
|
||||
|
||||
#ifdef USE_GLREADPIXEL
|
||||
// get frame
|
||||
frame_buffer->readPixels();
|
||||
#else
|
||||
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
|
||||
// update case ; alternating indices
|
||||
if ( pbo_next_index_ != pbo_index_ ) {
|
||||
|
||||
// set buffer target for saving the frame
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_next_index_]);
|
||||
|
||||
// new buffer
|
||||
buffer = gst_buffer_new_and_alloc (size_);
|
||||
|
||||
// map gst buffer into a memory WRITE target
|
||||
GstMapInfo map;
|
||||
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
|
||||
|
||||
// map PBO pixels into a memory READ pointer
|
||||
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
|
||||
// transfer pixels from PBO memory to buffer memory
|
||||
if (NULL != ptr)
|
||||
memmove(map.data, ptr, size_);
|
||||
|
||||
// un-map
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
// alternate indices
|
||||
pbo_next_index_ = pbo_index_;
|
||||
pbo_index_ = (pbo_index_ + 1) % 2;
|
||||
|
||||
// a frame was successfully grabbed
|
||||
if (buffer != nullptr) {
|
||||
|
||||
// give the frame to all recorders
|
||||
std::list<FrameGrabber *>::iterator iter = grabbers_.begin();
|
||||
while (iter != grabbers_.end())
|
||||
{
|
||||
FrameGrabber *rec = *iter;
|
||||
rec->addFrame(buffer, caps_, dt);
|
||||
|
||||
if (rec->finished()) {
|
||||
iter = grabbers_.erase(iter);
|
||||
delete rec;
|
||||
}
|
||||
else
|
||||
iter++;
|
||||
}
|
||||
|
||||
// unref / free the frame
|
||||
gst_buffer_unref(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
FrameGrabber::FrameGrabber(): finished_(false), active_(false), accept_buffer_(false),
|
||||
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timestamp_(0)
|
||||
{
|
||||
// unique id
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
// configure fix parameter
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
|
||||
timeframe_ = 2 * frame_duration_;
|
||||
}
|
||||
|
||||
FrameGrabber::~FrameGrabber()
|
||||
{
|
||||
if (src_ != nullptr)
|
||||
gst_object_unref (src_);
|
||||
if (caps_ != nullptr)
|
||||
gst_caps_unref (caps_);
|
||||
if (pipeline_ != nullptr) {
|
||||
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline_);
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameGrabber::finished() const
|
||||
{
|
||||
return finished_;
|
||||
}
|
||||
|
||||
bool FrameGrabber::busy() const
|
||||
{
|
||||
if (active_)
|
||||
return accept_buffer_ ? true : false;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
double FrameGrabber::duration() const
|
||||
{
|
||||
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
|
||||
}
|
||||
|
||||
void FrameGrabber::stop ()
|
||||
{
|
||||
// send end of stream
|
||||
gst_app_src_end_of_stream (src_);
|
||||
|
||||
// stop recording
|
||||
active_ = false;
|
||||
}
|
||||
|
||||
std::string FrameGrabber::info() const
|
||||
{
|
||||
if (active_)
|
||||
return GstToolkit::time_to_string(timestamp_);
|
||||
else
|
||||
return "Inactive";
|
||||
}
|
||||
|
||||
// appsrc needs data and we should start sending
|
||||
void FrameGrabber::callback_need_data (GstAppSrc *, guint , gpointer p)
|
||||
{
|
||||
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
|
||||
if (grabber)
|
||||
grabber->accept_buffer_ = true;
|
||||
}
|
||||
|
||||
// appsrc has enough data and we can stop sending
|
||||
void FrameGrabber::callback_enough_data (GstAppSrc *, gpointer p)
|
||||
{
|
||||
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
|
||||
if (grabber)
|
||||
grabber->accept_buffer_ = false;
|
||||
}
|
||||
|
||||
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
|
||||
{
|
||||
// ignore
|
||||
if (buffer == nullptr)
|
||||
return;
|
||||
|
||||
// first time initialization
|
||||
if (pipeline_ == nullptr)
|
||||
init(caps);
|
||||
|
||||
// cancel if finished
|
||||
if (finished_)
|
||||
return;
|
||||
|
||||
// stop if an incompatilble frame buffer given
|
||||
if ( !gst_caps_is_equal( caps_, caps ))
|
||||
{
|
||||
stop();
|
||||
// Log::Warning("FrameGrabber interrupted: new session (%s)\nincompatible with recording (%s)", gst_caps_to_string(frame.caps), gst_caps_to_string(caps_));
|
||||
Log::Warning("FrameGrabber interrupted because the resolution changed.");
|
||||
}
|
||||
|
||||
// store a frame if recording is active
|
||||
if (active_)
|
||||
{
|
||||
// calculate dt in ns
|
||||
timeframe_ += gst_gdouble_to_guint64( dt * 1000000.f );
|
||||
|
||||
// if time is passed one frame duration (with 10% margin)
|
||||
// and if the encoder accepts data
|
||||
if ( timeframe_ > frame_duration_ - 3000000 && accept_buffer_) {
|
||||
|
||||
// set timing of buffer
|
||||
buffer->pts = timestamp_;
|
||||
buffer->duration = frame_duration_;
|
||||
|
||||
// increment ref counter to make sure the frame remains available
|
||||
gst_buffer_ref(buffer);
|
||||
|
||||
// push
|
||||
gst_app_src_push_buffer (src_, buffer);
|
||||
// NB: buffer will be unrefed by the appsrc
|
||||
|
||||
accept_buffer_ = false;
|
||||
|
||||
// next timestamp
|
||||
timestamp_ += frame_duration_;
|
||||
|
||||
// restart frame counter
|
||||
timeframe_ = 0;
|
||||
}
|
||||
}
|
||||
// did the recording terminate with sink receiving end-of-stream ?
|
||||
else {
|
||||
|
||||
if (!finished_)
|
||||
{
|
||||
// Wait for EOS message
|
||||
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
|
||||
GstMessage *msg = gst_bus_poll(bus, GST_MESSAGE_EOS, GST_TIME_AS_USECONDS(1));
|
||||
// received EOS
|
||||
if (msg) {
|
||||
// stop the pipeline
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
Log::Warning("FrameGrabber Could not stop.");
|
||||
|
||||
finished_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (finished_)
|
||||
terminate();
|
||||
|
||||
}
|
||||
128
FrameGrabber.h
@@ -1,128 +0,0 @@
|
||||
#ifndef FRAMEGRABBER_H
|
||||
#define FRAMEGRABBER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
// use glReadPixel or glGetTextImage
|
||||
// read pixels & pbo should be the fastest
|
||||
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
|
||||
#define USE_GLREADPIXEL
|
||||
|
||||
class FrameBuffer;
|
||||
|
||||
|
||||
/**
|
||||
* @brief The FrameGrabber class defines the base class for all recorders
|
||||
* used to save images or videos from a frame buffer.
|
||||
*
|
||||
* Every subclass shall at least implement init() and terminate()
|
||||
*
|
||||
* The FrameGrabbing manager calls addFrame() for all its grabbers.
|
||||
*/
|
||||
class FrameGrabber
|
||||
{
|
||||
friend class FrameGrabbing;
|
||||
|
||||
uint64_t id_;
|
||||
|
||||
public:
|
||||
FrameGrabber();
|
||||
virtual ~FrameGrabber();
|
||||
|
||||
inline uint64_t id() const { return id_; }
|
||||
|
||||
virtual void stop();
|
||||
virtual std::string info() const;
|
||||
virtual double duration() const;
|
||||
virtual bool finished() const;
|
||||
virtual bool busy() const;
|
||||
|
||||
protected:
|
||||
|
||||
// only FrameGrabbing manager can add frame
|
||||
virtual void addFrame(GstBuffer *buffer, GstCaps *caps, float dt);
|
||||
|
||||
// only addFrame method shall call those
|
||||
virtual void init(GstCaps *caps) = 0;
|
||||
virtual void terminate() = 0;
|
||||
|
||||
// thread-safe testing termination
|
||||
std::atomic<bool> finished_;
|
||||
std::atomic<bool> active_;
|
||||
std::atomic<bool> accept_buffer_;
|
||||
|
||||
// gstreamer pipeline
|
||||
GstElement *pipeline_;
|
||||
GstAppSrc *src_;
|
||||
GstCaps *caps_;
|
||||
GstClockTime timeframe_;
|
||||
GstClockTime timestamp_;
|
||||
GstClockTime frame_duration_;
|
||||
|
||||
// gstreamer callbacks
|
||||
static void callback_need_data (GstAppSrc *, guint, gpointer user_data);
|
||||
static void callback_enough_data (GstAppSrc *, gpointer user_data);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The FrameGrabbing class manages all frame grabbers
|
||||
*
|
||||
* Session calls grabFrame after each render
|
||||
*
|
||||
*/
|
||||
class FrameGrabbing
|
||||
{
|
||||
friend class Mixer;
|
||||
|
||||
// Private Constructor
|
||||
FrameGrabbing();
|
||||
FrameGrabbing(FrameGrabbing const& copy); // Not Implemented
|
||||
FrameGrabbing& operator=(FrameGrabbing const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
|
||||
static FrameGrabbing& manager()
|
||||
{
|
||||
// The only instance
|
||||
static FrameGrabbing _instance;
|
||||
return _instance;
|
||||
}
|
||||
~FrameGrabbing();
|
||||
|
||||
inline uint width() const { return width_; }
|
||||
inline uint height() const { return height_; }
|
||||
|
||||
void add(FrameGrabber *rec);
|
||||
FrameGrabber *front();
|
||||
FrameGrabber *get(uint64_t id);
|
||||
void stopAll();
|
||||
void clearAll();
|
||||
|
||||
protected:
|
||||
|
||||
// only for friend Session
|
||||
void grabFrame(FrameBuffer *frame_buffer, float dt);
|
||||
|
||||
private:
|
||||
std::list<FrameGrabber *> grabbers_;
|
||||
guint pbo_[2];
|
||||
guint pbo_index_;
|
||||
guint pbo_next_index_;
|
||||
guint size_;
|
||||
guint width_;
|
||||
guint height_;
|
||||
bool use_alpha_;
|
||||
GstCaps *caps_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // FRAMEGRABBER_H
|
||||
@@ -1,154 +0,0 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Scene.h"
|
||||
#include "GarbageVisitor.h"
|
||||
|
||||
|
||||
GarbageVisitor::GarbageVisitor(Node *nodetocollect) : Visitor()
|
||||
{
|
||||
targets_.push_front(nodetocollect);
|
||||
current_ = nullptr;
|
||||
found_ = false;
|
||||
}
|
||||
|
||||
GarbageVisitor::GarbageVisitor(Source *sourcetocollect) : Visitor()
|
||||
{
|
||||
targets_.push_front(sourcetocollect->group(View::MIXING));
|
||||
targets_.push_front(sourcetocollect->group(View::GEOMETRY));
|
||||
targets_.push_front(sourcetocollect->group(View::RENDERING));
|
||||
current_ = nullptr;
|
||||
found_ = false;
|
||||
}
|
||||
|
||||
|
||||
void GarbageVisitor::visit(Node &n)
|
||||
{
|
||||
// found the target
|
||||
// if (n.id() == target_->id())
|
||||
// if ( &n == target_ )
|
||||
if ( std::count(targets_.begin(), targets_.end(), &n) )
|
||||
{
|
||||
// end recursive
|
||||
found_ = true;
|
||||
|
||||
// take the node out of the Tree
|
||||
if (current_)
|
||||
current_->detach(&n);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GarbageVisitor::~GarbageVisitor()
|
||||
{
|
||||
// actually delete the Node
|
||||
|
||||
}
|
||||
|
||||
|
||||
void GarbageVisitor::visit(Group &n)
|
||||
{
|
||||
// no need to go further if the node was found (or is this group)
|
||||
if (found_)
|
||||
return;
|
||||
|
||||
// current group
|
||||
current_ = &n;
|
||||
|
||||
// loop over members of a group
|
||||
// and stop when found
|
||||
for (NodeSet::iterator node = n.begin(); !found_ && node != n.end(); node++) {
|
||||
// visit the child node
|
||||
(*node)->accept(*this);
|
||||
// un-stack recursive browsing
|
||||
current_ = &n;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Scene &n)
|
||||
{
|
||||
n.root()->accept(*this);
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Switch &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Primitive &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Surface &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(ImageSurface &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(FrameBufferSurface &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(MediaSurface &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(MediaPlayer &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Shader &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(ImageShader &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(ImageProcessingShader &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(LineStrip &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(LineSquare &)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Mesh &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Frame &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void GarbageVisitor::visit (Source& s)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit (MediaSource& s)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#ifndef GARBAGEVISITOR_H
|
||||
#define GARBAGEVISITOR_H
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "Source.h"
|
||||
#include "Visitor.h"
|
||||
|
||||
|
||||
class GarbageVisitor : public Visitor
|
||||
{
|
||||
Group *current_;
|
||||
std::list<Node *> targets_;
|
||||
bool found_;
|
||||
|
||||
public:
|
||||
GarbageVisitor(Source *sourcetocollect);
|
||||
GarbageVisitor(Node *nodetocollect);
|
||||
~GarbageVisitor();
|
||||
|
||||
void visit(Scene& n) override;
|
||||
void visit(Node& n) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
void visit(Primitive& n) override;
|
||||
|
||||
void visit(Surface& n) override;
|
||||
void visit(ImageSurface& n) override;
|
||||
void visit(MediaSurface& n) override;
|
||||
void visit(FrameBufferSurface& n) override;
|
||||
void visit(LineStrip& n) override;
|
||||
void visit(LineSquare&) override;
|
||||
void visit(Mesh& n) override;
|
||||
void visit(Frame& n) override;
|
||||
|
||||
void visit(MediaPlayer& n) override;
|
||||
void visit(Shader& n) override;
|
||||
void visit(ImageShader& n) override;
|
||||
void visit(ImageProcessingShader& n) override;
|
||||
|
||||
void visit (Source& s) override;
|
||||
void visit (MediaSource& s) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // GARBAGEVISITOR_H
|
||||
1121
GeometryView.cpp
@@ -1,46 +0,0 @@
|
||||
#ifndef GEOMETRYVIEW_H
|
||||
#define GEOMETRYVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class GeometryView : public View
|
||||
{
|
||||
public:
|
||||
GeometryView();
|
||||
|
||||
void draw () override;
|
||||
void update (float dt) override;
|
||||
void resize (int) override;
|
||||
int size () override;
|
||||
bool canSelect(Source *) override;
|
||||
|
||||
std::pair<Node *, glm::vec2> pick(glm::vec2 P) override;
|
||||
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
|
||||
void terminate() override;
|
||||
void arrow (glm::vec2) override;
|
||||
|
||||
private:
|
||||
Surface *output_surface_;
|
||||
Node *overlay_position_;
|
||||
Node *overlay_position_cross_;
|
||||
Symbol *overlay_rotation_;
|
||||
Symbol *overlay_rotation_fix_;
|
||||
Group *overlay_rotation_clock_;
|
||||
Symbol *overlay_rotation_clock_tic_;
|
||||
Node *overlay_rotation_clock_hand_;
|
||||
Symbol *overlay_scaling_;
|
||||
Symbol *overlay_scaling_cross_;
|
||||
Node *overlay_scaling_grid_;
|
||||
Node *overlay_crop_;
|
||||
|
||||
void updateSelectionOverlay() override;
|
||||
bool overlay_selection_active_;
|
||||
Group *overlay_selection_stored_status_;
|
||||
Handles *overlay_selection_scale_;
|
||||
Handles *overlay_selection_rotate_;
|
||||
|
||||
void applySelectionTransform(glm::mat4 M);
|
||||
};
|
||||
|
||||
|
||||
#endif // GEOMETRYVIEW_H
|
||||
172
GstToolkit.cpp
@@ -1,172 +0,0 @@
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
using namespace std;
|
||||
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
#include "GstToolkit.h"
|
||||
|
||||
string GstToolkit::time_to_string(guint64 t, time_string_mode m)
|
||||
{
|
||||
if (t == GST_CLOCK_TIME_NONE) {
|
||||
switch (m) {
|
||||
case TIME_STRING_FIXED:
|
||||
return "00:00:00.00";
|
||||
case TIME_STRING_MINIMAL:
|
||||
return "0.0";
|
||||
default:
|
||||
return "00.00";
|
||||
}
|
||||
}
|
||||
|
||||
guint ms = GST_TIME_AS_MSECONDS(t);
|
||||
guint s = ms / 1000;
|
||||
ostringstream oss;
|
||||
|
||||
// MINIMAL: keep only the 2 higher values (most significant)
|
||||
if (m == TIME_STRING_MINIMAL) {
|
||||
int count = 0;
|
||||
if (s / 3600) {
|
||||
oss << s / 3600 << ':';
|
||||
count++;
|
||||
}
|
||||
if ((s % 3600) / 60) {
|
||||
oss << (s % 3600) / 60 << ':';
|
||||
count++;
|
||||
}
|
||||
if (count < 2) {
|
||||
oss << setw(count > 0 ? 2 : 1) << setfill('0') << (s % 3600) % 60;
|
||||
count++;
|
||||
}
|
||||
if (count < 2 )
|
||||
oss << '.'<< setw(1) << setfill('0') << (ms % 1000) / 10;
|
||||
}
|
||||
else {
|
||||
// TIME_STRING_FIXED : fixed length string (11 chars) HH:mm:ss.ii"
|
||||
// TIME_STRING_RIGHT : always show the right part (seconds), not the min or hours if none
|
||||
if (m == TIME_STRING_FIXED || (s / 3600) )
|
||||
oss << setw(2) << setfill('0') << s / 3600 << ':';
|
||||
if (m == TIME_STRING_FIXED || ((s % 3600) / 60) )
|
||||
oss << setw(2) << setfill('0') << (s % 3600) / 60 << ':';
|
||||
oss << setw(2) << setfill('0') << (s % 3600) % 60 << '.';
|
||||
oss << setw(2) << setfill('0') << (ms % 1000) / 10;
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
std::string GstToolkit::filename_to_uri(std::string path)
|
||||
{
|
||||
// set uri to open
|
||||
gchar *uritmp = gst_filename_to_uri(path.c_str(), NULL);
|
||||
std::string uri( uritmp );
|
||||
g_free(uritmp);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
list<string> GstToolkit::all_plugins()
|
||||
{
|
||||
list<string> pluginlist;
|
||||
GList *l, *g;
|
||||
|
||||
l = gst_registry_get_plugin_list (gst_registry_get ());
|
||||
|
||||
for (g = l; g; g = g->next) {
|
||||
GstPlugin *plugin = GST_PLUGIN (g->data);
|
||||
pluginlist.push_front( string( gst_plugin_get_name (plugin) ) );
|
||||
}
|
||||
|
||||
gst_plugin_list_free (l);
|
||||
|
||||
return pluginlist;
|
||||
}
|
||||
|
||||
|
||||
list<string> GstToolkit::all_plugin_features(string pluginname)
|
||||
{
|
||||
list<string> featurelist;
|
||||
GList *l, *g;
|
||||
|
||||
l = gst_registry_get_feature_list_by_plugin (gst_registry_get (), pluginname.c_str());
|
||||
|
||||
for (g = l; g; g = g->next) {
|
||||
GstPluginFeature *feature = GST_PLUGIN_FEATURE (g->data);
|
||||
featurelist.push_front( string( gst_plugin_feature_get_name (feature) ) );
|
||||
}
|
||||
|
||||
gst_plugin_feature_list_free (l);
|
||||
|
||||
return featurelist;
|
||||
}
|
||||
|
||||
bool GstToolkit::enable_feature (string name, bool enable) {
|
||||
GstRegistry *registry = NULL;
|
||||
GstElementFactory *factory = NULL;
|
||||
|
||||
registry = gst_registry_get();
|
||||
if (!registry) return false;
|
||||
|
||||
factory = gst_element_factory_find (name.c_str());
|
||||
if (!factory) return false;
|
||||
|
||||
if (enable) {
|
||||
gst_plugin_feature_set_rank (GST_PLUGIN_FEATURE (factory), GST_RANK_PRIMARY + 1);
|
||||
}
|
||||
else {
|
||||
gst_plugin_feature_set_rank (GST_PLUGIN_FEATURE (factory), GST_RANK_NONE);
|
||||
}
|
||||
|
||||
gst_registry_add_feature (registry, GST_PLUGIN_FEATURE (factory));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
string GstToolkit::gst_version()
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << GST_VERSION_MAJOR << '.' << GST_VERSION_MINOR << '.';
|
||||
oss << std::setw(2) << setfill('0') << GST_VERSION_MICRO ;
|
||||
if (GST_VERSION_NANO > 0)
|
||||
oss << ( (GST_VERSION_NANO < 2 ) ? " - (CVS)" : " - (Prerelease)");
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
// see https://developer.ridgerun.com/wiki/index.php?title=GStreamer_modify_the_elements_rank
|
||||
|
||||
std::list<std::string> GstToolkit::enable_gpu_decoding_plugins(bool enable)
|
||||
{
|
||||
static list<string> pluginslist;
|
||||
static GstRegistry* plugins_register = nullptr;
|
||||
|
||||
if ( plugins_register == nullptr ) {
|
||||
plugins_register = gst_registry_get();
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
// https://gstreamer.freedesktop.org/documentation/nvcodec/index.html?gi-language=c#plugin-nvcodec
|
||||
const char *plugins[6] = { "nvh264dec", "nvh265dec", "nvmpeg2videodec",
|
||||
"nvmpeg4videodec", "nvvp8dec", "nvvp9dec" };
|
||||
const int N = 6;
|
||||
#elif GST_GL_HAVE_PLATFORM_CGL
|
||||
const char *plugins[1] = { "vtdec_hw" };
|
||||
const int N = 1;
|
||||
#else
|
||||
const char *plugins[0] = { };
|
||||
const int N = 0;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
GstPluginFeature* feature = gst_registry_lookup_feature(plugins_register, plugins[i]);
|
||||
if(feature != NULL) {
|
||||
pluginslist.push_front( string( plugins[i] ) );
|
||||
gst_plugin_feature_set_rank(feature, enable ? GST_RANK_PRIMARY + 1 : GST_RANK_MARGINAL);
|
||||
gst_object_unref(feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pluginslist;
|
||||
}
|
||||
31
GstToolkit.h
@@ -1,31 +0,0 @@
|
||||
#ifndef __GSTGUI_TOOLKIT_H_
|
||||
#define __GSTGUI_TOOLKIT_H_
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
namespace GstToolkit
|
||||
{
|
||||
|
||||
typedef enum {
|
||||
TIME_STRING_FIXED = 0,
|
||||
TIME_STRING_ADJUSTED,
|
||||
TIME_STRING_MINIMAL
|
||||
} time_string_mode;
|
||||
|
||||
std::string time_to_string(guint64 t, time_string_mode m = TIME_STRING_ADJUSTED);
|
||||
std::string filename_to_uri(std::string filename);
|
||||
|
||||
std::string gst_version();
|
||||
|
||||
std::list<std::string> all_plugins();
|
||||
std::list<std::string> enable_gpu_decoding_plugins(bool enable = true);
|
||||
|
||||
std::list<std::string> all_plugin_features(std::string pluginname);
|
||||
bool enable_feature (std::string name, bool enable);
|
||||
|
||||
}
|
||||
|
||||
#endif // __GSTGUI_TOOLKIT_H_
|
||||
1212
ImGuiToolkit.cpp
@@ -1,69 +0,0 @@
|
||||
#ifndef __IMGUI_TOOLKIT_H_
|
||||
#define __IMGUI_TOOLKIT_H_
|
||||
|
||||
#include <glib.h>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "rsc/fonts/IconsFontAwesome5.h"
|
||||
|
||||
namespace ImGuiToolkit
|
||||
{
|
||||
// Icons from resource icon.dds
|
||||
void Icon (int i, int j, bool enabled = true);
|
||||
bool IconButton (int i, int j, const char *tooltips = nullptr);
|
||||
bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr);
|
||||
void ShowIconsWindow(bool* p_open);
|
||||
|
||||
// icon buttons
|
||||
bool ButtonIcon (int i, int j, const char* tooltip = nullptr);
|
||||
bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle);
|
||||
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state);
|
||||
bool ComboIcon (std::vector<std::pair<int, int> > icons, std::vector<std::string> labels, int* state);
|
||||
|
||||
// utility buttons
|
||||
bool ButtonToggle (const char* label, bool* toggle);
|
||||
void ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr);
|
||||
void ButtonOpenUrl (const char* url, const ImVec2& size_arg = ImVec2(0,0));
|
||||
|
||||
void ToolTip (const char* desc, const char* shortcut = nullptr);
|
||||
void HelpMarker (const char* desc, const char* icon = ICON_FA_QUESTION_CIRCLE, const char* shortcut = nullptr);
|
||||
void HelpIcon (const char* desc, int i = 19, int j = 5, const char* shortcut = nullptr);
|
||||
|
||||
// utility sliders
|
||||
bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width);
|
||||
bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size);
|
||||
bool EditPlotLines(const char* label, float *array, int values_count, float values_min, float values_max, const ImVec2 size);
|
||||
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, bool *released, const ImVec2 size);
|
||||
|
||||
// fonts from ressources 'fonts/'
|
||||
typedef enum {
|
||||
FONT_DEFAULT =0,
|
||||
FONT_BOLD,
|
||||
FONT_ITALIC,
|
||||
FONT_MONO,
|
||||
FONT_LARGE
|
||||
} font_style;
|
||||
void SetFont (font_style type, const std::string &ttf_font_name, int pointsize, int oversample = 2);
|
||||
void PushFont (font_style type);
|
||||
|
||||
void WindowText(const char* window_name, ImVec2 window_pos, const char* text);
|
||||
bool WindowButton(const char* window_name, ImVec2 window_pos, const char* text);
|
||||
void WindowDragFloat(const char* window_name, ImVec2 window_pos, float* v, float v_speed, float v_min, float v_max, const char* format);
|
||||
|
||||
|
||||
// color of gui items
|
||||
typedef enum {
|
||||
ACCENT_BLUE =0,
|
||||
ACCENT_ORANGE,
|
||||
ACCENT_GREY
|
||||
} accent_color;
|
||||
void SetAccentColor (accent_color color);
|
||||
struct ImVec4 HighlightColor (bool active = true);
|
||||
|
||||
void ShowStats (bool* p_open, int* p_corner, bool* p_timer);
|
||||
|
||||
}
|
||||
|
||||
#endif // __IMGUI_TOOLKIT_H_
|
||||
628
ImGuiVisitor.cpp
@@ -1,628 +0,0 @@
|
||||
#include "ImGuiVisitor.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Scene.h"
|
||||
#include "Primitives.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "MediaSource.h"
|
||||
#include "SessionSource.h"
|
||||
#include "PatternSource.h"
|
||||
#include "DeviceSource.h"
|
||||
#include "NetworkSource.h"
|
||||
#include "Settings.h"
|
||||
#include "Mixer.h"
|
||||
#include "ActionManager.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
|
||||
ImGuiVisitor::ImGuiVisitor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(Node &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(Group &n)
|
||||
{
|
||||
// MODEL VIEW
|
||||
ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(1, 16)) {
|
||||
n.translation_.x = 0.f;
|
||||
n.translation_.y = 0.f;
|
||||
n.rotation_.z = 0.f;
|
||||
n.scale_.x = 1.f;
|
||||
n.scale_.y = 1.f;
|
||||
Action::manager().store("Geometry Reset");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Geometry");
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(6, 15)) {
|
||||
n.translation_.x = 0.f;
|
||||
n.translation_.y = 0.f;
|
||||
Action::manager().store("Position 0.0, 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
float translation[2] = { n.translation_.x, n.translation_.y};
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if ( ImGui::SliderFloat2("Position", translation, -5.0, 5.0) )
|
||||
{
|
||||
n.translation_.x = translation[0];
|
||||
n.translation_.y = translation[1];
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Position " << std::setprecision(3) << n.translation_.x << ", " << n.translation_.y;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
if (ImGuiToolkit::ButtonIcon(3, 15)) {
|
||||
n.scale_.x = 1.f;
|
||||
n.scale_.y = 1.f;
|
||||
Action::manager().store("Scale 1.0 x 1.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
float scale[2] = { n.scale_.x, n.scale_.y} ;
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if ( ImGui::SliderFloat2("Scale", scale, -MAX_SCALE, MAX_SCALE, "%.2f") )
|
||||
{
|
||||
n.scale_.x = CLAMP_SCALE(scale[0]);
|
||||
n.scale_.y = CLAMP_SCALE(scale[1]);
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Scale " << std::setprecision(3) << n.scale_.x << " x " << n.scale_.y;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(18, 9)){
|
||||
n.rotation_.z = 0.f;
|
||||
Action::manager().store("Angle 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderAngle("Angle", &(n.rotation_.z), -180.f, 180.f) ;
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
std::ostringstream oss;
|
||||
oss << "Angle " << std::setprecision(3) << n.rotation_.z * 180.f / M_PI;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
// spacing
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(Switch &n)
|
||||
{
|
||||
if (n.numChildren()>0)
|
||||
n.activeChild()->accept(*this);
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(Scene &n)
|
||||
{
|
||||
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
|
||||
if (ImGui::CollapsingHeader("Scene Property Tree"))
|
||||
{
|
||||
n.root()->accept(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(Primitive &n)
|
||||
{
|
||||
ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
ImGui::Text("Primitive %d");
|
||||
|
||||
n.shader()->accept(*this);
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(FrameBufferSurface &n)
|
||||
{
|
||||
ImGui::Text("Framebuffer");
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(MediaSurface &n)
|
||||
{
|
||||
ImGui::Text("%s", n.path().c_str());
|
||||
|
||||
if (n.mediaPlayer())
|
||||
n.mediaPlayer()->accept(*this);
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(MediaPlayer &n)
|
||||
{
|
||||
ImGui::Text("Media Player");
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(Shader &n)
|
||||
{
|
||||
ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
|
||||
// Base color
|
||||
// if (ImGuiToolkit::ButtonIcon(10, 2)) {
|
||||
// n.blending = Shader::BLEND_OPACITY;
|
||||
// n.color = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
||||
// }
|
||||
// ImGui::SameLine(0, 10);
|
||||
// ImGui::ColorEdit3("Color", glm::value_ptr(n.color), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel ) ;
|
||||
// ImGui::SameLine(0, 5);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
int mode = n.blending;
|
||||
if (ImGui::Combo("Blending", &mode, "Normal\0Screen\0Subtract\0Multiply\0Soft light"
|
||||
"\0Hard light\0Soft subtract\0Lighten only\0") ) {
|
||||
n.blending = Shader::BlendMode(mode);
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Blending ";
|
||||
switch(n.blending) {
|
||||
case Shader::BLEND_OPACITY:
|
||||
oss<<"Normal";
|
||||
break;
|
||||
case Shader::BLEND_SCREEN:
|
||||
oss<<"Screen";
|
||||
break;
|
||||
case Shader::BLEND_SUBTRACT:
|
||||
oss<<"Subtract";
|
||||
break;
|
||||
case Shader::BLEND_MULTIPLY:
|
||||
oss<<"Multiply";
|
||||
break;
|
||||
case Shader::BLEND_HARD_LIGHT:
|
||||
oss<<"Hard light";
|
||||
break;
|
||||
case Shader::BLEND_SOFT_LIGHT:
|
||||
oss<<"Soft light";
|
||||
break;
|
||||
case Shader::BLEND_SOFT_SUBTRACT:
|
||||
oss<<"Soft subtract";
|
||||
break;
|
||||
case Shader::BLEND_LIGHTEN_ONLY:
|
||||
oss<<"Lighten only";
|
||||
break;
|
||||
case Shader::BLEND_NONE:
|
||||
oss<<"None";
|
||||
break;
|
||||
}
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
//void ImGuiVisitor::visit(ImageShader &n)
|
||||
//{
|
||||
// ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
// // get index of the mask used in this ImageShader
|
||||
// int item_current = n.mask;
|
||||
//// if (ImGuiToolkit::ButtonIcon(10, 3)) n.mask = 0;
|
||||
//// ImGui::SameLine(0, 10);
|
||||
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
// // combo list of masks
|
||||
// if ( ImGui::Combo("Mask", &item_current, ImageShader::mask_names, IM_ARRAYSIZE(ImageShader::mask_names) ) )
|
||||
// {
|
||||
// if (item_current < (int) ImageShader::mask_presets.size())
|
||||
// n.mask = item_current;
|
||||
// else {
|
||||
// // TODO ask for custom mask
|
||||
// }
|
||||
// Action::manager().store("Mask "+ std::string(ImageShader::mask_names[n.mask]));
|
||||
// }
|
||||
// ImGui::PopID();
|
||||
//}
|
||||
|
||||
void ImGuiVisitor::visit(ImageProcessingShader &n)
|
||||
{
|
||||
ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(6, 2)) {
|
||||
ImageProcessingShader defaultvalues;
|
||||
n = defaultvalues;
|
||||
Action::manager().store("Reset Filters");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Filters");
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(6, 4)) {
|
||||
n.gamma = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
||||
Action::manager().store("Gamma & Color");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::ColorEdit3("Gamma Color", glm::value_ptr(n.gamma), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ;
|
||||
if (ImGui::IsItemDeactivatedAfterEdit())
|
||||
Action::manager().store("Gamma Color changed");
|
||||
|
||||
ImGui::SameLine(0, 5);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Gamma", &n.gamma.w, 0.5f, 10.f, "%.2f", 2.f);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Gamma " << std::setprecision(2) << n.gamma.w;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
// ImGui::SliderFloat4("Levels", glm::value_ptr(n.levels), 0.0, 1.0);
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(5, 16)) {
|
||||
n.brightness = 0.f;
|
||||
n.contrast = 0.f;
|
||||
Action::manager().store("B & C 0.0 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
float bc[2] = { n.brightness, n.contrast};
|
||||
if ( ImGui::SliderFloat2("B & C", bc, -1.0, 1.0) )
|
||||
{
|
||||
n.brightness = bc[0];
|
||||
n.contrast = bc[1];
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "B & C " << std::setprecision(2) << n.brightness << " " << n.contrast;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(9, 16)) {
|
||||
n.saturation = 0.f;
|
||||
Action::manager().store("Saturation 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Saturation", &n.saturation, -1.0, 1.0);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Saturation " << std::setprecision(2) << n.saturation;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(12, 4)) {
|
||||
n.hueshift = 0.f;
|
||||
Action::manager().store("Hue shift 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Hue shift", &n.hueshift, 0.0, 1.0);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Hue shift " << std::setprecision(2) << n.hueshift;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(18, 1)) {
|
||||
n.nbColors = 0;
|
||||
Action::manager().store("Posterize None");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderInt("Posterize", &n.nbColors, 0, 16, n.nbColors == 0 ? "None" : "%d colors");
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Posterize ";
|
||||
if (n.nbColors == 0) oss << "None"; else oss << n.nbColors;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(8, 1)) {
|
||||
n.threshold = 0.f;
|
||||
Action::manager().store("Threshold None");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Threshold", &n.threshold, 0.0, 1.0, n.threshold < 0.001 ? "None" : "%.2f");
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Threshold ";
|
||||
if (n.threshold < 0.001) oss << "None"; else oss << std::setprecision(2) << n.threshold;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(3, 1)) {
|
||||
n.lumakey = 0.f;
|
||||
Action::manager().store("Lumakey 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Lumakey", &n.lumakey, 0.0, 1.0);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Lumakey " << std::setprecision(2) << n.lumakey;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(13, 4)) {
|
||||
n.chromakey = glm::vec4(0.f, 0.8f, 0.f, 1.f);
|
||||
n.chromadelta = 0.f;
|
||||
Action::manager().store("Chromakey & Color Reset");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::ColorEdit3("Chroma color", glm::value_ptr(n.chromakey), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel ) ;
|
||||
if (ImGui::IsItemDeactivatedAfterEdit())
|
||||
Action::manager().store("Chroma color changed");
|
||||
ImGui::SameLine(0, 5);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Chromakey", &n.chromadelta, 0.0, 1.0, n.chromadelta < 0.001 ? "None" : "Tolerance %.2f");
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Chromakey ";
|
||||
if (n.chromadelta < 0.001) oss << "None"; else oss << std::setprecision(2) << n.chromadelta;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(6, 16)) {
|
||||
n.invert = 0;
|
||||
Action::manager().store("Invert None");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::Combo("Invert", &n.invert, "None\0Invert Color\0Invert Luminance\0"))
|
||||
Action::manager().store("Invert " + std::string(n.invert<1 ? "None": (n.invert>1 ? "Luminance" : "Color")));
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(1, 7)) {
|
||||
n.filterid = 0;
|
||||
Action::manager().store("Filter None");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::Combo("Filter", &n.filterid, ImageProcessingShader::filter_names, IM_ARRAYSIZE(ImageProcessingShader::filter_names) ) )
|
||||
Action::manager().store("Filter " + std::string(ImageProcessingShader::filter_names[n.filterid]));
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
|
||||
void ImGuiVisitor::visit (Source& s)
|
||||
{
|
||||
ImGui::PushID(std::to_string(s.id()).c_str());
|
||||
// blending
|
||||
s.blendingShader()->accept(*this);
|
||||
|
||||
// preview
|
||||
float preview_width = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;
|
||||
float preview_height = 4.5f * ImGui::GetFrameHeightWithSpacing();
|
||||
ImVec2 pos = ImGui::GetCursorPos(); // remember where we were...
|
||||
|
||||
float space = ImGui::GetStyle().ItemSpacing.y;
|
||||
float width = preview_width;
|
||||
float height = s.frame()->projectionArea().y * width / ( s.frame()->projectionArea().x * s.frame()->aspectRatio());
|
||||
if (height > preview_height - space) {
|
||||
height = preview_height - space;
|
||||
width = height * s.frame()->aspectRatio() * ( s.frame()->projectionArea().x / s.frame()->projectionArea().y);
|
||||
}
|
||||
// centered image
|
||||
ImGui::SetCursorPos( ImVec2(pos.x + 0.5f * (preview_width-width), pos.y + 0.5f * (preview_height-height-space)) );
|
||||
ImGui::Image((void*)(uintptr_t) s.frame()->texture(), ImVec2(width, height));
|
||||
|
||||
// inform on visibility status
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y ) );
|
||||
if (s.active()) {
|
||||
if (s.blendingShader()->color.a > 0.f)
|
||||
ImGuiToolkit::HelpMarker("Visible", ICON_FA_EYE);
|
||||
else
|
||||
ImGuiToolkit::HelpMarker("Not visible", ICON_FA_EYE_SLASH);
|
||||
}
|
||||
else
|
||||
ImGuiToolkit::HelpMarker("Inactive", ICON_FA_SNOWFLAKE);
|
||||
|
||||
// Inform on workspace
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + ImGui::GetFrameHeightWithSpacing()) );
|
||||
if (s.workspace() == Source::BACKGROUND)
|
||||
ImGuiToolkit::HelpIcon("in Background",10, 16);
|
||||
else if (s.workspace() == Source::FOREGROUND)
|
||||
ImGuiToolkit::HelpIcon("in Foreground",12, 16);
|
||||
else
|
||||
ImGuiToolkit::HelpIcon("in Workspace",11, 16);
|
||||
|
||||
// locking
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 2.f * ImGui::GetFrameHeightWithSpacing()) );
|
||||
const char *tooltip[2] = {"Unlocked", "Locked"};
|
||||
bool l = s.locked();
|
||||
if (ImGuiToolkit::IconToggle(15,6,17,6, &l, tooltip ) ) {
|
||||
s.setLocked(l);
|
||||
if (l) {
|
||||
Mixer::selection().clear();
|
||||
Action::manager().store(s.name() + std::string(": lock."));
|
||||
}
|
||||
else {
|
||||
Mixer::selection().set(&s);
|
||||
Action::manager().store(s.name() + std::string(": unlock."));
|
||||
}
|
||||
}
|
||||
|
||||
// toggle enable/disable image processing
|
||||
bool on = s.imageProcessingEnabled();
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 15, pos.y + 3.5f * ImGui::GetFrameHeightWithSpacing()) );
|
||||
if ( ImGuiToolkit::ButtonToggle(ICON_FA_MAGIC, &on) ){
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << ": " << ( on ? "Enable Filter" : "Disable Filter");
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
s.setImageProcessingEnabled(on);
|
||||
|
||||
ImGui::SetCursorPosY(pos.y + preview_height); // ...come back
|
||||
|
||||
// image processing pannel
|
||||
if (s.imageProcessingEnabled())
|
||||
s.processingShader()->accept(*this);
|
||||
|
||||
// geometry direct control
|
||||
// s.groupNode(View::GEOMETRY)->accept(*this);
|
||||
// s.groupNode((View::Mode) Settings::application.current_view)->accept(*this);
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (MediaSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
if ( s.mediaplayer()->isImage() )
|
||||
ImGui::Text("Image File");
|
||||
else
|
||||
ImGui::Text("Video File");
|
||||
|
||||
if ( ImGui::Button(IMGUI_TITLE_MEDIAPLAYER, ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
UserInterface::manager().showMediaPlayer( s.mediaplayer());
|
||||
ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (SessionFileSource& s)
|
||||
{
|
||||
if (s.session() == nullptr)
|
||||
return;
|
||||
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Session File");
|
||||
// ImGui::Text("%s", SystemToolkit::base_filename(s.path()).c_str());
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(3, 2)) s.session()->setFading(0.f);
|
||||
float f = s.session()->fading();
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::SliderFloat("Fading", &f, 0.0, 1.0, f < 0.001 ? "None" : "%.2f") )
|
||||
s.session()->setFading(f);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << ": Fading " << std::setprecision(2) << f;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
if ( ImGui::Button( ICON_FA_FILE_UPLOAD " Open Session", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
Mixer::manager().set( s.detach() );
|
||||
|
||||
ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
|
||||
|
||||
ImGui::Text("Contains %d sources.", s.session()->numSource());
|
||||
if ( ImGui::Button( ICON_FA_FILE_EXPORT " Import", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
Mixer::manager().import( &s );
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (SessionGroupSource& s)
|
||||
{
|
||||
if (s.session() == nullptr)
|
||||
return;
|
||||
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Flat Sesion group");
|
||||
ImGui::Text("Contains %d sources.", s.session()->numSource());
|
||||
|
||||
if ( ImGui::Button( ICON_FA_UPLOAD " Expand", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ){
|
||||
Mixer::manager().import( &s );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (RenderSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Rendering Output");
|
||||
if ( ImGui::Button(IMGUI_TITLE_PREVIEW, ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
Settings::application.widget.preview = true;
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (CloneSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Clone");
|
||||
if ( ImGui::Button(s.origin()->name().c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
Mixer::manager().setCurrentSource(s.origin());
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (PatternSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Pattern");
|
||||
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::BeginCombo("##Patterns", Pattern::pattern_types[s.pattern()->type()].c_str()) )
|
||||
{
|
||||
for (uint p = 0; p < Pattern::pattern_types.size(); ++p){
|
||||
if (ImGui::Selectable( Pattern::pattern_types[p].c_str() )) {
|
||||
s.setPattern(p, s.pattern()->resolution());
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << ": Pattern " << Pattern::pattern_types[p];
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (DeviceSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Device");
|
||||
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::BeginCombo("##Hardware", s.device().c_str()))
|
||||
{
|
||||
for (int d = 0; d < Device::manager().numDevices(); ++d){
|
||||
std::string namedev = Device::manager().name(d);
|
||||
if (ImGui::Selectable( namedev.c_str() )) {
|
||||
s.setDevice(namedev);
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << " Device " << namedev;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device().c_str()));
|
||||
if ( !confs.empty()) {
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
|
||||
ImGui::Text("%s %s %dx%d@%.1ffps", best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (NetworkSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Network stream");
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
|
||||
ImGui::Text("%s", s.connection().c_str());
|
||||
ImGui::PopStyleColor(1);
|
||||
NetworkStream *ns = s.networkStream();
|
||||
ImGui::Text(" - %s (%dx%d)\n - Server address %s", NetworkToolkit::protocol_name[ns->protocol()],
|
||||
ns->resolution().x, ns->resolution().y, ns->serverAddress().c_str());
|
||||
|
||||
if ( ImGui::Button( ICON_FA_REPLY " Reconnect", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
{
|
||||
// TODO : reload ?
|
||||
s.setConnection(s.connection());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
151
ImageShader.cpp
@@ -1,151 +0,0 @@
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Visitor.h"
|
||||
#include "Resource.h"
|
||||
#include "rsc/fonts/IconsFontAwesome5.h"
|
||||
|
||||
#include "ImageShader.h"
|
||||
|
||||
ShadingProgram imageShadingProgram("shaders/image.vs", "shaders/image.fs");
|
||||
ShadingProgram imageAlphaProgram ("shaders/image.vs", "shaders/imageblending.fs");
|
||||
|
||||
const char* MaskShader::mask_names[3] = { ICON_FA_EXPAND, ICON_FA_EDIT, ICON_FA_SHAPES };
|
||||
const char* MaskShader::mask_shapes[5] = { "Elipse", "Oblong", "Rectangle", "Horizontal", "Vertical" };
|
||||
std::vector< ShadingProgram* > MaskShader::mask_programs;
|
||||
|
||||
ImageShader::ImageShader(): Shader(), stipple(0.0)
|
||||
{
|
||||
// static program shader
|
||||
program_ = &imageShadingProgram;
|
||||
// reset instance
|
||||
reset();
|
||||
}
|
||||
|
||||
void ImageShader::use()
|
||||
{
|
||||
Shader::use();
|
||||
|
||||
// set stippling
|
||||
program_->setUniform("stipple", stipple);
|
||||
|
||||
// setup mask texture
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture (GL_TEXTURE_2D, mask_texture);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void ImageShader::reset()
|
||||
{
|
||||
Shader::reset();
|
||||
|
||||
// default mask
|
||||
mask_texture = Resource::getTextureWhite();
|
||||
// no stippling
|
||||
stipple = 0.f;
|
||||
}
|
||||
|
||||
void ImageShader::operator = (const ImageShader &S)
|
||||
{
|
||||
Shader::operator =(S);
|
||||
|
||||
mask_texture = S.mask_texture;
|
||||
stipple = S.stipple;
|
||||
}
|
||||
|
||||
|
||||
void ImageShader::accept(Visitor& v) {
|
||||
Shader::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
AlphaShader::AlphaShader(): ImageShader()
|
||||
{
|
||||
// to inverse alpha mode, use dedicated shading program
|
||||
program_ = &imageAlphaProgram;
|
||||
// reset instance
|
||||
reset();
|
||||
|
||||
blending = Shader::BLEND_NONE;
|
||||
}
|
||||
|
||||
|
||||
MaskShader::MaskShader(): Shader(), mode(0)
|
||||
{
|
||||
// first initialization
|
||||
if ( mask_programs.empty() ) {
|
||||
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/simple.fs"));
|
||||
mask_programs.push_back(new ShadingProgram("shaders/image.vs", "shaders/mask_draw.fs"));
|
||||
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_elipse.fs"));
|
||||
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_round.fs"));
|
||||
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_box.fs"));
|
||||
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_horizontal.fs"));
|
||||
mask_programs.push_back(new ShadingProgram("shaders/simple.vs", "shaders/mask_vertical.fs"));
|
||||
}
|
||||
// reset instance
|
||||
reset();
|
||||
// static program shader
|
||||
program_ = mask_programs[0];
|
||||
}
|
||||
|
||||
void MaskShader::use()
|
||||
{
|
||||
// select program to use
|
||||
mode = CLAMP(mode, 0, 2);
|
||||
shape = CLAMP(shape, 0, 4);
|
||||
program_ = mode < 2 ? mask_programs[mode] : mask_programs[shape+2] ;
|
||||
|
||||
// actual use of shader program
|
||||
Shader::use();
|
||||
|
||||
// shape parameters
|
||||
size = shape < HORIZONTAL ? glm::max(glm::abs(size), glm::vec2(0.2)) : size;
|
||||
program_->setUniform("size", size);
|
||||
program_->setUniform("blur", blur);
|
||||
|
||||
// brush parameters
|
||||
program_->setUniform("cursor", cursor);
|
||||
program_->setUniform("brush", brush);
|
||||
program_->setUniform("option", option);
|
||||
program_->setUniform("effect", effect);
|
||||
}
|
||||
|
||||
void MaskShader::reset()
|
||||
{
|
||||
Shader::reset();
|
||||
|
||||
// default mask
|
||||
mode = 0;
|
||||
|
||||
// default shape
|
||||
shape = 0;
|
||||
blur = 0.5f;
|
||||
size = glm::vec2(1.f, 1.f);
|
||||
|
||||
// default brush
|
||||
cursor = glm::vec4(-10.f, -10.f, 1.f, 1.f);
|
||||
brush = glm::vec3(0.5f, 0.1f, 0.f);
|
||||
option = 0;
|
||||
effect = 0;
|
||||
}
|
||||
|
||||
void MaskShader::operator = (const MaskShader &S)
|
||||
{
|
||||
Shader::operator =(S);
|
||||
|
||||
mode = S.mode;
|
||||
shape = S.shape;
|
||||
blur = S.blur;
|
||||
size = S.size;
|
||||
}
|
||||
|
||||
|
||||
void MaskShader::accept(Visitor& v) {
|
||||
Shader::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
390
LayerView.cpp
@@ -1,390 +0,0 @@
|
||||
// Opengl
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "Decorations.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "BoundingBoxVisitor.h"
|
||||
#include "ActionManager.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "LayerView.h"
|
||||
|
||||
LayerView::LayerView() : View(LAYER), aspect_ratio(1.f)
|
||||
{
|
||||
// read default settings
|
||||
if ( Settings::application.views[mode_].name.empty() ) {
|
||||
// no settings found: store application default
|
||||
Settings::application.views[mode_].name = "Layer";
|
||||
scene.root()->scale_ = glm::vec3(LAYER_DEFAULT_SCALE, LAYER_DEFAULT_SCALE, 1.0f);
|
||||
scene.root()->translation_ = glm::vec3(2.2f, 1.2f, 0.0f);
|
||||
saveSettings();
|
||||
}
|
||||
else
|
||||
restoreSettings();
|
||||
|
||||
// Geometry Scene background
|
||||
frame_ = new Group;
|
||||
Surface *rect = new Surface;
|
||||
rect->shader()->color.a = 0.3f;
|
||||
frame_->attach(rect);
|
||||
|
||||
Frame *border = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
|
||||
border->color = glm::vec4( COLOR_FRAME, 0.95f );
|
||||
frame_->attach(border);
|
||||
scene.bg()->attach(frame_);
|
||||
|
||||
persp_left_ = new Mesh("mesh/perspective_axis_left.ply");
|
||||
persp_left_->shader()->color = glm::vec4( COLOR_FRAME_LIGHT, 1.f );
|
||||
persp_left_->scale_.x = LAYER_PERSPECTIVE;
|
||||
persp_left_->translation_.z = -0.1f;
|
||||
persp_left_->translation_.x = -1.f;
|
||||
scene.bg()->attach(persp_left_);
|
||||
|
||||
persp_right_ = new Mesh("mesh/perspective_axis_right.ply");
|
||||
persp_right_->shader()->color = glm::vec4( COLOR_FRAME_LIGHT, 1.f );
|
||||
persp_right_->scale_.x = LAYER_PERSPECTIVE;
|
||||
persp_right_->translation_.z = -0.1f;
|
||||
persp_right_->translation_.x = 1.f;
|
||||
scene.bg()->attach(persp_right_);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void LayerView::draw()
|
||||
{
|
||||
View::draw();
|
||||
|
||||
// initialize the verification of the selection
|
||||
static bool candidate_flatten_group = false;
|
||||
|
||||
// display popup menu
|
||||
if (show_context_menu_ == MENU_SELECTION) {
|
||||
|
||||
// initialize the verification of the selection
|
||||
candidate_flatten_group = true;
|
||||
// start loop on selection
|
||||
SourceList::iterator it = Mixer::selection().begin();
|
||||
float depth_first = (*it)->depth();
|
||||
for (; it != Mixer::selection().end(); it++) {
|
||||
// test if selection is contiguous in layer (i.e. not interrupted)
|
||||
SourceList::iterator inter = Mixer::manager().session()->find(depth_first, (*it)->depth());
|
||||
if ( inter != Mixer::manager().session()->end() && !Mixer::selection().contains(*inter)){
|
||||
// NOT a group: there is a source in the session that
|
||||
// - is between two selected sources (in depth)
|
||||
// - is not part of the selection
|
||||
candidate_flatten_group = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::OpenPopup( "LayerSelectionContextMenu" );
|
||||
show_context_menu_ = MENU_NONE;
|
||||
}
|
||||
if (ImGui::BeginPopup("LayerSelectionContextMenu")) {
|
||||
|
||||
// colored context menu
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiToolkit::HighlightColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.36f, 0.36f, 0.36f, 0.44f));
|
||||
|
||||
// special action of Mixing view
|
||||
if (candidate_flatten_group){
|
||||
if (ImGui::Selectable( ICON_FA_DOWNLOAD " Flatten" )) {
|
||||
Mixer::manager().groupSelection();
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImGui::TextDisabled( ICON_FA_DOWNLOAD " Flatten" );
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
// manipulation of sources in Mixing view
|
||||
if (ImGui::Selectable( ICON_FA_ALIGN_CENTER " Distribute" )){
|
||||
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
|
||||
SourceList::iterator it = dsl.begin();
|
||||
float depth = (*it)->depth();
|
||||
float depth_inc = (dsl.back()->depth() - depth) / static_cast<float>(Mixer::selection().size()-1);
|
||||
for (it++; it != dsl.end(); it++) {
|
||||
depth += depth_inc;
|
||||
(*it)->setDepth(depth);
|
||||
}
|
||||
Action::manager().store(std::string("Selection Layer Distribute."));
|
||||
View::need_deep_update_++;
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_RULER_HORIZONTAL " Compress" )){
|
||||
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
|
||||
SourceList::iterator it = dsl.begin();
|
||||
float depth = (*it)->depth();
|
||||
for (it++; it != dsl.end(); it++) {
|
||||
depth += LAYER_STEP;
|
||||
(*it)->setDepth(depth);
|
||||
}
|
||||
Action::manager().store(std::string("Selection Layer Compress."));
|
||||
View::need_deep_update_++;
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_EXCHANGE_ALT " Reverse order" )){
|
||||
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
|
||||
SourceList::iterator it = dsl.begin();
|
||||
SourceList::reverse_iterator rit = dsl.rbegin();
|
||||
for (; it != dsl.end(); it++, rit++) {
|
||||
(*it)->setDepth((*rit)->depth());
|
||||
}
|
||||
Action::manager().store(std::string("Selection Layer Reverse order."));
|
||||
View::need_deep_update_++;
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LayerView::update(float dt)
|
||||
{
|
||||
View::update(dt);
|
||||
|
||||
// a more complete update is requested
|
||||
if (View::need_deep_update_ > 0) {
|
||||
|
||||
// update rendering of render frame
|
||||
FrameBuffer *output = Mixer::manager().session()->frame();
|
||||
if (output){
|
||||
// correct with aspect ratio
|
||||
aspect_ratio = output->aspectRatio();
|
||||
frame_->scale_.x = aspect_ratio;
|
||||
persp_left_->translation_.x = -aspect_ratio;
|
||||
persp_right_->translation_.x = aspect_ratio + 0.06;
|
||||
}
|
||||
}
|
||||
|
||||
if (Mixer::manager().view() == this )
|
||||
{
|
||||
// update the selection overlay
|
||||
updateSelectionOverlay();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool LayerView::canSelect(Source *s) {
|
||||
|
||||
return ( View::canSelect(s) && s->active() );
|
||||
}
|
||||
|
||||
void LayerView::resize ( int scale )
|
||||
{
|
||||
float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
|
||||
z *= z;
|
||||
z *= LAYER_MAX_SCALE - LAYER_MIN_SCALE;
|
||||
z += LAYER_MIN_SCALE;
|
||||
scene.root()->scale_.x = z;
|
||||
scene.root()->scale_.y = z;
|
||||
|
||||
// Clamp translation to acceptable area
|
||||
glm::vec3 border_left(scene.root()->scale_.x * -2.f, scene.root()->scale_.y * -1.f, 0.f);
|
||||
glm::vec3 border_right(scene.root()->scale_.x * 8.f, scene.root()->scale_.y * 8.f, 0.f);
|
||||
scene.root()->translation_ = glm::clamp(scene.root()->translation_, border_left, border_right);
|
||||
}
|
||||
|
||||
int LayerView::size ()
|
||||
{
|
||||
float z = (scene.root()->scale_.x - LAYER_MIN_SCALE) / (LAYER_MAX_SCALE - LAYER_MIN_SCALE);
|
||||
return (int) ( sqrt(z) * 100.f);
|
||||
}
|
||||
|
||||
|
||||
std::pair<Node *, glm::vec2> LayerView::pick(glm::vec2 P)
|
||||
{
|
||||
// get picking from generic View
|
||||
std::pair<Node *, glm::vec2> pick = View::pick(P);
|
||||
|
||||
// deal with internal interactive objects
|
||||
if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) {
|
||||
|
||||
openContextMenu(MENU_SELECTION);
|
||||
}
|
||||
else {
|
||||
// get if a source was picked
|
||||
Source *s = Mixer::manager().findSource(pick.first);
|
||||
if (s != nullptr) {
|
||||
// pick on the lock icon; unlock source
|
||||
if ( pick.first == s->lock_) {
|
||||
lock(s, false);
|
||||
pick = { s->locker_, pick.second };
|
||||
}
|
||||
// pick on the open lock icon; lock source and cancel pick
|
||||
else if ( pick.first == s->unlock_ ) {
|
||||
lock(s, true);
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick a locked source; cancel pick
|
||||
else if ( s->locked() ) {
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick the symbol: ask to show editor
|
||||
else if ( pick.first == s->symbol_ ) {
|
||||
UserInterface::manager().showSourceEditor(s);
|
||||
}
|
||||
}
|
||||
else
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
|
||||
return pick;
|
||||
}
|
||||
|
||||
|
||||
float LayerView::setDepth(Source *s, float d)
|
||||
{
|
||||
if (!s)
|
||||
return -1.f;
|
||||
|
||||
// move the layer node of the source
|
||||
Group *sourceNode = s->group(mode_);
|
||||
|
||||
float depth = d < 0.f ? sourceNode->translation_.z : d;
|
||||
|
||||
// negative or no depth given; find the front most depth
|
||||
if ( depth < 0.f ) {
|
||||
// default to place visible in front of background
|
||||
depth = LAYER_BACKGROUND + LAYER_STEP;
|
||||
|
||||
// find the front-most souce in the workspace (behind FOREGROUND)
|
||||
for (NodeSet::iterator node = scene.ws()->begin(); node != scene.ws()->end(); node++) {
|
||||
// place in front of previous sources
|
||||
depth = MAX(depth, (*node)->translation_.z + LAYER_STEP);
|
||||
|
||||
// in case node is already at max depth
|
||||
if ((*node)->translation_.z + DELTA_DEPTH > MAX_DEPTH )
|
||||
(*node)->translation_.z -= DELTA_DEPTH;
|
||||
}
|
||||
}
|
||||
|
||||
// change depth
|
||||
sourceNode->translation_.z = CLAMP( depth, MIN_DEPTH, MAX_DEPTH);
|
||||
|
||||
// request reordering of scene at next update
|
||||
View::need_deep_update_++;
|
||||
|
||||
// request update of source
|
||||
s->touch();
|
||||
|
||||
return sourceNode->translation_.z;
|
||||
}
|
||||
|
||||
View::Cursor LayerView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick)
|
||||
{
|
||||
if (!s)
|
||||
return Cursor();
|
||||
|
||||
// unproject
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(from, scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(to, scene.root()->transform_);
|
||||
|
||||
// compute delta translation
|
||||
glm::vec3 dest_translation = s->stored_status_->translation_ + gl_Position_to - gl_Position_from;
|
||||
|
||||
// discretized translation with ALT
|
||||
if (UserInterface::manager().altModifier())
|
||||
dest_translation.x = ROUND(dest_translation.x, 5.f);
|
||||
|
||||
// apply change
|
||||
float d = setDepth( s, MAX( -dest_translation.x, 0.f) );
|
||||
|
||||
std::ostringstream info;
|
||||
info << "Depth " << std::fixed << std::setprecision(2) << d << " ";
|
||||
// info << (s->locked() ? ICON_FA_LOCK : ICON_FA_LOCK_OPEN); // TODO static not locked
|
||||
|
||||
// store action in history
|
||||
current_action_ = s->name() + ": " + info.str();
|
||||
|
||||
return Cursor(Cursor_ResizeNESW, info.str() );
|
||||
}
|
||||
|
||||
void LayerView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static int accumulator = 0;
|
||||
accumulator++;
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(glm::vec2(movement.x-movement.y, 0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
bool first = true;
|
||||
glm::vec3 delta_translation(0.f);
|
||||
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
|
||||
// individual move with SHIFT
|
||||
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
|
||||
continue;
|
||||
|
||||
Group *sourceNode = (*it)->group(mode_);
|
||||
glm::vec3 dest_translation(0.f);
|
||||
|
||||
if (first) {
|
||||
// dest starts at current
|
||||
dest_translation = sourceNode->translation_;
|
||||
|
||||
// + ALT : discrete displacement
|
||||
if (UserInterface::manager().altModifier()) {
|
||||
if (accumulator > 10) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.21f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
accumulator = 0;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR;
|
||||
}
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
info << "Depth " << std::fixed << std::setprecision(2) << (*it)->depth() << " ";
|
||||
current_action_ = (*it)->name() + ": " + info.str();
|
||||
|
||||
// delta for others to follow
|
||||
delta_translation = dest_translation - sourceNode->translation_;
|
||||
}
|
||||
else {
|
||||
// dest = current + delta from first
|
||||
dest_translation = sourceNode->translation_ + delta_translation;
|
||||
}
|
||||
|
||||
// apply & request update
|
||||
setDepth( *it, MAX( -dest_translation.x, 0.f) );
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void LayerView::updateSelectionOverlay()
|
||||
{
|
||||
View::updateSelectionOverlay();
|
||||
|
||||
if (overlay_selection_->visible_) {
|
||||
// calculate bbox on selection
|
||||
GlmToolkit::AxisAlignedBoundingBox selection_box = BoundingBoxVisitor::AABB(Mixer::selection().getCopy(), this);
|
||||
overlay_selection_->scale_ = selection_box.scale();
|
||||
overlay_selection_->translation_ = selection_box.center();
|
||||
|
||||
// slightly extend the boundary of the selection
|
||||
overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.07f, 0.07f, 1.f) / overlay_selection_->scale_;
|
||||
}
|
||||
}
|
||||
241
Loopback.cpp
@@ -1,241 +0,0 @@
|
||||
#include <thread>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// gstreamer
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "Loopback.h"
|
||||
|
||||
bool Loopback::system_loopback_initialized = false;
|
||||
|
||||
#if defined(LINUX)
|
||||
|
||||
/**
|
||||
*
|
||||
* Linux video 4 linux loopback device
|
||||
*
|
||||
* 1) Linux system has to have the v4l2loopback package
|
||||
* See documentation at https://github.com/umlaeute/v4l2loopback
|
||||
*
|
||||
* $ sudo -A apt install v4l2loopback-dkms
|
||||
*
|
||||
* 2) User (sudo) has to install a v4l2loopback
|
||||
*
|
||||
* $ sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10
|
||||
*
|
||||
* 3) But to do that, the user has to enter sudo passwd
|
||||
*
|
||||
* The command line above should be preceeded by
|
||||
* export SUDO_ASKPASS="/tmp/mysudo.sh"
|
||||
*
|
||||
* where mysudo.sh contains the following:
|
||||
* #!/bin/bash
|
||||
* zenity --password --title=Authentication
|
||||
*
|
||||
* 4) Optionaly, we can set the dynamic properties of the stream
|
||||
*
|
||||
* $ sudo v4l2loopback-ctl set-caps "RGBA:640x480" /dev/video10
|
||||
* $ sudo v4l2loopback-ctl set-fps 30 /dev/video10
|
||||
*
|
||||
* 5) Finally, the gstreamer pipeline can write into v4l2sink
|
||||
*
|
||||
* gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
|
||||
*
|
||||
*
|
||||
* Useful command lines for debug
|
||||
* $ v4l2-ctl --all -d 10
|
||||
* $ gst-launch-1.0 v4l2src device=/dev/video10 ! videoconvert ! autovideosink
|
||||
* $ gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
std::string Loopback::system_loopback_name = "/dev/video10";
|
||||
std::string Loopback::system_loopback_pipeline = "appsrc name=src ! videoconvert ! videorate ! video/x-raw,framerate=30/1 ! v4l2sink sync=false name=sink";
|
||||
|
||||
bool Loopback::initializeSystemLoopback()
|
||||
{
|
||||
if (!Loopback::systemLoopbackInitialized()) {
|
||||
|
||||
// create script for asking sudo password
|
||||
std::string sudoscript = SystemToolkit::full_filename(SystemToolkit::settings_path(), "sudo.sh");
|
||||
FILE *file = fopen(sudoscript.c_str(), "w");
|
||||
if (file) {
|
||||
fprintf(file, "#!/bin/bash\n");
|
||||
fprintf(file, "zenity --password --title=Authentication\n");
|
||||
fclose(file);
|
||||
|
||||
// make script executable
|
||||
int fildes = 0;
|
||||
fildes = open(sudoscript.c_str(), O_RDWR);
|
||||
fchmod(fildes, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH);
|
||||
close(fildes);
|
||||
|
||||
// create command line for installing v4l2loopback
|
||||
std::string cmdline = "export SUDO_ASKPASS=\"" + sudoscript + "\"\n";
|
||||
cmdline += "sudo -A apt install v4l2loopback-dkms 2>&1\n";
|
||||
cmdline += "sudo -A modprobe -r v4l2loopback 2>&1\n";
|
||||
cmdline += "sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\" 2>&1\n";
|
||||
|
||||
// execute v4l2 command line
|
||||
std::string report;
|
||||
FILE *fp = popen(cmdline.c_str(), "r");
|
||||
if (fp != NULL) {
|
||||
|
||||
// get stdout content from command line
|
||||
char linestdout[PATH_MAX];
|
||||
while (fgets(linestdout, PATH_MAX, fp) != NULL)
|
||||
report += linestdout;
|
||||
|
||||
// error reported by pclose?
|
||||
if (pclose(fp) != 0 )
|
||||
Log::Warning("Failed to initialize system v4l2loopback\n%s", report.c_str());
|
||||
// okay, probaly all good...
|
||||
else
|
||||
system_loopback_initialized = true;
|
||||
}
|
||||
else
|
||||
Log::Warning("Failed to initialize system v4l2loopback\nCannot execute command line");
|
||||
|
||||
}
|
||||
else
|
||||
Log::Warning("Failed to initialize system v4l2loopback\nCannot create script", sudoscript.c_str());
|
||||
}
|
||||
|
||||
return system_loopback_initialized;
|
||||
}
|
||||
|
||||
bool Loopback::systemLoopbackInitialized()
|
||||
{
|
||||
// test if already initialized
|
||||
if (!system_loopback_initialized) {
|
||||
// check the existence of loopback device
|
||||
if ( SystemToolkit::file_exists(system_loopback_name) )
|
||||
system_loopback_initialized = true;
|
||||
}
|
||||
|
||||
return system_loopback_initialized;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::string Loopback::system_loopback_name = "undefined";
|
||||
std::string Loopback::system_loopback_pipeline = "";
|
||||
|
||||
|
||||
bool Loopback::initializeSystemLoopback()
|
||||
{
|
||||
system_loopback_initialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Loopback::systemLoopbackInitialized()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Loopback::Loopback() : FrameGrabber()
|
||||
{
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 60);
|
||||
|
||||
}
|
||||
|
||||
void Loopback::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
|
||||
if (!Loopback::systemLoopbackInitialized()){
|
||||
Log::Warning("Loopback system shall be initialized first.");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// create a gstreamer pipeline
|
||||
std::string description = Loopback::system_loopback_pipeline;
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("Loopback Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// setup device sink
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"device", Loopback::system_loopback_name.c_str(),
|
||||
NULL);
|
||||
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
// "do-timestamp", TRUE,
|
||||
NULL);
|
||||
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
callbacks.need_data = FrameGrabber::callback_need_data;
|
||||
callbacks.enough_data = FrameGrabber::callback_enough_data;
|
||||
callbacks.seek_data = NULL; // stream type is not seekable
|
||||
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("Loopback Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// start recording
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("Loopback Could not open %s", Loopback::system_loopback_name.c_str());
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// all good
|
||||
#if defined(LINUX)
|
||||
Log::Notify("Loopback started (v4l2loopback on %s)", Loopback::system_loopback_name.c_str());
|
||||
#else
|
||||
Log::Notify("Loopback started (%s)", Loopback::system_loopback_name.c_str());
|
||||
#endif
|
||||
// start
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void Loopback::terminate()
|
||||
{
|
||||
Log::Notify("Loopback to %s terminated.", Loopback::system_loopback_name.c_str());
|
||||
}
|
||||
31
Loopback.h
@@ -1,31 +0,0 @@
|
||||
#ifndef LOOPBACK_H
|
||||
#define LOOPBACK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
#include "FrameGrabber.h"
|
||||
|
||||
|
||||
class Loopback : public FrameGrabber
|
||||
{
|
||||
static std::string system_loopback_pipeline;
|
||||
static std::string system_loopback_name;
|
||||
static bool system_loopback_initialized;
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
|
||||
public:
|
||||
|
||||
Loopback();
|
||||
|
||||
static bool systemLoopbackInitialized();
|
||||
static bool initializeSystemLoopback();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // LOOPBACK_H
|
||||
1168
MediaPlayer.cpp
148
MediaSource.cpp
@@ -1,148 +0,0 @@
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "MediaSource.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Decorations.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
MediaSource::MediaSource() : Source(), path_("")
|
||||
{
|
||||
// create media player
|
||||
mediaplayer_ = new MediaPlayer;
|
||||
}
|
||||
|
||||
MediaSource::~MediaSource()
|
||||
{
|
||||
// delete media player
|
||||
delete mediaplayer_;
|
||||
}
|
||||
|
||||
void MediaSource::setPath(const std::string &p)
|
||||
{
|
||||
Log::Notify("Creating Source with media '%s'", p.c_str());
|
||||
|
||||
path_ = p;
|
||||
mediaplayer_->open(path_);
|
||||
mediaplayer_->play(true);
|
||||
}
|
||||
|
||||
std::string MediaSource::path() const
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
||||
MediaPlayer *MediaSource::mediaplayer() const
|
||||
{
|
||||
return mediaplayer_;
|
||||
}
|
||||
|
||||
glm::ivec2 MediaSource::icon() const
|
||||
{
|
||||
if (mediaplayer_->isImage())
|
||||
return glm::ivec2(4, 9);
|
||||
else
|
||||
return glm::ivec2(18, 13);
|
||||
}
|
||||
|
||||
bool MediaSource::failed() const
|
||||
{
|
||||
return mediaplayer_->failed();
|
||||
}
|
||||
|
||||
uint MediaSource::texture() const
|
||||
{
|
||||
return mediaplayer_->texture();
|
||||
}
|
||||
|
||||
void MediaSource::init()
|
||||
{
|
||||
if ( mediaplayer_->isOpen() ) {
|
||||
|
||||
// update video
|
||||
mediaplayer_->update();
|
||||
|
||||
// once the texture of media player is created
|
||||
if (mediaplayer_->texture() != Resource::getTextureBlack()) {
|
||||
|
||||
// get the texture index from media player, apply it to the media surface
|
||||
texturesurface_->setTextureIndex( mediaplayer_->texture() );
|
||||
|
||||
// create Frame buffer matching size of media player
|
||||
float height = float(mediaplayer_->width()) / mediaplayer_->aspectRatio();
|
||||
FrameBuffer *renderbuffer = new FrameBuffer(mediaplayer_->width(), (uint)height, true);
|
||||
|
||||
// icon in mixing view
|
||||
if (mediaplayer_->isImage())
|
||||
symbol_ = new Symbol(Symbol::IMAGE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
else
|
||||
symbol_ = new Symbol(Symbol::VIDEO, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// force update of activation mode
|
||||
active_ = true;
|
||||
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MediaSource::setActive (bool on)
|
||||
{
|
||||
bool was_active = active_;
|
||||
|
||||
Source::setActive(on);
|
||||
|
||||
// change status of media player (only if status changed)
|
||||
if ( active_ != was_active ) {
|
||||
mediaplayer_->enable(active_);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSource::update(float dt)
|
||||
{
|
||||
Source::update(dt);
|
||||
|
||||
// update video
|
||||
mediaplayer_->update();
|
||||
}
|
||||
|
||||
void MediaSource::render()
|
||||
{
|
||||
if (!initialized_)
|
||||
init();
|
||||
else {
|
||||
// blendingshader_->color.r = mediaplayer_->currentTimelineFading();
|
||||
// blendingshader_->color.g = mediaplayer_->currentTimelineFading();
|
||||
// blendingshader_->color.b = mediaplayer_->currentTimelineFading();
|
||||
|
||||
// render the media player into frame buffer
|
||||
renderbuffer_->begin();
|
||||
// texturesurface_->shader()->color.a = mediaplayer_->currentTimelineFading();
|
||||
texturesurface_->shader()->color.r = mediaplayer_->currentTimelineFading();
|
||||
texturesurface_->shader()->color.g = mediaplayer_->currentTimelineFading();
|
||||
texturesurface_->shader()->color.b = mediaplayer_->currentTimelineFading();
|
||||
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
|
||||
renderbuffer_->end();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
675
MixingView.cpp
@@ -1,675 +0,0 @@
|
||||
// Opengl
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "Decorations.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "BoundingBoxVisitor.h"
|
||||
#include "ActionManager.h"
|
||||
#include "MixingGroup.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "MixingView.h"
|
||||
|
||||
// internal utility
|
||||
float sin_quad_texture(float x, float y);
|
||||
uint textureMixingQuadratic();
|
||||
|
||||
|
||||
|
||||
MixingView::MixingView() : View(MIXING), limbo_scale_(MIXING_LIMBO_SCALE)
|
||||
{
|
||||
// read default settings
|
||||
if ( Settings::application.views[mode_].name.empty() ) {
|
||||
// no settings found: store application default
|
||||
Settings::application.views[mode_].name = "Mixing";
|
||||
scene.root()->scale_ = glm::vec3(MIXING_DEFAULT_SCALE, MIXING_DEFAULT_SCALE, 1.0f);
|
||||
scene.root()->translation_ = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
saveSettings();
|
||||
}
|
||||
else
|
||||
restoreSettings();
|
||||
|
||||
// Mixing scene background
|
||||
Mesh *tmp = new Mesh("mesh/disk.ply");
|
||||
tmp->scale_ = glm::vec3(limbo_scale_, limbo_scale_, 1.f);
|
||||
tmp->shader()->color = glm::vec4( COLOR_LIMBO_CIRCLE, 0.6f );
|
||||
scene.bg()->attach(tmp);
|
||||
|
||||
mixingCircle_ = new Mesh("mesh/disk.ply");
|
||||
mixingCircle_->setTexture(textureMixingQuadratic());
|
||||
mixingCircle_->shader()->color = glm::vec4( 1.f, 1.f, 1.f, 1.f );
|
||||
scene.bg()->attach(mixingCircle_);
|
||||
|
||||
circle_ = new Mesh("mesh/circle.ply");
|
||||
circle_->shader()->color = glm::vec4( COLOR_CIRCLE, 1.0f );
|
||||
scene.bg()->attach(circle_);
|
||||
|
||||
// Mixing scene foreground
|
||||
|
||||
// button frame
|
||||
tmp = new Mesh("mesh/disk.ply");
|
||||
tmp->scale_ = glm::vec3(0.033f, 0.033f, 1.f);
|
||||
tmp->translation_ = glm::vec3(0.f, 1.f, 0.f);
|
||||
tmp->shader()->color = glm::vec4( COLOR_CIRCLE, 0.9f );
|
||||
scene.fg()->attach(tmp);
|
||||
// interactive button
|
||||
button_white_ = new Disk();
|
||||
button_white_->scale_ = glm::vec3(0.026f, 0.026f, 1.f);
|
||||
button_white_->translation_ = glm::vec3(0.f, 1.f, 0.f);
|
||||
button_white_->color = glm::vec4( 0.85f, 0.85f, 0.85f, 1.0f );
|
||||
scene.fg()->attach(button_white_);
|
||||
// button frame
|
||||
tmp = new Mesh("mesh/disk.ply");
|
||||
tmp->scale_ = glm::vec3(0.033f, 0.033f, 1.f);
|
||||
tmp->translation_ = glm::vec3(0.f, -1.f, 0.f);
|
||||
tmp->shader()->color = glm::vec4( COLOR_CIRCLE, 0.9f );
|
||||
scene.fg()->attach(tmp);
|
||||
// interactive button
|
||||
button_black_ = new Disk();
|
||||
button_black_->scale_ = glm::vec3(0.026f, 0.026f, 1.f);
|
||||
button_black_->translation_ = glm::vec3(0.f, -1.f, 0.f);
|
||||
button_black_->color = glm::vec4( 0.1f, 0.1f, 0.1f, 1.0f );
|
||||
scene.fg()->attach(button_black_);
|
||||
// moving slider
|
||||
slider_root_ = new Group;
|
||||
scene.fg()->attach(slider_root_);
|
||||
// interactive slider
|
||||
slider_ = new Disk();
|
||||
slider_->scale_ = glm::vec3(0.08f, 0.08f, 1.f);
|
||||
slider_->translation_ = glm::vec3(0.0f, 1.0f, 0.f);
|
||||
slider_->color = glm::vec4( COLOR_CIRCLE, 0.9f );
|
||||
slider_root_->attach(slider_);
|
||||
// dark mask in front
|
||||
tmp = new Mesh("mesh/disk.ply");
|
||||
tmp->scale_ = glm::vec3(0.075f, 0.075f, 1.f);
|
||||
tmp->translation_ = glm::vec3(0.0f, 1.0f, 0.f);
|
||||
tmp->shader()->color = glm::vec4( COLOR_SLIDER_CIRCLE, 1.0f );
|
||||
slider_root_->attach(tmp);
|
||||
|
||||
|
||||
stashCircle_ = new Disk();
|
||||
stashCircle_->scale_ = glm::vec3(0.5f, 0.5f, 1.f);
|
||||
stashCircle_->translation_ = glm::vec3(2.f, -1.0f, 0.f);
|
||||
stashCircle_->color = glm::vec4( COLOR_STASH_CIRCLE, 0.6f );
|
||||
// scene.bg()->attach(stashCircle_);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MixingView::draw()
|
||||
{
|
||||
// temporarily force shaders to use opacity blending for rendering icons
|
||||
Shader::force_blending_opacity = true;
|
||||
// draw scene of this view
|
||||
View::draw();
|
||||
// restore state
|
||||
Shader::force_blending_opacity = false;
|
||||
|
||||
// display popup menu
|
||||
if (show_context_menu_ == MENU_SELECTION) {
|
||||
ImGui::OpenPopup( "MixingSelectionContextMenu" );
|
||||
show_context_menu_ = MENU_NONE;
|
||||
}
|
||||
if (ImGui::BeginPopup("MixingSelectionContextMenu")) {
|
||||
|
||||
// colored context menu
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGuiToolkit::HighlightColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.36f, 0.36f, 0.36f, 0.44f));
|
||||
|
||||
// special action of Mixing view: link or unlink
|
||||
SourceList selected = Mixer::selection().getCopy();
|
||||
if ( Mixer::manager().session()->canlink( selected )) {
|
||||
// the selected sources can be linked
|
||||
if (ImGui::Selectable( ICON_FA_LINK " Link" )){
|
||||
// assemble a MixingGroup
|
||||
Mixer::manager().session()->link(selected, scene.fg() );
|
||||
Action::manager().store(std::string("Sources linked."));
|
||||
// clear selection and select one of the sources of the group
|
||||
Source *cur = Mixer::selection().front();
|
||||
Mixer::manager().unsetCurrentSource();
|
||||
Mixer::selection().clear();
|
||||
Mixer::manager().setCurrentSource( cur );
|
||||
}
|
||||
}
|
||||
else {
|
||||
// the selected sources cannot be linked: offer to unlink!
|
||||
if (ImGui::Selectable( ICON_FA_UNLINK " Unlink" )){
|
||||
// dismantle MixingGroup(s)
|
||||
Mixer::manager().session()->unlink(selected);
|
||||
Action::manager().store(std::string("Sources unlinked."));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// manipulation of sources in Mixing view
|
||||
if (ImGui::Selectable( ICON_FA_CROSSHAIRS " Center" )){
|
||||
glm::vec2 center = glm::vec2(0.f, 0.f);
|
||||
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
// compute barycenter (1)
|
||||
center += glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
}
|
||||
// compute barycenter (2)
|
||||
center /= Mixer::selection().size();
|
||||
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
(*it)->group(View::MIXING)->translation_ -= glm::vec3(center, 0.f);
|
||||
(*it)->touch();
|
||||
}
|
||||
Action::manager().store(std::string("Selection Mixing Center."));
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_HAYKAL " Distribute" )){
|
||||
SourceList list;
|
||||
glm::vec2 center = glm::vec2(0.f, 0.f);
|
||||
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
list.push_back(*it);
|
||||
// compute barycenter (1)
|
||||
center += glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
}
|
||||
// compute barycenter (2)
|
||||
center /= list.size();
|
||||
// sort the vector of sources in clockwise order around the center pos_
|
||||
list = mixing_sorted( list, center);
|
||||
// average distance
|
||||
float d = 0.f;
|
||||
for (SourceList::iterator it = list.begin(); it != list.end(); it++) {
|
||||
d += glm::distance(glm::vec2((*it)->group(View::MIXING)->translation_), center);
|
||||
}
|
||||
d /= list.size();
|
||||
// distribute with equal angle
|
||||
float angle = 0.f;
|
||||
for (SourceList::iterator it = list.begin(); it != list.end(); it++) {
|
||||
glm::vec2 P = center + glm::rotate(glm::vec2(0.f, d), angle);
|
||||
(*it)->group(View::MIXING)->translation_.x = P.x;
|
||||
(*it)->group(View::MIXING)->translation_.y = P.y;
|
||||
(*it)->touch();
|
||||
angle -= glm::two_pi<float>() / float(list.size());
|
||||
}
|
||||
Action::manager().store(std::string("Selection Mixing Distribute."));
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_CLOUD_SUN " Expand & hide" )){
|
||||
SourceList::iterator it = Mixer::selection().begin();
|
||||
for (; it != Mixer::selection().end(); it++) {
|
||||
(*it)->setAlpha(0.f);
|
||||
}
|
||||
Action::manager().store(std::string("Selection Mixing Expand & hide."));
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_SUN " Compress & show" )){
|
||||
SourceList::iterator it = Mixer::selection().begin();
|
||||
for (; it != Mixer::selection().end(); it++) {
|
||||
(*it)->setAlpha(0.99f);
|
||||
}
|
||||
Action::manager().store(std::string("Selection Mixing Compress & show."));
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void MixingView::resize ( int scale )
|
||||
{
|
||||
float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
|
||||
z *= z;
|
||||
z *= MIXING_MAX_SCALE - MIXING_MIN_SCALE;
|
||||
z += MIXING_MIN_SCALE;
|
||||
scene.root()->scale_.x = z;
|
||||
scene.root()->scale_.y = z;
|
||||
|
||||
// Clamp translation to acceptable area
|
||||
glm::vec3 border(scene.root()->scale_.x * 1.f, scene.root()->scale_.y * 1.f, 0.f);
|
||||
scene.root()->translation_ = glm::clamp(scene.root()->translation_, -border, border);
|
||||
}
|
||||
|
||||
int MixingView::size ()
|
||||
{
|
||||
float z = (scene.root()->scale_.x - MIXING_MIN_SCALE) / (MIXING_MAX_SCALE - MIXING_MIN_SCALE);
|
||||
return (int) ( sqrt(z) * 100.f);
|
||||
}
|
||||
|
||||
void MixingView::centerSource(Source *s)
|
||||
{
|
||||
// setup view so that the center of the source ends at screen coordinates (650, 150)
|
||||
// -> this is just next to the navigation pannel
|
||||
glm::vec2 screenpoint = glm::vec2(500.f, 20.f) * Rendering::manager().mainWindow().dpiScale();
|
||||
glm::vec3 pos_to = Rendering::manager().unProject(screenpoint, scene.root()->transform_);
|
||||
glm::vec3 pos_from( - s->group(View::MIXING)->scale_.x, s->group(View::MIXING)->scale_.y, 0.f);
|
||||
pos_from += s->group(View::MIXING)->translation_;
|
||||
glm::vec4 pos_delta = glm::vec4(pos_to.x, pos_to.y, 0.f, 0.f) - glm::vec4(pos_from.x, pos_from.y, 0.f, 0.f);
|
||||
pos_delta = scene.root()->transform_ * pos_delta;
|
||||
scene.root()->translation_ += glm::vec3(pos_delta);
|
||||
|
||||
}
|
||||
|
||||
void MixingView::update(float dt)
|
||||
{
|
||||
View::update(dt);
|
||||
|
||||
// // always update the mixinggroups
|
||||
// for (auto g = groups_.begin(); g != groups_.end(); g++)
|
||||
// (*g)->update(dt);
|
||||
|
||||
// a more complete update is requested
|
||||
// for mixing, this means restore position of the fading slider
|
||||
if (View::need_deep_update_ > 0) {
|
||||
|
||||
//
|
||||
// Set slider to match the actual fading of the session
|
||||
//
|
||||
float f = Mixer::manager().session()->empty() ? 0.f : Mixer::manager().session()->fading();
|
||||
|
||||
// reverse calculate angle from fading & move slider
|
||||
slider_root_->rotation_.z = SIGN(slider_root_->rotation_.z) * asin(f) * 2.f;
|
||||
|
||||
// visual feedback on mixing circle
|
||||
f = 1.f - f;
|
||||
mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f);
|
||||
|
||||
}
|
||||
|
||||
// the current view is the mixing view
|
||||
if (Mixer::manager().view() == this )
|
||||
{
|
||||
//
|
||||
// Set session fading to match the slider angle (during animation)
|
||||
//
|
||||
|
||||
// calculate fading from angle
|
||||
float f = sin( ABS(slider_root_->rotation_.z) * 0.5f);
|
||||
|
||||
// apply fading
|
||||
if ( ABS_DIFF( f, Mixer::manager().session()->fading()) > EPSILON )
|
||||
{
|
||||
// apply fading to session
|
||||
Mixer::manager().session()->setFading(f);
|
||||
|
||||
// visual feedback on mixing circle
|
||||
f = 1.f - f;
|
||||
mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f);
|
||||
}
|
||||
|
||||
// update the selection overlay
|
||||
updateSelectionOverlay();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::pair<Node *, glm::vec2> MixingView::pick(glm::vec2 P)
|
||||
{
|
||||
// get picking from generic View
|
||||
std::pair<Node *, glm::vec2> pick = View::pick(P);
|
||||
|
||||
// deal with internal interactive objects
|
||||
if ( pick.first == button_white_ || pick.first == button_black_ ) {
|
||||
|
||||
RotateToCallback *anim = nullptr;
|
||||
if (pick.first == button_white_)
|
||||
anim = new RotateToCallback(0.f, 500.f);
|
||||
else
|
||||
anim = new RotateToCallback(SIGN(slider_root_->rotation_.z) * M_PI, 500.f);
|
||||
|
||||
// animate clic
|
||||
pick.first->update_callbacks_.push_back(new BounceScaleCallback(0.3f));
|
||||
|
||||
// reset & start animation
|
||||
slider_root_->update_callbacks_.clear();
|
||||
slider_root_->update_callbacks_.push_back(anim);
|
||||
|
||||
}
|
||||
else if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) {
|
||||
|
||||
openContextMenu(MENU_SELECTION);
|
||||
}
|
||||
else {
|
||||
// get if a source was picked
|
||||
Source *s = Mixer::manager().findSource(pick.first);
|
||||
if (s != nullptr) {
|
||||
// pick on the lock icon; unlock source
|
||||
if ( pick.first == s->lock_) {
|
||||
lock(s, false);
|
||||
pick = { s->locker_, pick.second };
|
||||
}
|
||||
// pick on the open lock icon; lock source and cancel pick
|
||||
else if ( pick.first == s->unlock_ ) {
|
||||
lock(s, true);
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick a locked source ; cancel pick
|
||||
else if ( s->locked() ) {
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick the symbol: ask to show editor
|
||||
else if ( pick.first == s->symbol_ ) {
|
||||
UserInterface::manager().showSourceEditor(s);
|
||||
}
|
||||
// pick on the mixing group rotation icon
|
||||
else if ( pick.first == s->rotation_mixingroup_ ) {
|
||||
s->mixinggroup_->setAction( MixingGroup::ACTION_ROTATE_ALL );
|
||||
}
|
||||
// pick source of a mixing group
|
||||
else if ( s->mixinggroup_ != nullptr ) {
|
||||
if (UserInterface::manager().ctrlModifier()) {
|
||||
SourceList linked = s->mixinggroup_->getCopy();
|
||||
linked.remove(s);
|
||||
if (Mixer::selection().empty())
|
||||
Mixer::selection().add(linked);
|
||||
}
|
||||
else if (UserInterface::manager().shiftModifier())
|
||||
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ONE );
|
||||
else
|
||||
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ALL );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pick;
|
||||
}
|
||||
|
||||
|
||||
|
||||
View::Cursor MixingView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick)
|
||||
{
|
||||
View::Cursor ret = Cursor();
|
||||
ret.type = Cursor_ResizeAll;
|
||||
|
||||
// unproject
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(from, scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(to, scene.root()->transform_);
|
||||
|
||||
// No source is given
|
||||
if (!s) {
|
||||
|
||||
// if interaction with slider
|
||||
if (pick.first == slider_) {
|
||||
|
||||
// apply rotation to match angle with mouse cursor
|
||||
float angle = glm::orientedAngle( glm::normalize(glm::vec2(0.f, 1.0)), glm::normalize(glm::vec2(gl_Position_to)));
|
||||
|
||||
// snap on 0 and PI angles
|
||||
if ( ABS_DIFF(angle, 0.f) < 0.05)
|
||||
angle = 0.f;
|
||||
else if ( ABS_DIFF(angle, M_PI) < 0.05)
|
||||
angle = M_PI;
|
||||
|
||||
// animate slider (rotation angle on its parent)
|
||||
slider_root_->rotation_.z = angle;
|
||||
|
||||
// cursor feedback
|
||||
slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f );
|
||||
std::ostringstream info;
|
||||
info << "Global opacity " << 100 - int(Mixer::manager().session()->fading() * 100.0) << " %";
|
||||
return Cursor(Cursor_Hand, info.str() );
|
||||
}
|
||||
|
||||
// nothing to do
|
||||
return Cursor();
|
||||
}
|
||||
//
|
||||
// Interaction with source
|
||||
//
|
||||
// compute delta translation
|
||||
s->group(mode_)->translation_ = s->stored_status_->translation_ + gl_Position_to - gl_Position_from;
|
||||
|
||||
// manage mixing group
|
||||
if (s->mixinggroup_ != nullptr ) {
|
||||
// inform mixing groups to follow the current source
|
||||
if (Source::isCurrent(s) && s->mixinggroup_->action() > MixingGroup::ACTION_UPDATE) {
|
||||
s->mixinggroup_->follow(s);
|
||||
if (s->mixinggroup_->action() == MixingGroup::ACTION_ROTATE_ALL)
|
||||
ret.type = Cursor_Hand;
|
||||
}
|
||||
else
|
||||
s->mixingGroup()->setAction(MixingGroup::ACTION_NONE);
|
||||
}
|
||||
|
||||
// request update
|
||||
s->touch();
|
||||
|
||||
// // trying to enter stash
|
||||
// if ( glm::distance( glm::vec2(s->group(mode_)->translation_), glm::vec2(stashCircle_->translation_)) < stashCircle_->scale_.x) {
|
||||
|
||||
// // refuse to put an active source in stash
|
||||
// if (s->active())
|
||||
// s->group(mode_)->translation_ = s->stored_status_->translation_;
|
||||
// else {
|
||||
// Mixer::manager().conceal(s);
|
||||
// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE) - glm::vec3(0.1f, 0.1f, 0.f);
|
||||
// }
|
||||
// }
|
||||
// else if ( Mixer::manager().concealed(s) ) {
|
||||
// Mixer::manager().uncover(s);
|
||||
// s->group(mode_)->scale_ = glm::vec3(MIXING_ICON_SCALE);
|
||||
// }
|
||||
|
||||
|
||||
std::ostringstream info;
|
||||
if (s->active()) {
|
||||
info << "Alpha " << std::fixed << std::setprecision(3) << s->blendingShader()->color.a << " ";
|
||||
info << ( (s->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
|
||||
}
|
||||
else
|
||||
info << "Inactive " << ICON_FA_SNOWFLAKE;
|
||||
|
||||
// store action in history
|
||||
current_action_ = s->name() + ": " + info.str();
|
||||
|
||||
// update cursor
|
||||
ret.info = info.str();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MixingView::terminate()
|
||||
{
|
||||
View::terminate();
|
||||
|
||||
// terminate all mixing group actions
|
||||
for (auto g = Mixer::manager().session()->beginMixingGroup();
|
||||
g != Mixer::manager().session()->endMixingGroup(); g++)
|
||||
(*g)->setAction( MixingGroup::ACTION_FINISH );
|
||||
|
||||
}
|
||||
|
||||
View::Cursor MixingView::over (glm::vec2 pos)
|
||||
{
|
||||
View::Cursor ret = Cursor();
|
||||
std::pair<Node *, glm::vec2> pick = View::pick(pos);
|
||||
|
||||
// deal with internal interactive objects
|
||||
if ( pick.first == slider_ ) {
|
||||
slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f );
|
||||
ret.type = Cursor_Hand;
|
||||
}
|
||||
else
|
||||
slider_->color = glm::vec4( COLOR_CIRCLE, 0.9f );
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MixingView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static int accumulator = 0;
|
||||
accumulator++;
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
bool first = true;
|
||||
glm::vec3 delta_translation(0.f);
|
||||
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
|
||||
// individual move with SHIFT
|
||||
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
|
||||
continue;
|
||||
|
||||
Group *sourceNode = (*it)->group(mode_);
|
||||
glm::vec3 dest_translation(0.f);
|
||||
|
||||
if (first) {
|
||||
// dest starts at current
|
||||
dest_translation = sourceNode->translation_;
|
||||
|
||||
// + ALT : discrete displacement
|
||||
if (UserInterface::manager().altModifier()) {
|
||||
if (accumulator > 10) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.11f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
dest_translation.y = ROUND(dest_translation.y, 10.f);
|
||||
accumulator = 0;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR;
|
||||
}
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
if ((*it)->active()) {
|
||||
info << "Alpha " << std::fixed << std::setprecision(3) << (*it)->blendingShader()->color.a << " ";
|
||||
info << ( ((*it)->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
|
||||
}
|
||||
else
|
||||
info << "Inactive " << ICON_FA_SNOWFLAKE;
|
||||
current_action_ = (*it)->name() + ": " + info.str();
|
||||
|
||||
// delta for others to follow
|
||||
delta_translation = dest_translation - sourceNode->translation_;
|
||||
}
|
||||
else {
|
||||
// dest = current + delta from first
|
||||
dest_translation = sourceNode->translation_ + delta_translation;
|
||||
}
|
||||
|
||||
// apply & request update
|
||||
sourceNode->translation_ = dest_translation;
|
||||
(*it)->touch();
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MixingView::setAlpha(Source *s)
|
||||
{
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
// move the layer node of the source
|
||||
Group *sourceNode = s->group(mode_);
|
||||
glm::vec2 mix_pos = glm::vec2(DEFAULT_MIXING_TRANSLATION);
|
||||
|
||||
for(NodeSet::iterator it = scene.ws()->begin(); it != scene.ws()->end(); it++) {
|
||||
// avoid superposing icons: distribute equally
|
||||
if ( glm::distance(glm::vec2((*it)->translation_), mix_pos) < DELTA_ALPHA) {
|
||||
mix_pos += glm::vec2(-0.03f, 0.03f);
|
||||
}
|
||||
}
|
||||
|
||||
sourceNode->translation_.x = mix_pos.x;
|
||||
sourceNode->translation_.y = mix_pos.y;
|
||||
|
||||
// request update
|
||||
s->touch();
|
||||
}
|
||||
|
||||
|
||||
void MixingView::updateSelectionOverlay()
|
||||
{
|
||||
View::updateSelectionOverlay();
|
||||
|
||||
if (overlay_selection_->visible_) {
|
||||
// calculate bbox on selection
|
||||
GlmToolkit::AxisAlignedBoundingBox selection_box = BoundingBoxVisitor::AABB(Mixer::selection().getCopy(), this);
|
||||
overlay_selection_->scale_ = selection_box.scale();
|
||||
overlay_selection_->translation_ = selection_box.center();
|
||||
|
||||
// slightly extend the boundary of the selection
|
||||
overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.01f, 0.01f, 1.f) / overlay_selection_->scale_;
|
||||
}
|
||||
}
|
||||
|
||||
#define CIRCLE_PIXELS 64
|
||||
#define CIRCLE_PIXEL_RADIUS 1024.0
|
||||
//#define CIRCLE_PIXELS 256
|
||||
//#define CIRCLE_PIXEL_RADIUS 16384.0
|
||||
//#define CIRCLE_PIXELS 1024
|
||||
//#define CIRCLE_PIXEL_RADIUS 262144.0
|
||||
|
||||
float sin_quad_texture(float x, float y) {
|
||||
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ) / CIRCLE_PIXEL_RADIUS, 0.f, 1.f ) );
|
||||
float D = sqrt( ( x * x )/ CIRCLE_PIXEL_RADIUS + ( y * y )/ CIRCLE_PIXEL_RADIUS );
|
||||
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
|
||||
}
|
||||
|
||||
uint textureMixingQuadratic()
|
||||
{
|
||||
static GLuint texid = 0;
|
||||
if (texid == 0) {
|
||||
// generate the texture with alpha exactly as computed for sources
|
||||
GLubyte matrix[CIRCLE_PIXELS*CIRCLE_PIXELS * 4];
|
||||
GLubyte color[4] = {0,0,0,0};
|
||||
GLfloat luminance = 1.f;
|
||||
GLfloat alpha = 0.f;
|
||||
GLfloat distance = 0.f;
|
||||
int l = -CIRCLE_PIXELS / 2 + 1, c = 0;
|
||||
|
||||
for (int i = 0; i < CIRCLE_PIXELS / 2; ++i) {
|
||||
c = -CIRCLE_PIXELS / 2 + 1;
|
||||
for (int j=0; j < CIRCLE_PIXELS / 2; ++j) {
|
||||
// distance to the center
|
||||
distance = sin_quad_texture( (float) c , (float) l );
|
||||
// distance = 1.f - (GLfloat) ((c * c) + (l * l)) / CIRCLE_PIXEL_RADIUS; // quadratic
|
||||
// distance = 1.f - (GLfloat) sqrt( (GLfloat) ((c * c) + (l * l))) / (GLfloat) sqrt(CIRCLE_PIXEL_RADIUS); // linear
|
||||
|
||||
// transparency
|
||||
alpha = 255.f * CLAMP( distance , 0.f, 1.f);
|
||||
color[3] = static_cast<GLubyte>(alpha);
|
||||
|
||||
// luminance adjustment
|
||||
luminance = 255.f * CLAMP( 0.2f + 0.75f * distance, 0.f, 1.f);
|
||||
color[0] = color[1] = color[2] = static_cast<GLubyte>(luminance);
|
||||
|
||||
// 1st quadrant
|
||||
memmove(&matrix[ j * 4 + i * CIRCLE_PIXELS * 4 ], color, 4 * sizeof(GLubyte));
|
||||
// 4nd quadrant
|
||||
memmove(&matrix[ (CIRCLE_PIXELS -j -1)* 4 + i * CIRCLE_PIXELS * 4 ], color, 4 * sizeof(GLubyte));
|
||||
// 3rd quadrant
|
||||
memmove(&matrix[ j * 4 + (CIRCLE_PIXELS -i -1) * CIRCLE_PIXELS * 4 ], color, 4 * sizeof(GLubyte));
|
||||
// 4th quadrant
|
||||
memmove(&matrix[ (CIRCLE_PIXELS -j -1) * 4 + (CIRCLE_PIXELS -i -1) * CIRCLE_PIXELS * 4 ], color, 4 * sizeof(GLubyte));
|
||||
|
||||
++c;
|
||||
}
|
||||
++l;
|
||||
}
|
||||
// setup texture
|
||||
glGenTextures(1, &texid);
|
||||
glBindTexture(GL_TEXTURE_2D, texid);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, CIRCLE_PIXELS, CIRCLE_PIXELS);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, CIRCLE_PIXELS, CIRCLE_PIXELS, GL_BGRA, GL_UNSIGNED_BYTE, matrix);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
|
||||
}
|
||||
return texid;
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
#include <sstream>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "PatternSource.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Decorations.h"
|
||||
#include "Stream.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
#define MAX_PATTERN 24
|
||||
|
||||
// smpte (0) – SMPTE 100%% color bars
|
||||
// snow (1) – Random (television snow)
|
||||
// black (2) – 100%% Black
|
||||
// white (3) – 100%% White
|
||||
// red (4) – Red
|
||||
// green (5) – Green
|
||||
// blue (6) – Blue
|
||||
// checkers-1 (7) – Checkers 1px
|
||||
// checkers-2 (8) – Checkers 2px
|
||||
// checkers-4 (9) – Checkers 4px
|
||||
// checkers-8 (10) – Checkers 8px
|
||||
// circular (11) – Circular
|
||||
// blink (12) – Blink
|
||||
// smpte75 (13) – SMPTE 75%% color bars
|
||||
// zone-plate (14) – Zone plate
|
||||
// gamut (15) – Gamut checkers
|
||||
// chroma-zone-plate (16) – Chroma zone plate
|
||||
// solid-color (17) – Solid color
|
||||
// ball (18) – Moving ball
|
||||
// smpte100 (19) – SMPTE 100%% color bars
|
||||
// bar (20) – Bar
|
||||
// pinwheel (21) – Pinwheel
|
||||
// spokes (22) – Spokes
|
||||
// gradient (23) – Gradient
|
||||
// colors (24) – Colors
|
||||
const char* pattern_internal_[MAX_PATTERN] = { "videotestsrc pattern=black",
|
||||
"videotestsrc pattern=white",
|
||||
"videotestsrc pattern=gradient",
|
||||
"videotestsrc pattern=checkers-1 ! video/x-raw,format=GRAY8 ! videoconvert",
|
||||
"videotestsrc pattern=checkers-8 ! video/x-raw,format=GRAY8 ! videoconvert",
|
||||
"videotestsrc pattern=circular",
|
||||
"frei0r-src-lissajous0r ratiox=0.001 ratioy=0.999 ! videoconvert",
|
||||
"videotestsrc pattern=pinwheel",
|
||||
"videotestsrc pattern=spokes",
|
||||
"videotestsrc pattern=red",
|
||||
"videotestsrc pattern=green",
|
||||
"videotestsrc pattern=blue",
|
||||
"videotestsrc pattern=smpte100",
|
||||
"videotestsrc pattern=colors",
|
||||
"videotestsrc pattern=smpte",
|
||||
"videotestsrc pattern=snow",
|
||||
"videotestsrc pattern=blink",
|
||||
"videotestsrc pattern=zone-plate",
|
||||
"videotestsrc pattern=chroma-zone-plate",
|
||||
"videotestsrc pattern=bar horizontal-speed=5",
|
||||
"videotestsrc pattern=ball",
|
||||
"frei0r-src-ising0r",
|
||||
"videotestsrc pattern=black ! timeoverlay halignment=center valignment=center font-desc=\"Sans, 72\" ",
|
||||
"videotestsrc pattern=black ! clockoverlay halignment=center valignment=center font-desc=\"Sans, 72\" "
|
||||
};
|
||||
|
||||
std::vector<std::string> Pattern::pattern_types = { "Black",
|
||||
"White",
|
||||
"Gradient",
|
||||
"Checkers 1x1 px",
|
||||
"Checkers 8x8 px",
|
||||
"Circles",
|
||||
"Lissajous",
|
||||
"Pinwheel",
|
||||
"Spokes",
|
||||
"Red",
|
||||
"Green",
|
||||
"Blue",
|
||||
"Color bars",
|
||||
"RGB grid",
|
||||
"SMPTE test pattern",
|
||||
"Television snow",
|
||||
"Blink",
|
||||
"Fresnel zone plate",
|
||||
"Chroma zone plate",
|
||||
"Bar moving",
|
||||
"Ball bouncing"
|
||||
#if GST_VERSION_MINOR > 17
|
||||
,
|
||||
"Blob",
|
||||
"Timer",
|
||||
"Clock"
|
||||
#endif
|
||||
};
|
||||
|
||||
Pattern::Pattern() : Stream(), type_(MAX_PATTERN) // invalid pattern
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
glm::ivec2 Pattern::resolution()
|
||||
{
|
||||
return glm::ivec2( width_, height_);
|
||||
}
|
||||
|
||||
|
||||
void Pattern::open( uint pattern, glm::ivec2 res )
|
||||
{
|
||||
type_ = MIN(pattern, MAX_PATTERN-1);
|
||||
std::string gstreamer_pattern = pattern_internal_[type_];
|
||||
|
||||
// there is always a special case...
|
||||
switch(type_)
|
||||
{
|
||||
case 18: // zone plates
|
||||
case 17:
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << " kx2=" << (int)(res.x * 10.f / res.y) << " ky2=10 kt=4";
|
||||
gstreamer_pattern += oss.str(); // Zone plate
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// all patterns before 'SMPTE test pattern' are single frames (not animated)
|
||||
single_frame_ = type_ < 14;
|
||||
|
||||
// (private) open stream
|
||||
Stream::open(gstreamer_pattern, res.x, res.y);
|
||||
}
|
||||
|
||||
PatternSource::PatternSource() : StreamSource()
|
||||
{
|
||||
// create stream
|
||||
stream_ = (Stream *) new Pattern;
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::PATTERN, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
void PatternSource::setPattern(uint type, glm::ivec2 resolution)
|
||||
{
|
||||
Log::Notify("Creating Source with pattern '%s'", Pattern::pattern_types[type].c_str());
|
||||
|
||||
pattern()->open( (uint) type, resolution );
|
||||
stream_->play(true);
|
||||
}
|
||||
|
||||
void PatternSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
Pattern *PatternSource::pattern() const
|
||||
{
|
||||
return dynamic_cast<Pattern *>(stream_);
|
||||
}
|
||||
|
||||
|
||||
107
README.md
@@ -1,5 +1,8 @@
|
||||
# vimix
|
||||
Live Video Mixing
|
||||
__Live Video Mixing__
|
||||
|
||||
<img src=docs/vimix_screenshot.png width="800">
|
||||
|
||||
|
||||
vimix performs graphical mixing and blending of several movie clips and
|
||||
computer generated graphics, with image processing effects in real-time.
|
||||
@@ -7,59 +10,135 @@ computer generated graphics, with image processing effects in real-time.
|
||||
Its intuitive and hands-on user interface gives direct control on image opacity and
|
||||
shape for producing live graphics during concerts and VJ-ing sessions.
|
||||
|
||||
The ouput image is typically projected full-screen on an external
|
||||
monitor or a projector, but can be recorded live (no audio).
|
||||
The output image is typically projected full-screen on an external
|
||||
monitor or a projector, and can be streamed live (SRT, Shmdata) or recorded (without audio).
|
||||
|
||||
vimix is the successor for GLMixer - https://sourceforge.net/projects/glmixer/
|
||||
|
||||
# Install
|
||||
# License
|
||||
|
||||
GPL-3.0-or-later
|
||||
See [LICENSE](https://github.com/brunoherbelin/vimix/blob/master/LICENSE)
|
||||
|
||||
# Install vimix
|
||||
|
||||
Check the [Quick Installation Guide](https://github.com/brunoherbelin/vimix/wiki/Quick-Installation-Guide)
|
||||
|
||||
### Linux
|
||||
|
||||
Download and install a release package from https://snapcraft.io/vimix
|
||||
Download and install a released [flatpak package](https://flathub.org/apps/details/io.github.brunoherbelin.Vimix)
|
||||
|
||||
flatpak install --user vimix
|
||||
|
||||
NB: Build your flatpak package to get the latest beta version; instructions are [here](https://github.com/brunoherbelin/vimix/tree/master/flatpak).
|
||||
|
||||
|
||||
[](https://snapcraft.io/vimix)
|
||||
|
||||
Download and install a released [snap package](https://snapcraft.io/vimix)
|
||||
|
||||
snap install vimix
|
||||
|
||||
Install the stable debian package (slower release frequency)
|
||||
|
||||
sudo apt install vimix
|
||||
|
||||
### Mac OSX
|
||||
|
||||
Download and open a release package from https://github.com/brunoherbelin/vimix/releases
|
||||
|
||||
NB: You'll need to accept the exception in OSX security preference.
|
||||
|
||||
# Build vimix
|
||||
|
||||
## Clone
|
||||
|
||||
git clone --recursive https://github.com/brunoherbelin/vimix.git
|
||||
|
||||
This will create the directory 'vimix', download the latest version of vimix code,
|
||||
and (recursively) clone all the internal git Dependencies.
|
||||
and (recursively) clone all the internal git dependencies.
|
||||
|
||||
## Compile
|
||||
|
||||
First time after git clone:
|
||||
|
||||
mkdir vimix-build
|
||||
cd vimix-build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ../vimix
|
||||
cmake --build .
|
||||
|
||||
This will create the directory 'vimix-build', configure the program for build, and compile vimix.
|
||||
If successful, the compilation will have produced the executable `vimix` in the `src` directory.
|
||||
You can run vimix with `./src/vimix` :
|
||||
|
||||
...
|
||||
[100%] Built target vimix
|
||||
./src/vimix
|
||||
|
||||
## Update clone and re-compile
|
||||
|
||||
### Dependencies
|
||||
Run these commands from the `vimix-build` directory if you did 'Clone' and 'Compile' previously and only want to get the latest update and rebuild.
|
||||
|
||||
git -C ../vimix/ pull
|
||||
cmake --build .
|
||||
|
||||
This will pull the latest commit from git and recompile.
|
||||
|
||||
## Try the Beta branch
|
||||
|
||||
Run this commands from the `vimix-build` directory before runing 'Update clone and re-compile above'
|
||||
|
||||
git -C ../vimix/ checkout beta
|
||||
|
||||
It should say;
|
||||
|
||||
branch 'beta' set up to track 'origin/beta'.
|
||||
Switched to a new branch 'beta'
|
||||
|
||||
## Dependencies
|
||||
|
||||
**Compiling tools:**
|
||||
|
||||
- gcc
|
||||
- make
|
||||
- cmake
|
||||
- git
|
||||
|
||||
**Libraries:**
|
||||
|
||||
- gstreamer
|
||||
- gst-plugins : base, good, bad & ugly
|
||||
- libpng
|
||||
- gst-plugins (libav, base, good, bad & ugly)
|
||||
- libglfw3
|
||||
- libicu (icu-i18n icu-uc icu-io)
|
||||
|
||||
#### Install Dependencies
|
||||
Optionnal:
|
||||
|
||||
**Ubuntu**
|
||||
- glm
|
||||
- stb
|
||||
- TinyXML2
|
||||
- AbletonLink
|
||||
- Shmdata
|
||||
|
||||
apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libicu-dev
|
||||
### Install Dependencies
|
||||
|
||||
**OSX with Brew**
|
||||
#### Ubuntu
|
||||
|
||||
apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav libicu-dev libgtk-3-dev
|
||||
|
||||
Optionnal:
|
||||
|
||||
apt-get install libglm-dev libstb-dev libtinyxml2-dev ableton-link-dev
|
||||
|
||||
> Follow these instructions to [install Shmdata](https://github.com/nicobou/shmdata/blob/develop/doc/install-from-sources.md).
|
||||
|
||||
git clone https://gitlab.com/sat-metalab/shmdata.git
|
||||
mkdir shmdata-build
|
||||
cd shmdata-build
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=Release -DWITH_PYTHON=0 -DWITH_SDCRASH=0 -DWITH_SDFLOW=0 ../shmdata-build
|
||||
cmake --build . --target package
|
||||
sudo dpkg -i ./libshmdata_1.3*_amd64.deb
|
||||
|
||||
#### OSX with Brew
|
||||
|
||||
brew install cmake libpng glfw gstreamer icu4c
|
||||
|
||||
brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly icu4c
|
||||
|
||||
311
Recorder.cpp
@@ -1,311 +0,0 @@
|
||||
#include <thread>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// standalone image loader
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
// gstreamer
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "Settings.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "defines.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "Recorder.h"
|
||||
|
||||
PNGRecorder::PNGRecorder() : FrameGrabber()
|
||||
{
|
||||
}
|
||||
|
||||
void PNGRecorder::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
|
||||
// create a gstreamer pipeline
|
||||
std::string description = "appsrc name=src ! videoconvert ! pngenc ! filesink name=sink";
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("PNG Capture Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// verify location path (path is always terminated by the OS dependent separator)
|
||||
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
|
||||
if (path.empty())
|
||||
path = SystemToolkit::home_path();
|
||||
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".png";
|
||||
|
||||
// setup file sink
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"location", filename_.c_str(),
|
||||
"sync", FALSE,
|
||||
NULL);
|
||||
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
// "do-timestamp", TRUE,
|
||||
NULL);
|
||||
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
callbacks.need_data = FrameGrabber::callback_need_data;
|
||||
callbacks.enough_data = FrameGrabber::callback_enough_data;
|
||||
callbacks.seek_data = NULL; // stream type is not seekable
|
||||
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("PNG Capture Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// start pipeline
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("PNG Capture Could not record %s", filename_.c_str());
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// all good
|
||||
Log::Info("PNG Capture started.");
|
||||
|
||||
// start recording !!
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void PNGRecorder::terminate()
|
||||
{
|
||||
Log::Notify("PNG Capture %s is ready.", filename_.c_str());
|
||||
}
|
||||
|
||||
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps, float dt)
|
||||
{
|
||||
FrameGrabber::addFrame(buffer, caps, dt);
|
||||
|
||||
// PNG Recorder specific :
|
||||
// stop after one frame
|
||||
if (timestamp_ > 0) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* VideoRecorder::profile_name[VideoRecorder::DEFAULT] = {
|
||||
"H264 (Realtime)",
|
||||
"H264 (High 4:4:4)",
|
||||
"H265 (Realtime)",
|
||||
"H265 (HQ Animation)",
|
||||
"ProRes (Standard)",
|
||||
"ProRes (HQ 4444)",
|
||||
"WebM VP8 (2MB/s)",
|
||||
"Multiple JPEG"
|
||||
};
|
||||
const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// Control x264 encoder quality :
|
||||
// pass
|
||||
// quant (4) – Constant Quantizer
|
||||
// qual (5) – Constant Quality
|
||||
// quantizer
|
||||
// The total range is from 0 to 51, where 0 is lossless, 18 can be considered ‘visually lossless’,
|
||||
// and 51 is terrible quality. A sane range is 18-26, and the default is 23.
|
||||
// speed-preset
|
||||
// veryfast (3)
|
||||
// faster (4)
|
||||
// fast (5)
|
||||
#ifndef APPLE
|
||||
// "video/x-raw, format=I420 ! x264enc pass=4 quantizer=26 speed-preset=3 threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
|
||||
"video/x-raw, format=I420 ! x264enc tune=\"zerolatency\" threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
|
||||
#else
|
||||
"video/x-raw, format=I420 ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 ! h264parse ! ",
|
||||
#endif
|
||||
"video/x-raw, format=Y444_10LE ! x264enc pass=4 quantizer=16 speed-preset=4 threads=4 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
|
||||
// Control x265 encoder quality :
|
||||
// NB: apparently x265 only accepts I420 format :(
|
||||
// speed-preset
|
||||
// veryfast (3)
|
||||
// faster (4)
|
||||
// fast (5)
|
||||
// Tune
|
||||
// psnr (1)
|
||||
// ssim (2) DEFAULT
|
||||
// grain (3)
|
||||
// zerolatency (4) Encoder latency is removed
|
||||
// fastdecode (5)
|
||||
// animation (6) optimize the encode quality for animation content without impacting the encode speed
|
||||
// crf Quality-controlled variable bitrate [0 51]
|
||||
// default 28
|
||||
// 24 for x265 should be visually transparent; anything lower will probably just waste file size
|
||||
"video/x-raw, format=I420 ! x265enc tune=4 speed-preset=3 ! video/x-h265, profile=(string)main ! h265parse ! ",
|
||||
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=4 option-string=\"crf=24\" ! video/x-h265, profile=(string)main ! h265parse ! ",
|
||||
// Apple ProRes encoding parameters
|
||||
// pass
|
||||
// cbr (0) – Constant Bitrate Encoding
|
||||
// quant (2) – Constant Quantizer
|
||||
// pass1 (512) – VBR Encoding - Pass 1
|
||||
// profile
|
||||
// 0 ‘proxy’
|
||||
// 1 ‘lt’
|
||||
// 2 ‘standard’
|
||||
// 3 ‘hq’
|
||||
// 4 ‘4444’
|
||||
"avenc_prores_ks pass=2 profile=2 quantizer=26 ! ",
|
||||
"video/x-raw, format=Y444_10LE ! avenc_prores_ks pass=2 profile=4 quantizer=12 ! ",
|
||||
// VP8 WebM encoding
|
||||
"vp8enc end-usage=vbr cpu-used=8 max-quantizer=35 deadline=100000 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=100 ! ",
|
||||
"jpegenc ! "
|
||||
};
|
||||
|
||||
// Too slow
|
||||
//// WebM VP9 encoding parameters
|
||||
//// https://www.webmproject.org/docs/encoder-parameters/
|
||||
//// https://developers.google.com/media/vp9/settings/vod/
|
||||
//"vp9enc end-usage=vbr end-usage=vbr cpu-used=3 max-quantizer=35 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=1000 ! "
|
||||
|
||||
// FAILED
|
||||
// x265 encoder quality
|
||||
// string description = "appsrc name=src ! videoconvert ! "
|
||||
// "x265enc tune=4 speed-preset=2 option-string='crf=28' ! h265parse ! "
|
||||
// "qtmux ! filesink name=sink";
|
||||
|
||||
|
||||
VideoRecorder::VideoRecorder() : FrameGrabber()
|
||||
{
|
||||
}
|
||||
|
||||
void VideoRecorder::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
|
||||
// create a gstreamer pipeline
|
||||
std::string description = "appsrc name=src ! videoconvert ! ";
|
||||
if (Settings::application.record.profile < 0 || Settings::application.record.profile >= DEFAULT)
|
||||
Settings::application.record.profile = H264_STANDARD;
|
||||
description += profile_description[Settings::application.record.profile];
|
||||
|
||||
// verify location path (path is always terminated by the OS dependent separator)
|
||||
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
|
||||
if (path.empty())
|
||||
path = SystemToolkit::home_path();
|
||||
|
||||
// setup filename & muxer
|
||||
if( Settings::application.record.profile == JPEG_MULTI) {
|
||||
std::string folder = path + "vimix_" + SystemToolkit::date_time_string();
|
||||
filename_ = SystemToolkit::full_filename(folder, "%05d.jpg");
|
||||
if (SystemToolkit::create_directory(folder))
|
||||
description += "multifilesink name=sink";
|
||||
}
|
||||
else if( Settings::application.record.profile == VP8) {
|
||||
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".webm";
|
||||
description += "webmmux ! filesink name=sink";
|
||||
}
|
||||
else {
|
||||
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".mov";
|
||||
description += "qtmux ! filesink name=sink";
|
||||
}
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("VideoRecorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// setup file sink
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"location", filename_.c_str(),
|
||||
"sync", FALSE,
|
||||
NULL);
|
||||
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
// "do-timestamp", TRUE,
|
||||
NULL);
|
||||
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
callbacks.need_data = FrameGrabber::callback_need_data;
|
||||
callbacks.enough_data = FrameGrabber::callback_enough_data;
|
||||
callbacks.seek_data = NULL; // stream type is not seekable
|
||||
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("VideoRecorder Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// start recording
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("VideoRecorder Could not record %s", filename_.c_str());
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// all good
|
||||
Log::Info("Video Recording started (%s)", profile_name[Settings::application.record.profile]);
|
||||
|
||||
// start recording !!
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void VideoRecorder::terminate()
|
||||
{
|
||||
Log::Notify("Video Recording %s is ready.", filename_.c_str());
|
||||
}
|
||||
|
||||
std::string VideoRecorder::info() const
|
||||
{
|
||||
if (active_)
|
||||
return GstToolkit::time_to_string(timestamp_);
|
||||
else
|
||||
return "Saving file...";
|
||||
}
|
||||
57
Recorder.h
@@ -1,57 +0,0 @@
|
||||
#ifndef RECORDER_H
|
||||
#define RECORDER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
#include "FrameGrabber.h"
|
||||
|
||||
class PNGRecorder : public FrameGrabber
|
||||
{
|
||||
std::string filename_;
|
||||
|
||||
public:
|
||||
|
||||
PNGRecorder();
|
||||
|
||||
protected:
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
void addFrame(GstBuffer *buffer, GstCaps *caps, float dt) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class VideoRecorder : public FrameGrabber
|
||||
{
|
||||
std::string filename_;
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
|
||||
public:
|
||||
|
||||
typedef enum {
|
||||
H264_STANDARD = 0,
|
||||
H264_HQ,
|
||||
H265_REALTIME,
|
||||
H265_ANIMATION,
|
||||
PRORES_STANDARD,
|
||||
PRORES_HQ,
|
||||
VP8,
|
||||
JPEG_MULTI,
|
||||
DEFAULT
|
||||
} Profile;
|
||||
static const char* profile_name[DEFAULT];
|
||||
static const std::vector<std::string> profile_description;
|
||||
|
||||
VideoRecorder();
|
||||
std::string info() const override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // RECORDER_H
|
||||
@@ -1,84 +0,0 @@
|
||||
// Opengl
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "Decorations.h"
|
||||
|
||||
#include "RenderView.h"
|
||||
|
||||
|
||||
RenderView::RenderView() : View(RENDERING), frame_buffer_(nullptr), fading_overlay_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
RenderView::~RenderView()
|
||||
{
|
||||
if (frame_buffer_)
|
||||
delete frame_buffer_;
|
||||
if (fading_overlay_)
|
||||
delete fading_overlay_;
|
||||
}
|
||||
|
||||
bool RenderView::canSelect(Source *s) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void RenderView::setFading(float f)
|
||||
{
|
||||
if (fading_overlay_ == nullptr)
|
||||
fading_overlay_ = new Surface;
|
||||
|
||||
fading_overlay_->shader()->color.a = CLAMP( f < EPSILON ? 0.f : f, 0.f, 1.f);
|
||||
}
|
||||
|
||||
float RenderView::fading() const
|
||||
{
|
||||
if (fading_overlay_)
|
||||
return fading_overlay_->shader()->color.a;
|
||||
else
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
void RenderView::setResolution(glm::vec3 resolution, bool useAlpha)
|
||||
{
|
||||
// use default resolution if invalid resolution is given (default behavior)
|
||||
if (resolution.x < 2.f || resolution.y < 2.f)
|
||||
resolution = FrameBuffer::getResolutionFromParameters(Settings::application.render.ratio, Settings::application.render.res);
|
||||
|
||||
// do we need to change resolution ?
|
||||
if (frame_buffer_ && frame_buffer_->resolution() != resolution) {
|
||||
|
||||
// new frame buffer
|
||||
delete frame_buffer_;
|
||||
frame_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
if (!frame_buffer_)
|
||||
// output frame is an RBG Multisamples FrameBuffer
|
||||
frame_buffer_ = new FrameBuffer(resolution, useAlpha, true);
|
||||
|
||||
// reset fading
|
||||
setFading();
|
||||
}
|
||||
|
||||
void RenderView::draw()
|
||||
{
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -SCENE_DEPTH, 1.f);
|
||||
|
||||
if (frame_buffer_) {
|
||||
// draw in frame buffer
|
||||
glm::mat4 P = glm::scale( projection, glm::vec3(1.f / frame_buffer_->aspectRatio(), 1.f, 1.f));
|
||||
|
||||
// render the scene normally (pre-multiplied alpha in RGB)
|
||||
frame_buffer_->begin();
|
||||
scene.root()->draw(glm::identity<glm::mat4>(), P);
|
||||
fading_overlay_->draw(glm::identity<glm::mat4>(), projection);
|
||||
frame_buffer_->end();
|
||||
}
|
||||
}
|
||||
27
RenderView.h
@@ -1,27 +0,0 @@
|
||||
#ifndef RENDERVIEW_H
|
||||
#define RENDERVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class RenderView : public View
|
||||
{
|
||||
FrameBuffer *frame_buffer_;
|
||||
Surface *fading_overlay_;
|
||||
|
||||
public:
|
||||
RenderView ();
|
||||
~RenderView ();
|
||||
|
||||
void draw () override;
|
||||
bool canSelect(Source *) override;
|
||||
|
||||
void setResolution (glm::vec3 resolution = glm::vec3(0.f), bool useAlpha = false);
|
||||
glm::vec3 resolution() const { return frame_buffer_->resolution(); }
|
||||
|
||||
void setFading(float f = 0.f);
|
||||
float fading() const;
|
||||
|
||||
inline FrameBuffer *frame () const { return frame_buffer_; }
|
||||
};
|
||||
|
||||
#endif // RENDERVIEW_H
|
||||
@@ -1,897 +0,0 @@
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h> // Initialized with gladLoadGLLoader()
|
||||
|
||||
// Include glfw3.h after our OpenGL definitions
|
||||
#define GLFW_INCLUDE_GLEXT
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#ifdef APPLE
|
||||
#include "osx/CocoaToolkit.h"
|
||||
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||
#define GLFW_EXPOSE_NATIVE_NSGL
|
||||
#else
|
||||
#define GLFW_EXPOSE_NATIVE_X11
|
||||
#define GLFW_EXPOSE_NATIVE_GLX
|
||||
#endif
|
||||
#include <GLFW/glfw3native.h>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
|
||||
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
|
||||
|
||||
// Include GStreamer
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/gstglcontext.h>
|
||||
|
||||
#ifdef GLFW_EXPOSE_NATIVE_COCOA
|
||||
//#include <gst/gl/cocoa/gstgldisplay_cocoa.h>
|
||||
#endif
|
||||
#ifdef GLFW_EXPOSE_NATIVE_GLX
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
#endif
|
||||
|
||||
// standalone image loader
|
||||
#include <stb_image.h>
|
||||
|
||||
// vmix
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Resource.h"
|
||||
#include "Settings.h"
|
||||
#include "Primitives.h"
|
||||
#include "Mixer.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "RenderingManager.h"
|
||||
|
||||
// local statics
|
||||
static GstGLContext *global_gl_context = NULL;
|
||||
static GstGLDisplay *global_display = NULL;
|
||||
|
||||
static std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
|
||||
|
||||
static void glfw_error_callback(int error, const char* description)
|
||||
{
|
||||
Log::Error("Glfw Error %d: %s", error, description);
|
||||
}
|
||||
|
||||
static void WindowRefreshCallback( GLFWwindow * )
|
||||
{
|
||||
Rendering::manager().draw();
|
||||
}
|
||||
|
||||
static void WindowResizeCallback( GLFWwindow *w, int width, int height)
|
||||
{
|
||||
int id = GLFW_window_[w]->index();
|
||||
if (!Settings::application.windows[id].fullscreen) {
|
||||
Settings::application.windows[id].w = width;
|
||||
Settings::application.windows[id].h = height;
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowMoveCallback( GLFWwindow *w, int x, int y)
|
||||
{
|
||||
int id = GLFW_window_[w]->index();
|
||||
if (!Settings::application.windows[id].fullscreen) {
|
||||
Settings::application.windows[id].x = x;
|
||||
Settings::application.windows[id].y = y;
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowEscapeFullscreen( GLFWwindow *w, int key, int, int action, int)
|
||||
{
|
||||
if (action == GLFW_PRESS && key == GLFW_KEY_ESCAPE)
|
||||
{
|
||||
// escape fullscreen
|
||||
GLFW_window_[w]->exitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowToggleFullscreen( GLFWwindow *w, int button, int action, int)
|
||||
{
|
||||
static double seconds = 0.f;
|
||||
|
||||
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
|
||||
{
|
||||
// detect double clic
|
||||
if ( glfwGetTime() - seconds < 0.2f ) {
|
||||
// toggle fullscreen
|
||||
GLFW_window_[w]->toggleFullscreen();
|
||||
}
|
||||
// for next clic detection
|
||||
seconds = glfwGetTime();
|
||||
}
|
||||
}
|
||||
|
||||
Rendering::Rendering()
|
||||
{
|
||||
// main_window_ = nullptr;
|
||||
request_screenshot_ = false;
|
||||
}
|
||||
|
||||
bool Rendering::init()
|
||||
{
|
||||
// Setup window
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit()){
|
||||
Log::Error("Failed to Initialize GLFW.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decide GL+GLSL versions GL 3.3 + GLSL 150
|
||||
glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
#if __APPLE__
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
#endif
|
||||
|
||||
//
|
||||
// OpenGL Multisampling main window
|
||||
//
|
||||
glfwWindowHint(GLFW_SAMPLES, Settings::application.render.multisampling);
|
||||
main_.init(0);
|
||||
// set application icon
|
||||
main_.setIcon("images/vimix_256x256.png");
|
||||
// additional window callbacks for main window
|
||||
glfwSetWindowRefreshCallback( main_.window(), WindowRefreshCallback );
|
||||
glfwSetDropCallback( main_.window(), Rendering::FileDropped);
|
||||
|
||||
//
|
||||
// Gstreamer setup
|
||||
//
|
||||
std::string plugins_path = SystemToolkit::cwd_path() + "gstreamer-1.0";
|
||||
std::string plugins_scanner = SystemToolkit::cwd_path() + "gst-plugin-scanner" ;
|
||||
if ( SystemToolkit::file_exists(plugins_path)) {
|
||||
Log::Info("Found Gstreamer plugins in %s", plugins_path.c_str());
|
||||
g_setenv ("GST_PLUGIN_SYSTEM_PATH", plugins_path.c_str(), TRUE);
|
||||
g_setenv ("GST_PLUGIN_SCANNER", plugins_scanner.c_str(), TRUE);
|
||||
}
|
||||
g_setenv ("GST_GL_API", "opengl3", TRUE);
|
||||
gst_init (NULL, NULL);
|
||||
|
||||
// increase selection rank for GPU decoding plugins
|
||||
std::list<std::string> gpuplugins = GstToolkit::enable_gpu_decoding_plugins(Settings::application.render.gpu_decoding);
|
||||
if (Settings::application.render.gpu_decoding) {
|
||||
if (gpuplugins.size() > 0) {
|
||||
Log::Info("Video decoding favoring the following GPU decoding plugin(s):");
|
||||
for(auto it = gpuplugins.begin(); it != gpuplugins.end(); it++)
|
||||
Log::Info(" - %s", (*it).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//#if GST_GL_HAVE_PLATFORM_WGL
|
||||
// global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
|
||||
// GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
|
||||
//#elif GST_GL_HAVE_PLATFORM_CGL
|
||||
//// global_display = GST_GL_DISPLAY ( glfwGetCocoaMonitor(main_.window()) );
|
||||
// global_display = GST_GL_DISPLAY (gst_gl_display_cocoa_new ());
|
||||
|
||||
// global_gl_context = gst_gl_context_new_wrapped (global_display,
|
||||
// (guintptr) 0,
|
||||
// GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
|
||||
//#elif GST_GL_HAVE_PLATFORM_GLX
|
||||
// global_display = (GstGLDisplay*) gst_gl_display_x11_new_with_display( glfwGetX11Display() );
|
||||
// global_gl_context = gst_gl_context_new_wrapped (global_display,
|
||||
// (guintptr) glfwGetGLXContext(main_.window()),
|
||||
// GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
|
||||
//#endif
|
||||
|
||||
|
||||
//
|
||||
// output window
|
||||
//
|
||||
glfwWindowHint(GLFW_SAMPLES, 0); // no need for multisampling in displaying output
|
||||
output_.init(1, main_.window());
|
||||
output_.setIcon("images/vimix_256x256.png");
|
||||
// special callbacks for user input in output window
|
||||
glfwSetKeyCallback( output_.window(), WindowEscapeFullscreen);
|
||||
glfwSetMouseButtonCallback( output_.window(), WindowToggleFullscreen);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Rendering::show()
|
||||
{
|
||||
// show output window
|
||||
output_.show();
|
||||
|
||||
// show main window
|
||||
main_.show();
|
||||
|
||||
// show menu on first show
|
||||
UserInterface::manager().showPannel(NAV_MENU);
|
||||
}
|
||||
|
||||
bool Rendering::isActive()
|
||||
{
|
||||
return !glfwWindowShouldClose(main_.window());
|
||||
}
|
||||
|
||||
|
||||
void Rendering::pushFrontDrawCallback(RenderingCallback function)
|
||||
{
|
||||
draw_callbacks_.push_front(function);
|
||||
}
|
||||
|
||||
void Rendering::pushBackDrawCallback(RenderingCallback function)
|
||||
{
|
||||
draw_callbacks_.push_back(function);
|
||||
}
|
||||
|
||||
void Rendering::draw()
|
||||
{
|
||||
// guint64 _time = gst_util_get_timestamp ();
|
||||
|
||||
// operate on main window context
|
||||
main_.makeCurrent();
|
||||
|
||||
// User Interface step 1
|
||||
UserInterface::manager().NewFrame();
|
||||
|
||||
// Custom draw
|
||||
std::list<Rendering::RenderingCallback>::iterator iter;
|
||||
for (iter=draw_callbacks_.begin(); iter != draw_callbacks_.end(); iter++)
|
||||
{
|
||||
(*iter)();
|
||||
}
|
||||
|
||||
// User Interface step 2
|
||||
UserInterface::manager().Render();
|
||||
|
||||
// perform screenshot if requested
|
||||
if (request_screenshot_) {
|
||||
// glfwMakeContextCurrent(main_window_);
|
||||
screenshot_.captureGL(0, 0, main_.width(), main_.height());
|
||||
request_screenshot_ = false;
|
||||
}
|
||||
|
||||
// draw output window (and swap buffer output)
|
||||
output_.draw( Mixer::manager().session()->frame() );
|
||||
|
||||
// swap GL buffers
|
||||
glfwSwapBuffers(main_.window());
|
||||
glfwSwapBuffers(output_.window());
|
||||
|
||||
// Poll and handle events (inputs, window resize, etc.)
|
||||
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
|
||||
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
|
||||
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
|
||||
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
||||
glfwPollEvents();
|
||||
|
||||
// change windows
|
||||
main_.toggleFullscreen_();
|
||||
output_.toggleFullscreen_();
|
||||
|
||||
// no g_main_loop_run(loop) : update global GMainContext
|
||||
g_main_context_iteration(NULL, FALSE);
|
||||
|
||||
// software framerate limiter 60FPS if not v-sync
|
||||
if ( Settings::application.render.vsync < 1 ) {
|
||||
static GTimer *timer = g_timer_new ();
|
||||
double elapsed = g_timer_elapsed (timer, NULL) * 1000000.0;
|
||||
if (elapsed < 16000)
|
||||
g_usleep( 16000 - (gulong)elapsed );
|
||||
g_timer_start(timer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Rendering::terminate()
|
||||
{
|
||||
// close window
|
||||
glfwDestroyWindow(output_.window());
|
||||
glfwDestroyWindow(main_.window());
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
|
||||
void Rendering::close()
|
||||
{
|
||||
glfwSetWindowShouldClose(main_.window(), true);
|
||||
}
|
||||
|
||||
|
||||
void Rendering::pushAttrib(RenderingAttrib ra)
|
||||
{
|
||||
// push it to top of pile
|
||||
draw_attributes_.push_front(ra);
|
||||
|
||||
// apply Changes to OpenGL
|
||||
glViewport(0, 0, ra.viewport.x, ra.viewport.y);
|
||||
glClearColor(ra.clear_color.r, ra.clear_color.g, ra.clear_color.b, ra.clear_color.a);
|
||||
}
|
||||
|
||||
void Rendering::popAttrib()
|
||||
{
|
||||
// pops the top of the pile
|
||||
if (draw_attributes_.size() > 0)
|
||||
draw_attributes_.pop_front();
|
||||
|
||||
// set attribute element to default
|
||||
RenderingAttrib ra = currentAttrib();
|
||||
|
||||
// apply Changes to OpenGL
|
||||
glViewport(0, 0, ra.viewport.x, ra.viewport.y);
|
||||
glClearColor(ra.clear_color.r, ra.clear_color.g, ra.clear_color.b, ra.clear_color.a);
|
||||
}
|
||||
|
||||
RenderingAttrib Rendering::currentAttrib()
|
||||
{
|
||||
// default rendering attrib is the main window's
|
||||
RenderingAttrib ra = main_.attribs();
|
||||
|
||||
// but if there is an element at top, return it
|
||||
if (draw_attributes_.size() > 0)
|
||||
ra = draw_attributes_.front();
|
||||
return ra;
|
||||
}
|
||||
|
||||
glm::mat4 Rendering::Projection()
|
||||
{
|
||||
static glm::mat4 projection = glm::ortho(-SCENE_UNIT, SCENE_UNIT, -SCENE_UNIT, SCENE_UNIT, -SCENE_DEPTH, 1.f);
|
||||
glm::mat4 scale = glm::scale(glm::identity<glm::mat4>(), glm::vec3(1.f, main_.aspectRatio(), 1.f));
|
||||
|
||||
return projection * scale;
|
||||
}
|
||||
|
||||
|
||||
glm::vec3 Rendering::unProject(glm::vec2 screen_coordinate, glm::mat4 modelview)
|
||||
{
|
||||
glm::vec3 coordinates = glm::vec3( screen_coordinate.x, main_.height() - screen_coordinate.y, 0.f);
|
||||
glm::vec4 viewport = glm::vec4( 0.f, 0.f, main_.width(), main_.height());
|
||||
|
||||
// Log::Info("unproject %d x %d", main_window_attributes_.viewport.x, main_window_attributes_.viewport.y);
|
||||
|
||||
glm::vec3 point = glm::unProject(coordinates, modelview, Projection(), viewport);
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
|
||||
glm::vec2 Rendering::project(glm::vec3 scene_coordinate, glm::mat4 modelview, bool to_framebuffer)
|
||||
{
|
||||
glm::vec4 viewport;
|
||||
if (to_framebuffer)
|
||||
viewport= glm::vec4( 0.f, 0.f, main_.width(), main_.height());
|
||||
else
|
||||
viewport= glm::vec4( 0.f, 0.f, main_.width() / main_.dpiScale(), main_.height() / main_.dpiScale());
|
||||
glm::vec3 P = glm::project( scene_coordinate, modelview, Projection(), viewport );
|
||||
|
||||
return glm::vec2(P.x, viewport.w - P.y);
|
||||
}
|
||||
|
||||
void Rendering::FileDropped(GLFWwindow *, int path_count, const char* paths[])
|
||||
{
|
||||
for (int i = 0; i < path_count; ++i) {
|
||||
std::string filename(paths[i]);
|
||||
if (filename.empty())
|
||||
break;
|
||||
// try to create a source
|
||||
Mixer::manager().addSource ( Mixer::manager().createSourceFile( filename ) );
|
||||
}
|
||||
if (path_count>0) {
|
||||
UserInterface::manager().showPannel();
|
||||
Rendering::manager().mainWindow().show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Screenshot *Rendering::currentScreenshot()
|
||||
{
|
||||
return &screenshot_;
|
||||
}
|
||||
|
||||
void Rendering::requestScreenshot()
|
||||
{
|
||||
request_screenshot_ = true;
|
||||
}
|
||||
|
||||
|
||||
// custom surface with a new VAO
|
||||
class WindowSurface : public Primitive {
|
||||
|
||||
public:
|
||||
WindowSurface(Shader *s = new ImageShader);
|
||||
};
|
||||
|
||||
WindowSurface::WindowSurface(Shader *s) : Primitive(s)
|
||||
{
|
||||
points_ = std::vector<glm::vec3> { glm::vec3( -1.f, -1.f, 0.f ), glm::vec3( -1.f, 1.f, 0.f ),
|
||||
glm::vec3( 1.f, -1.f, 0.f ), glm::vec3( 1.f, 1.f, 0.f ) };
|
||||
colors_ = std::vector<glm::vec4> { glm::vec4( 1.f, 1.f, 1.f , 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ),
|
||||
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ) };
|
||||
texCoords_ = std::vector<glm::vec2> { glm::vec2( 0.f, 1.f ), glm::vec2( 0.f, 0.f ),
|
||||
glm::vec2( 1.f, 1.f ), glm::vec2( 1.f, 0.f ) };
|
||||
indices_ = std::vector<uint> { 0, 1, 2, 3 };
|
||||
drawMode_ = GL_TRIANGLE_STRIP;
|
||||
}
|
||||
|
||||
|
||||
RenderingWindow::RenderingWindow() : window_(nullptr), master_(nullptr),
|
||||
index_(-1), dpi_scale_(1.f), textureid_(0), fbo_(0), surface_(nullptr), request_toggle_fullscreen_(false)
|
||||
{
|
||||
}
|
||||
|
||||
RenderingWindow::~RenderingWindow()
|
||||
{
|
||||
if (surface_ != nullptr)
|
||||
delete surface_;
|
||||
if (fbo_ != 0)
|
||||
glDeleteFramebuffers(1, &fbo_);
|
||||
}
|
||||
|
||||
void RenderingWindow::setTitle(const std::string &title)
|
||||
{
|
||||
std::string fulltitle = Settings::application.windows[index_].name;
|
||||
if ( !title.empty() )
|
||||
fulltitle += " -- " + title;
|
||||
|
||||
glfwSetWindowTitle(window_, fulltitle.c_str());
|
||||
}
|
||||
|
||||
void RenderingWindow::setIcon(const std::string &resource)
|
||||
{
|
||||
size_t fpsize = 0;
|
||||
const char *fp = Resource::getData(resource, &fpsize);
|
||||
if (fp != nullptr) {
|
||||
GLFWimage icon;
|
||||
icon.pixels = stbi_load_from_memory( (const stbi_uc*)fp, fpsize, &icon.width, &icon.height, nullptr, 4 );
|
||||
glfwSetWindowIcon( window_, 1, &icon );
|
||||
free( icon.pixels );
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderingWindow::isFullscreen ()
|
||||
{
|
||||
// return (glfwGetWindowMonitor(window_) != nullptr);
|
||||
return Settings::application.windows[index_].fullscreen;
|
||||
}
|
||||
|
||||
GLFWmonitor *RenderingWindow::monitorAt(int x, int y)
|
||||
{
|
||||
// default to primary monitor
|
||||
GLFWmonitor *mo = glfwGetPrimaryMonitor();
|
||||
|
||||
// list all monitors
|
||||
int count_monitors = 0;
|
||||
GLFWmonitor** monitors = glfwGetMonitors(&count_monitors);
|
||||
// if there is more than one monitor
|
||||
if (count_monitors > 1) {
|
||||
// pick at the coordinates given or at pos of window
|
||||
// try every monitor
|
||||
int i = 0;
|
||||
for (; i < count_monitors; i++) {
|
||||
int workarea_x, workarea_y, workarea_width, workarea_height;
|
||||
#if GLFW_VERSION_MINOR > 2
|
||||
glfwGetMonitorWorkarea(monitors[i], &workarea_x, &workarea_y, &workarea_width, &workarea_height);
|
||||
#else
|
||||
glfwGetMonitorPos(monitors[i], &workarea_x, &workarea_y);
|
||||
const GLFWvidmode *vm = glfwGetVideoMode(monitors[i]);
|
||||
workarea_width = vm->width;
|
||||
workarea_height = vm->height;
|
||||
#endif
|
||||
if ( x >= workarea_x && x <= workarea_x + workarea_width &&
|
||||
y >= workarea_y && y <= workarea_y + workarea_height)
|
||||
break;
|
||||
}
|
||||
// found the monitor containing this point !
|
||||
if ( i < count_monitors)
|
||||
mo = monitors[i];
|
||||
}
|
||||
|
||||
return mo;
|
||||
}
|
||||
|
||||
GLFWmonitor *RenderingWindow::monitorNamed(const std::string &name)
|
||||
{
|
||||
// default to primary monitor
|
||||
GLFWmonitor *mo = glfwGetPrimaryMonitor();
|
||||
|
||||
// list all monitors
|
||||
int count_monitors = 0;
|
||||
GLFWmonitor** monitors = glfwGetMonitors(&count_monitors);
|
||||
// if there is more than one monitor
|
||||
if (count_monitors > 1) {
|
||||
// pick at the coordinates given or at pos of window
|
||||
// try every monitor
|
||||
int i = 0;
|
||||
for (; i < count_monitors; i++) {
|
||||
if ( std::string( glfwGetMonitorName(monitors[i])) == name )
|
||||
break;
|
||||
}
|
||||
// found the monitor with this name
|
||||
if ( i < count_monitors)
|
||||
mo = monitors[i];
|
||||
}
|
||||
|
||||
return mo;
|
||||
}
|
||||
|
||||
GLFWmonitor *RenderingWindow::monitor()
|
||||
{
|
||||
// pick at the coordinates given or at pos of window
|
||||
int x, y;
|
||||
glfwGetWindowPos(window_, &x, &y);
|
||||
return monitorAt(x, y);
|
||||
}
|
||||
|
||||
void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
|
||||
{
|
||||
// done request
|
||||
request_toggle_fullscreen_ = false;
|
||||
|
||||
// if in fullscreen mode
|
||||
if (mo == nullptr) {
|
||||
// store fullscreen mode
|
||||
Settings::application.windows[index_].fullscreen = false;
|
||||
|
||||
// set to window mode
|
||||
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||
glfwSetWindowMonitor( window_, nullptr, Settings::application.windows[index_].x,
|
||||
Settings::application.windows[index_].y,
|
||||
Settings::application.windows[index_].w,
|
||||
Settings::application.windows[index_].h, 0 );
|
||||
}
|
||||
// not in fullscreen mode
|
||||
else {
|
||||
// store fullscreen mode
|
||||
Settings::application.windows[index_].fullscreen = true;
|
||||
Settings::application.windows[index_].monitor = glfwGetMonitorName(mo);
|
||||
|
||||
// set to fullscreen mode
|
||||
const GLFWvidmode * mode = glfwGetVideoMode(mo);
|
||||
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
||||
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||
|
||||
// Enable vsync on output window only (i.e. not 0 if has a master)
|
||||
// Workaround for disabled vsync in fullscreen (https://github.com/glfw/glfw/issues/1072)
|
||||
glfwSwapInterval( nullptr == master_ ? 0 : Settings::application.render.vsync);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void RenderingWindow::exitFullscreen()
|
||||
{
|
||||
if (isFullscreen()) {
|
||||
// exit fullscreen
|
||||
request_toggle_fullscreen_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderingWindow::toggleFullscreen()
|
||||
{
|
||||
request_toggle_fullscreen_ = true;
|
||||
}
|
||||
|
||||
void RenderingWindow::toggleFullscreen_()
|
||||
{
|
||||
if (request_toggle_fullscreen_) {
|
||||
|
||||
// if in fullscreen mode
|
||||
if (glfwGetWindowMonitor(window_) != nullptr) {
|
||||
// exit fullscreen
|
||||
setFullscreen_(nullptr);
|
||||
}
|
||||
// not in fullscreen mode
|
||||
else {
|
||||
// enter fullscreen in monitor where the window is
|
||||
setFullscreen_(monitor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int RenderingWindow::width()
|
||||
{
|
||||
return window_attributes_.viewport.x;
|
||||
}
|
||||
|
||||
int RenderingWindow::height()
|
||||
{
|
||||
return window_attributes_.viewport.y;
|
||||
}
|
||||
|
||||
int RenderingWindow::pixelsforRealHeight(float milimeters)
|
||||
{
|
||||
GLFWmonitor *mo = monitor();
|
||||
|
||||
int mm_w = 0;
|
||||
int mm_h = 0;
|
||||
glfwGetMonitorPhysicalSize(mo, &mm_w, &mm_h);
|
||||
|
||||
float pixels = milimeters;
|
||||
if (mm_h > 0)
|
||||
pixels *= static_cast<float>(glfwGetVideoMode(mo)->height) / static_cast<float>(mm_h);
|
||||
else
|
||||
pixels *= 5; // something reasonnable if monitor's physical size is unknown
|
||||
|
||||
return static_cast<int>( round(pixels) );
|
||||
}
|
||||
|
||||
float RenderingWindow::aspectRatio()
|
||||
{
|
||||
return static_cast<float>(window_attributes_.viewport.x) / static_cast<float>(window_attributes_.viewport.y);
|
||||
}
|
||||
|
||||
bool RenderingWindow::init(int index, GLFWwindow *share)
|
||||
{
|
||||
index_ = index;
|
||||
master_ = share;
|
||||
|
||||
// access Settings
|
||||
Settings::WindowConfig winset = Settings::application.windows[index_];
|
||||
|
||||
// do not show at creation
|
||||
glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
|
||||
|
||||
// create the window normal
|
||||
window_ = glfwCreateWindow(winset.w, winset.h, winset.name.c_str(), NULL, master_);
|
||||
if (window_ == NULL){
|
||||
Log::Error("Failed to create GLFW Window %d", index_);
|
||||
return false;
|
||||
}
|
||||
|
||||
// set position
|
||||
glfwSetWindowPos(window_, winset.x, winset.y);
|
||||
|
||||
/// CALLBACKS
|
||||
// store global ref to pointers (used by callbacks)
|
||||
GLFW_window_[window_] = this;
|
||||
// window position and resize callbacks
|
||||
glfwSetWindowSizeCallback( window_, WindowResizeCallback );
|
||||
// glfwSetFramebufferSizeCallback( window_, WindowResizeCallback );
|
||||
glfwSetWindowPosCallback( window_, WindowMoveCallback );
|
||||
|
||||
// take opengl context ownership
|
||||
glfwMakeContextCurrent(window_);
|
||||
|
||||
//
|
||||
// Initialize OpenGL loader on first call
|
||||
//
|
||||
static bool glad_initialized = false;
|
||||
if ( !glad_initialized ) {
|
||||
bool err = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress) == 0;
|
||||
if (err) {
|
||||
Log::Error("Failed to initialize GLAD OpenGL loader.");
|
||||
return false;
|
||||
}
|
||||
glad_initialized = true;
|
||||
}
|
||||
|
||||
// get rendering area
|
||||
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
||||
// DPI scaling (retina)
|
||||
dpi_scale_ = float(window_attributes_.viewport.y) / float(winset.h);
|
||||
|
||||
// This hint can improve the speed of texturing when perspective-correct texture coordinate interpolation isn't needed
|
||||
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
|
||||
// fast mipmaps (we are not really using mipmaps anyway)
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
||||
// acurate derivative for shader
|
||||
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
|
||||
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
||||
|
||||
// if not main window
|
||||
if ( master_ != NULL ) {
|
||||
// Enable vsync on output window
|
||||
glfwSwapInterval(Settings::application.render.vsync);
|
||||
// no need for multisampling
|
||||
glDisable(GL_MULTISAMPLE);
|
||||
// clear to black
|
||||
window_attributes_.clear_color = glm::vec4(0.f, 0.f, 0.f, 1.f);
|
||||
// give back context ownership
|
||||
glfwMakeContextCurrent(master_);
|
||||
}
|
||||
else {
|
||||
// Disable vsync on main window
|
||||
glfwSwapInterval(0);
|
||||
// Enable Antialiasing multisampling
|
||||
if (Settings::application.render.multisampling > 0) {
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST);
|
||||
}
|
||||
// clear to grey
|
||||
window_attributes_.clear_color = glm::vec4(COLOR_BGROUND, 1.f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderingWindow::show()
|
||||
{
|
||||
glfwShowWindow(window_);
|
||||
|
||||
if ( Settings::application.windows[index_].fullscreen ) {
|
||||
GLFWmonitor *mo = monitorNamed(Settings::application.windows[index_].monitor);
|
||||
setFullscreen_(mo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void RenderingWindow::makeCurrent()
|
||||
{
|
||||
// handle window resize
|
||||
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
||||
|
||||
// ensure main context is current
|
||||
glfwMakeContextCurrent(window_);
|
||||
|
||||
// set and clear
|
||||
glViewport(0, 0, window_attributes_.viewport.x, window_attributes_.viewport.y);
|
||||
glClearColor(window_attributes_.clear_color.r, window_attributes_.clear_color.g,
|
||||
window_attributes_.clear_color.b, window_attributes_.clear_color.a);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
// TODO update parameters for draw on resize event (not every frame)
|
||||
|
||||
void RenderingWindow::draw(FrameBuffer *fb)
|
||||
{
|
||||
if (!window_ || !fb)
|
||||
return;
|
||||
|
||||
// only draw if window is not iconified
|
||||
if( !glfwGetWindowAttrib(window_, GLFW_ICONIFIED ) ) {
|
||||
|
||||
// update viewport (could be done with callback)
|
||||
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
||||
|
||||
// take context ownership
|
||||
glfwMakeContextCurrent(window_);
|
||||
|
||||
// setup attribs
|
||||
Rendering::manager().pushAttrib(window_attributes_);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// blit framebuffer
|
||||
if (Settings::application.render.blit) {
|
||||
|
||||
if ( textureid_ != fb->texture()) {
|
||||
|
||||
textureid_ = fb->texture();
|
||||
|
||||
// create a new fbo in this opengl context
|
||||
if (fbo_ != 0)
|
||||
glDeleteFramebuffers(1, &fbo_);
|
||||
glGenFramebuffers(1, &fbo_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||
|
||||
// attach the 2D texture to local FBO
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
|
||||
|
||||
Log::Info("Blit to output window enabled.");
|
||||
}
|
||||
|
||||
// calculate scaling factor of frame buffer inside window
|
||||
int rx, ry, rw, rh;
|
||||
float renderingAspectRatio = fb->aspectRatio();
|
||||
if (aspectRatio() < renderingAspectRatio) {
|
||||
int nh = (int)( float(window_attributes_.viewport.x) / renderingAspectRatio);
|
||||
rx = 0;
|
||||
ry = (window_attributes_.viewport.y - nh) / 2;
|
||||
rw = window_attributes_.viewport.x;
|
||||
rh = (window_attributes_.viewport.y + nh) / 2;
|
||||
} else {
|
||||
int nw = (int)( float(window_attributes_.viewport.y) * renderingAspectRatio );
|
||||
rx = (window_attributes_.viewport.x - nw) / 2;
|
||||
ry = 0;
|
||||
rw = (window_attributes_.viewport.x + nw) / 2;
|
||||
rh = window_attributes_.viewport.y;
|
||||
}
|
||||
|
||||
// select fbo texture read target
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
||||
|
||||
// select screen target
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
// blit operation from fbo (containing texture) to screen
|
||||
glBlitFramebuffer(0, fb->height(), fb->width(), 0, rx, ry, rw, rh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
|
||||
}
|
||||
// draw geometry
|
||||
else
|
||||
{
|
||||
// VAO is not shared between multiple contexts of different windows
|
||||
// so we have to create a new VAO for rendering the surface in this window
|
||||
if (surface_ == 0)
|
||||
surface_ = new WindowSurface;
|
||||
|
||||
// calculate scaling factor of frame buffer inside window
|
||||
float windowAspectRatio = aspectRatio();
|
||||
float renderingAspectRatio = fb->aspectRatio();
|
||||
glm::vec3 scale;
|
||||
if (windowAspectRatio < renderingAspectRatio)
|
||||
scale = glm::vec3(1.f, windowAspectRatio / renderingAspectRatio, 1.f);
|
||||
else
|
||||
scale = glm::vec3(renderingAspectRatio / windowAspectRatio, 1.f, 1.f);
|
||||
|
||||
// make sure previous shader in another glcontext is disabled
|
||||
ShadingProgram::enduse();
|
||||
|
||||
// draw
|
||||
glBindTexture(GL_TEXTURE_2D, fb->texture());
|
||||
// surface->shader()->color.a = 0.4f; // TODO alpha blending ?
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
|
||||
surface_->draw(glm::scale(glm::identity<glm::mat4>(), scale), projection);
|
||||
|
||||
// done drawing (unload shader from this glcontext)
|
||||
ShadingProgram::enduse();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
// restore attribs
|
||||
Rendering::manager().popAttrib();
|
||||
|
||||
}
|
||||
|
||||
// give back context ownership
|
||||
glfwMakeContextCurrent(master_);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Discarded because not working under OSX - kept in case it would become useful
|
||||
//
|
||||
// Linking pipeline to the rendering instance ensures the opengl contexts
|
||||
// created by gstreamer inside plugins (e.g. glsinkbin) is the same
|
||||
//
|
||||
|
||||
static GstBusSyncReply
|
||||
bus_sync_handler (GstBus *, GstMessage * msg, gpointer )
|
||||
{
|
||||
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_NEED_CONTEXT) {
|
||||
const gchar* contextType;
|
||||
gst_message_parse_context_type(msg, &contextType);
|
||||
|
||||
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
|
||||
GstContext *displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
|
||||
gst_context_set_gl_display(displayContext, global_display);
|
||||
gst_element_set_context(GST_ELEMENT(msg->src), displayContext);
|
||||
gst_context_unref (displayContext);
|
||||
|
||||
g_info ("Managed %s\n", contextType);
|
||||
}
|
||||
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
|
||||
GstContext *appContext = gst_context_new("gst.gl.app_context", TRUE);
|
||||
GstStructure* structure = gst_context_writable_structure(appContext);
|
||||
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, global_gl_context, nullptr);
|
||||
gst_element_set_context(GST_ELEMENT(msg->src), appContext);
|
||||
gst_context_unref (appContext);
|
||||
|
||||
g_info ("Managed %s\n", contextType);
|
||||
}
|
||||
}
|
||||
|
||||
gst_message_unref (msg);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
void Rendering::LinkPipeline( GstPipeline *pipeline )
|
||||
{
|
||||
// capture bus signals to force a unique opengl context for all GST elements
|
||||
GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) bus_sync_handler, pipeline, NULL);
|
||||
gst_object_unref (m_bus);
|
||||
|
||||
|
||||
// GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
// gst_bus_enable_sync_message_emission (m_bus);
|
||||
// g_signal_connect (m_bus, "sync-message", G_CALLBACK (bus_sync_handler), pipeline);
|
||||
// gst_object_unref (m_bus);
|
||||
}
|
||||
137
Screenshot.cpp
@@ -1,137 +0,0 @@
|
||||
#include "Screenshot.h"
|
||||
|
||||
#include <memory.h>
|
||||
#include <assert.h>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
// standalone image loader
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
|
||||
|
||||
Screenshot::Screenshot()
|
||||
{
|
||||
Width = Height = 0;
|
||||
Data = nullptr;
|
||||
Pbo = 0;
|
||||
Pbo_size = 0;
|
||||
Pbo_full = false;
|
||||
}
|
||||
|
||||
Screenshot::~Screenshot()
|
||||
{
|
||||
if (Pbo > 0)
|
||||
glDeleteBuffers(1, &Pbo);
|
||||
if (Data)
|
||||
free(Data);
|
||||
}
|
||||
|
||||
bool Screenshot::isFull()
|
||||
{
|
||||
return Pbo_full;
|
||||
}
|
||||
|
||||
void Screenshot::captureGL(int x, int y, int w, int h)
|
||||
{
|
||||
Width = w - x;
|
||||
Height = h - y;
|
||||
unsigned int size = Width * Height * 3;
|
||||
|
||||
// create BPO
|
||||
if (Pbo == 0)
|
||||
glGenBuffers(1, &Pbo);
|
||||
|
||||
// bind
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, Pbo);
|
||||
|
||||
// init
|
||||
if (Pbo_size != size) {
|
||||
Pbo_size = size;
|
||||
if (Data) free(Data);
|
||||
Data = (unsigned char*) malloc(Pbo_size);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, Pbo_size, NULL, GL_STREAM_READ);
|
||||
}
|
||||
|
||||
// screenshot to PBO (fast)
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
Pbo_full = true;
|
||||
|
||||
// done
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
void Screenshot::save(std::string filename)
|
||||
{
|
||||
// is there something to save?
|
||||
if (Pbo && Pbo_size > 0 && Pbo_full) {
|
||||
|
||||
// bind buffer
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, Pbo);
|
||||
|
||||
// get pixels (quite fast)
|
||||
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
if (NULL != ptr) {
|
||||
memmove(Data, ptr, Pbo_size);
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
}
|
||||
|
||||
// initiate saving in thread (slow)
|
||||
std::thread(storeToFile, this, filename).detach();
|
||||
|
||||
// ready for next
|
||||
Pbo_full = false;
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Screenshot::RemoveAlpha()
|
||||
{
|
||||
unsigned int* p = (unsigned int*)Data;
|
||||
int n = Width * Height;
|
||||
while (n-- > 0)
|
||||
{
|
||||
*p |= 0xFF000000;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
void Screenshot::FlipVertical()
|
||||
{
|
||||
int comp = 4;
|
||||
int stride = Width * comp;
|
||||
unsigned char* line_tmp = new unsigned char[stride];
|
||||
unsigned char* line_a = (unsigned char*)Data;
|
||||
unsigned char* line_b = (unsigned char*)Data + (stride * (Height - 1));
|
||||
while (line_a < line_b)
|
||||
{
|
||||
memcpy(line_tmp, line_a, stride);
|
||||
memcpy(line_a, line_b, stride);
|
||||
memcpy(line_b, line_tmp, stride);
|
||||
line_a += stride;
|
||||
line_b -= stride;
|
||||
}
|
||||
delete[] line_tmp;
|
||||
}
|
||||
|
||||
// Thread to perform slow operation of saving to file
|
||||
void Screenshot::storeToFile(Screenshot *s, std::string filename)
|
||||
{
|
||||
static std::atomic<bool> ScreenshotSavePending_ = false;
|
||||
// only one save at a time
|
||||
if (ScreenshotSavePending_)
|
||||
return;
|
||||
ScreenshotSavePending_ = true;
|
||||
// got data to save ?
|
||||
if (s && s->Data) {
|
||||
// save file
|
||||
stbi_flip_vertically_on_write(true);
|
||||
stbi_write_png(filename.c_str(), s->Width, s->Height, 3, s->Data, s->Width * 3);
|
||||
}
|
||||
ScreenshotSavePending_ = false;
|
||||
}
|
||||
449
Session.cpp
@@ -1,449 +0,0 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Session.h"
|
||||
#include "FrameGrabber.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "SessionSource.h"
|
||||
#include "MixingGroup.h"
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f)
|
||||
{
|
||||
filename_ = "";
|
||||
|
||||
config_[View::RENDERING] = new Group;
|
||||
config_[View::RENDERING]->scale_ = glm::vec3(0.f);
|
||||
|
||||
config_[View::GEOMETRY] = new Group;
|
||||
config_[View::GEOMETRY]->scale_ = Settings::application.views[View::GEOMETRY].default_scale;
|
||||
config_[View::GEOMETRY]->translation_ = Settings::application.views[View::GEOMETRY].default_translation;
|
||||
|
||||
config_[View::LAYER] = new Group;
|
||||
config_[View::LAYER]->scale_ = Settings::application.views[View::LAYER].default_scale;
|
||||
config_[View::LAYER]->translation_ = Settings::application.views[View::LAYER].default_translation;
|
||||
|
||||
config_[View::MIXING] = new Group;
|
||||
config_[View::MIXING]->scale_ = Settings::application.views[View::MIXING].default_scale;
|
||||
config_[View::MIXING]->translation_ = Settings::application.views[View::MIXING].default_translation;
|
||||
|
||||
config_[View::TEXTURE] = new Group;
|
||||
config_[View::TEXTURE]->scale_ = Settings::application.views[View::TEXTURE].default_scale;
|
||||
config_[View::TEXTURE]->translation_ = Settings::application.views[View::TEXTURE].default_translation;
|
||||
}
|
||||
|
||||
|
||||
Session::~Session()
|
||||
{
|
||||
// TODO delete all mixing groups?
|
||||
auto group_iter = mixing_groups_.begin();
|
||||
while ( group_iter != mixing_groups_.end() ){
|
||||
delete (*group_iter);
|
||||
group_iter = mixing_groups_.erase(group_iter);
|
||||
}
|
||||
|
||||
// delete all sources
|
||||
for(auto it = sources_.begin(); it != sources_.end(); ) {
|
||||
// erase this source from the list
|
||||
it = deleteSource(*it);
|
||||
}
|
||||
|
||||
delete config_[View::RENDERING];
|
||||
delete config_[View::GEOMETRY];
|
||||
delete config_[View::LAYER];
|
||||
delete config_[View::MIXING];
|
||||
delete config_[View::TEXTURE];
|
||||
}
|
||||
|
||||
void Session::setActive (bool on)
|
||||
{
|
||||
if (active_ != on) {
|
||||
active_ = on;
|
||||
for(auto it = sources_.begin(); it != sources_.end(); it++) {
|
||||
(*it)->setActive(active_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update all sources
|
||||
void Session::update(float dt)
|
||||
{
|
||||
// no update until render view is initialized
|
||||
if ( render_.frame() == nullptr )
|
||||
return;
|
||||
|
||||
// pre-render of all sources
|
||||
failedSource_ = nullptr;
|
||||
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); it++){
|
||||
|
||||
// ensure the RenderSource is rendering this session
|
||||
RenderSource *s = dynamic_cast<RenderSource *>( *it );
|
||||
if ( s!= nullptr && s->session() != this )
|
||||
s->setSession(this);
|
||||
|
||||
if ( (*it)->failed() ) {
|
||||
failedSource_ = (*it);
|
||||
}
|
||||
else {
|
||||
// render the source
|
||||
(*it)->render();
|
||||
// update the source
|
||||
(*it)->update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
// update session's mixing groups
|
||||
auto group_iter = mixing_groups_.begin();
|
||||
while ( group_iter != mixing_groups_.end() ){
|
||||
// update all valid groups
|
||||
if ((*group_iter)->size() > 1) {
|
||||
(*group_iter)->update(dt);
|
||||
group_iter++;
|
||||
}
|
||||
else
|
||||
// delete invalid groups (singletons)
|
||||
group_iter = deleteMixingGroup(group_iter);
|
||||
}
|
||||
|
||||
// apply fading (smooth dicotomic reaching)
|
||||
float f = render_.fading();
|
||||
if ( ABS_DIFF(f, fading_target_) > EPSILON) {
|
||||
render_.setFading( f + ( fading_target_ - f ) / 2.f);
|
||||
}
|
||||
|
||||
// update the scene tree
|
||||
render_.update(dt);
|
||||
|
||||
// draw render view in Frame Buffer
|
||||
render_.draw();
|
||||
|
||||
}
|
||||
|
||||
|
||||
SourceList::iterator Session::addSource(Source *s)
|
||||
{
|
||||
SourceList::iterator its = sources_.end();
|
||||
|
||||
// lock before change
|
||||
access_.lock();
|
||||
|
||||
// find the source
|
||||
its = find(s);
|
||||
|
||||
// ok, its NOT in the list !
|
||||
if (its == sources_.end()) {
|
||||
// insert the source in the rendering
|
||||
render_.scene.ws()->attach(s->group(View::RENDERING));
|
||||
// insert the source to the beginning of the list
|
||||
sources_.push_front(s);
|
||||
// return the iterator to the source created at the beginning
|
||||
its = sources_.begin();
|
||||
}
|
||||
|
||||
// unlock access
|
||||
access_.unlock();
|
||||
|
||||
return its;
|
||||
}
|
||||
|
||||
SourceList::iterator Session::deleteSource(Source *s)
|
||||
{
|
||||
// lock before change
|
||||
access_.lock();
|
||||
|
||||
// find the source
|
||||
SourceList::iterator its = find(s);
|
||||
// ok, its in the list !
|
||||
if (its != sources_.end()) {
|
||||
// remove Node from the rendering scene
|
||||
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
||||
// inform group
|
||||
if (s->mixingGroup() != nullptr)
|
||||
s->mixingGroup()->detach(s);
|
||||
// erase the source from the update list & get next element
|
||||
its = sources_.erase(its);
|
||||
// delete the source : safe now
|
||||
delete s;
|
||||
}
|
||||
|
||||
// unlock access
|
||||
access_.unlock();
|
||||
|
||||
// return end of next element
|
||||
return its;
|
||||
}
|
||||
|
||||
|
||||
void Session::removeSource(Source *s)
|
||||
{
|
||||
// lock before change
|
||||
access_.lock();
|
||||
|
||||
// find the source
|
||||
SourceList::iterator its = find(s);
|
||||
// ok, its in the list !
|
||||
if (its != sources_.end()) {
|
||||
// remove Node from the rendering scene
|
||||
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
||||
// inform group
|
||||
if (s->mixingGroup() != nullptr)
|
||||
s->mixingGroup()->detach(s);
|
||||
// erase the source from the update list & get next element
|
||||
sources_.erase(its);
|
||||
}
|
||||
|
||||
// unlock access
|
||||
access_.unlock();
|
||||
}
|
||||
|
||||
|
||||
Source *Session::popSource()
|
||||
{
|
||||
Source *s = nullptr;
|
||||
|
||||
SourceList::iterator its = sources_.begin();
|
||||
if (its != sources_.end())
|
||||
{
|
||||
s = *its;
|
||||
// remove Node from the rendering scene
|
||||
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
||||
// erase the source from the update list & get next element
|
||||
sources_.erase(its);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void Session::setResolution(glm::vec3 resolution, bool useAlpha)
|
||||
{
|
||||
// setup the render view: if not specified the default config resulution will be used
|
||||
render_.setResolution( resolution, useAlpha );
|
||||
// store the actual resolution set in the render view
|
||||
config_[View::RENDERING]->scale_ = render_.resolution();
|
||||
}
|
||||
|
||||
void Session::setFading(float f, bool forcenow)
|
||||
{
|
||||
if (forcenow)
|
||||
render_.setFading( f );
|
||||
|
||||
fading_target_ = CLAMP(f, 0.f, 1.f);
|
||||
}
|
||||
|
||||
SourceList::iterator Session::begin()
|
||||
{
|
||||
return sources_.begin();
|
||||
}
|
||||
|
||||
SourceList::iterator Session::end()
|
||||
{
|
||||
return sources_.end();
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(Source *s)
|
||||
{
|
||||
return std::find(sources_.begin(), sources_.end(), s);
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(uint64_t id)
|
||||
{
|
||||
return std::find_if(sources_.begin(), sources_.end(), Source::hasId(id));
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(std::string namesource)
|
||||
{
|
||||
return std::find_if(sources_.begin(), sources_.end(), Source::hasName(namesource));
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(Node *node)
|
||||
{
|
||||
return std::find_if(sources_.begin(), sources_.end(), Source::hasNode(node));
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(float depth_from, float depth_to)
|
||||
{
|
||||
return std::find_if(sources_.begin(), sources_.end(), Source::hasDepth(depth_from, depth_to));
|
||||
}
|
||||
|
||||
SourceList Session::getDepthSortedList() const
|
||||
{
|
||||
return depth_sorted(sources_);
|
||||
}
|
||||
|
||||
uint Session::numSource() const
|
||||
{
|
||||
return sources_.size();
|
||||
}
|
||||
|
||||
SourceIdList Session::getIdList() const
|
||||
{
|
||||
return ids(sources_);
|
||||
}
|
||||
|
||||
bool Session::empty() const
|
||||
{
|
||||
return sources_.empty();
|
||||
}
|
||||
|
||||
SourceList::iterator Session::at(int index)
|
||||
{
|
||||
if (index<0)
|
||||
return sources_.end();
|
||||
|
||||
int i = 0;
|
||||
SourceList::iterator it = sources_.begin();
|
||||
while ( i < index && it != sources_.end() ){
|
||||
i++;
|
||||
it++;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
int Session::index(SourceList::iterator it) const
|
||||
{
|
||||
int index = -1;
|
||||
int count = 0;
|
||||
for(auto i = sources_.begin(); i != sources_.end(); i++, count++) {
|
||||
if ( i == it ) {
|
||||
index = count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void Session::move(int current_index, int target_index)
|
||||
{
|
||||
if ( current_index < 0 || current_index > (int) sources_.size()
|
||||
|| target_index < 0 || target_index > (int) sources_.size()
|
||||
|| target_index == current_index )
|
||||
return;
|
||||
|
||||
SourceList::iterator from = at(current_index);
|
||||
SourceList::iterator to = at(target_index);
|
||||
if ( target_index > current_index )
|
||||
to++;
|
||||
|
||||
Source *s = (*from);
|
||||
sources_.erase(from);
|
||||
sources_.insert(to, s);
|
||||
}
|
||||
|
||||
bool Session::canlink (SourceList sources)
|
||||
{
|
||||
bool canlink = true;
|
||||
|
||||
// verify that all sources given are valid in the sesion
|
||||
validate(sources);
|
||||
|
||||
for (auto it = sources.begin(); it != sources.end(); it++) {
|
||||
// this source is linked
|
||||
if ( (*it)->mixingGroup() != nullptr ) {
|
||||
// askt its group to detach it
|
||||
canlink = false;
|
||||
}
|
||||
}
|
||||
|
||||
return canlink;
|
||||
}
|
||||
|
||||
void Session::link(SourceList sources, Group *parent)
|
||||
{
|
||||
// we need at least 2 sources to make a group
|
||||
if (sources.size() > 1) {
|
||||
|
||||
unlink(sources);
|
||||
|
||||
// create and add a new mixing group
|
||||
MixingGroup *g = new MixingGroup(sources);
|
||||
mixing_groups_.push_back(g);
|
||||
|
||||
// if provided, attach the group to the parent
|
||||
if (g && parent != nullptr)
|
||||
g->attachTo( parent );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Session::unlink (SourceList sources)
|
||||
{
|
||||
// verify that all sources given are valid in the sesion
|
||||
validate(sources);
|
||||
|
||||
// brute force : detach all given sources
|
||||
for (auto it = sources.begin(); it != sources.end(); it++) {
|
||||
// this source is linked
|
||||
if ( (*it)->mixingGroup() != nullptr ) {
|
||||
// askt its group to detach it
|
||||
(*it)->mixingGroup()->detach(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::list<SourceList> Session::getMixingGroups () const
|
||||
{
|
||||
std::list<SourceList> lmg;
|
||||
|
||||
for (auto group_it = mixing_groups_.begin(); group_it!= mixing_groups_.end(); group_it++)
|
||||
lmg.push_back( (*group_it)->getCopy() );
|
||||
|
||||
return lmg;
|
||||
}
|
||||
|
||||
|
||||
std::list<MixingGroup *>::iterator Session::deleteMixingGroup (std::list<MixingGroup *>::iterator g)
|
||||
{
|
||||
if (g != mixing_groups_.end()) {
|
||||
delete (*g);
|
||||
return mixing_groups_.erase(g);
|
||||
}
|
||||
return mixing_groups_.end();
|
||||
}
|
||||
|
||||
std::list<MixingGroup *>::iterator Session::beginMixingGroup()
|
||||
{
|
||||
return mixing_groups_.begin();
|
||||
}
|
||||
|
||||
std::list<MixingGroup *>::iterator Session::endMixingGroup()
|
||||
{
|
||||
return mixing_groups_.end();
|
||||
}
|
||||
|
||||
void Session::lock()
|
||||
{
|
||||
access_.lock();
|
||||
}
|
||||
|
||||
void Session::unlock()
|
||||
{
|
||||
access_.unlock();
|
||||
}
|
||||
|
||||
|
||||
void Session::validate (SourceList &sources)
|
||||
{
|
||||
// verify that all sources given are valid in the sesion
|
||||
// and remove the invalid sources
|
||||
for (auto _it = sources.begin(); _it != sources.end(); ) {
|
||||
SourceList::iterator found = std::find(sources_.begin(), sources_.end(), *_it);
|
||||
if ( found == sources_.end() )
|
||||
_it = sources.erase(_it);
|
||||
else
|
||||
_it++;
|
||||
}
|
||||
}
|
||||
|
||||
Session *Session::load(const std::string& filename, uint recursion)
|
||||
{
|
||||
// create session
|
||||
SessionCreator creator(recursion);
|
||||
creator.load(filename);
|
||||
|
||||
// return created session
|
||||
return creator.session();
|
||||
}
|
||||
|
||||
114
Session.h
@@ -1,114 +0,0 @@
|
||||
#ifndef SESSION_H
|
||||
#define SESSION_H
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "RenderView.h"
|
||||
#include "Source.h"
|
||||
|
||||
class FrameGrabber;
|
||||
class MixingGroup;
|
||||
|
||||
class Session
|
||||
{
|
||||
public:
|
||||
Session();
|
||||
~Session();
|
||||
|
||||
static Session *load(const std::string& filename, uint recursion = 0);
|
||||
|
||||
// add given source into the session
|
||||
SourceList::iterator addSource (Source *s);
|
||||
|
||||
// delete the source s from the session
|
||||
SourceList::iterator deleteSource (Source *s);
|
||||
|
||||
// remove this source from the session
|
||||
// Does not delete the source
|
||||
void removeSource(Source *s);
|
||||
|
||||
// get ptr to front most source and remove it from the session
|
||||
// Does not delete the source
|
||||
Source *popSource();
|
||||
|
||||
// management of list of sources
|
||||
bool empty() const;
|
||||
uint numSource() const;
|
||||
SourceList::iterator begin ();
|
||||
SourceList::iterator end ();
|
||||
SourceList::iterator find (Source *s);
|
||||
SourceList::iterator find (std::string name);
|
||||
SourceList::iterator find (Node *node);
|
||||
SourceList::iterator find (float depth_from, float depth_to);
|
||||
SourceList getDepthSortedList () const;
|
||||
|
||||
SourceList::iterator find (uint64_t id);
|
||||
SourceIdList getIdList() const;
|
||||
|
||||
SourceList::iterator at (int index);
|
||||
int index (SourceList::iterator it) const;
|
||||
void move (int current_index, int target_index);
|
||||
|
||||
// update all sources and mark sources which failed
|
||||
void update (float dt);
|
||||
|
||||
// update mode (active or not)
|
||||
void setActive (bool on);
|
||||
inline bool active () { return active_; }
|
||||
|
||||
// return the last source which failed
|
||||
Source *failedSource () { return failedSource_; }
|
||||
|
||||
// get frame result of render
|
||||
inline FrameBuffer *frame () const { return render_.frame(); }
|
||||
|
||||
// configure rendering resolution
|
||||
void setResolution (glm::vec3 resolution, bool useAlpha = false);
|
||||
|
||||
// manipulate fading of output
|
||||
void setFading (float f, bool forcenow = false);
|
||||
inline float fading () const { return fading_target_; }
|
||||
|
||||
// configuration for group nodes of views
|
||||
inline Group *config (View::Mode m) const { return config_.at(m); }
|
||||
|
||||
// name of file containing this session (for transfer)
|
||||
void setFilename (const std::string &filename) { filename_ = filename; }
|
||||
std::string filename () const { return filename_; }
|
||||
|
||||
// get the list of sources in mixing groups
|
||||
std::list<SourceList> getMixingGroups () const;
|
||||
// returns true if something can be done to create a mixing group
|
||||
bool canlink (SourceList sources);
|
||||
// try to link sources of the given list:
|
||||
// can either create a new mixing group or extend an existing group
|
||||
void link (SourceList sources, Group *parent = nullptr);
|
||||
// try to unlink sources of the given list:
|
||||
// can either delete an entire mixing group, or remove the given sources from existing groups
|
||||
void unlink (SourceList sources);
|
||||
// iterators for looping over mixing groups
|
||||
std::list<MixingGroup *>::iterator beginMixingGroup ();
|
||||
std::list<MixingGroup *>::iterator endMixingGroup ();
|
||||
std::list<MixingGroup *>::iterator deleteMixingGroup (std::list<MixingGroup *>::iterator g);
|
||||
|
||||
// lock and unlock access (e.g. while saving)
|
||||
void lock ();
|
||||
void unlock ();
|
||||
|
||||
protected:
|
||||
RenderView render_;
|
||||
std::string filename_;
|
||||
Source *failedSource_;
|
||||
SourceList sources_;
|
||||
void validate(SourceList &sources);
|
||||
|
||||
std::list<MixingGroup *> mixing_groups_;
|
||||
std::map<View::Mode, Group*> config_;
|
||||
bool active_;
|
||||
std::list<FrameGrabber *> grabbers_;
|
||||
float fading_target_;
|
||||
std::mutex access_;
|
||||
};
|
||||
|
||||
|
||||
#endif // SESSION_H
|
||||
@@ -1,668 +0,0 @@
|
||||
#include "SessionCreator.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "defines.h"
|
||||
#include "Scene.h"
|
||||
#include "Primitives.h"
|
||||
#include "Mesh.h"
|
||||
#include "Source.h"
|
||||
#include "MediaSource.h"
|
||||
#include "SessionSource.h"
|
||||
#include "StreamSource.h"
|
||||
#include "PatternSource.h"
|
||||
#include "DeviceSource.h"
|
||||
#include "NetworkSource.h"
|
||||
#include "Session.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "MediaPlayer.h"
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
using namespace tinyxml2;
|
||||
|
||||
|
||||
std::string SessionCreator::info(const std::string& filename)
|
||||
{
|
||||
std::string ret = "";
|
||||
|
||||
XMLDocument doc;
|
||||
XMLError eResult = doc.LoadFile(filename.c_str());
|
||||
if ( XMLResultError(eResult)) {
|
||||
Log::Warning("%s could not be openned.", filename.c_str());
|
||||
return ret;
|
||||
}
|
||||
|
||||
XMLElement *header = doc.FirstChildElement(APP_NAME);
|
||||
if (header != nullptr && header->Attribute("date") != 0) {
|
||||
int s = header->IntAttribute("size");
|
||||
ret = std::to_string( s ) + " source" + ( s > 1 ? "s\n" : "\n");
|
||||
const char *att_string = header->Attribute("resolution");
|
||||
if (att_string)
|
||||
ret += std::string( att_string ) + "\n";
|
||||
att_string = header->Attribute("date");
|
||||
if (att_string) {
|
||||
std::string date( att_string );
|
||||
ret += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " @ ";
|
||||
ret += date.substr(8,2) + ":" + date.substr(10,2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SessionCreator::SessionCreator(int recursion): SessionLoader(nullptr, recursion)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SessionCreator::load(const std::string& filename)
|
||||
{
|
||||
XMLError eResult = xmlDoc_.LoadFile(filename.c_str());
|
||||
if ( XMLResultError(eResult)){
|
||||
Log::Warning("%s could not be openned.", filename.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
XMLElement *header = xmlDoc_.FirstChildElement(APP_NAME);
|
||||
if (header == nullptr) {
|
||||
Log::Warning("%s is not a %s session file.", filename.c_str(), APP_NAME);
|
||||
return;
|
||||
}
|
||||
|
||||
int version_major = -1, version_minor = -1;
|
||||
header->QueryIntAttribute("major", &version_major);
|
||||
header->QueryIntAttribute("minor", &version_minor);
|
||||
if (version_major != XML_VERSION_MAJOR || version_minor != XML_VERSION_MINOR){
|
||||
Log::Warning("%s session file is in version v%d.%d. but this vimix program expects v%d.%d.\n"
|
||||
"Loading might fail or lead to different or incomplete configuration.\n"
|
||||
"You can save this session again to avoid this warning.",
|
||||
filename.c_str(), version_major, version_minor, XML_VERSION_MAJOR, XML_VERSION_MINOR);
|
||||
// return;
|
||||
}
|
||||
|
||||
// session file seems legit, create a session
|
||||
session_ = new Session;
|
||||
|
||||
// load views config (includes resolution of session rendering)
|
||||
loadConfig( xmlDoc_.FirstChildElement("Views") );
|
||||
|
||||
// ready to read sources
|
||||
SessionLoader::load( xmlDoc_.FirstChildElement("Session") );
|
||||
|
||||
// create groups
|
||||
std::list< SourceList > groups = getMixingGroups();
|
||||
for (auto group_it = groups.begin(); group_it != groups.end(); group_it++)
|
||||
session_->link( *group_it );
|
||||
|
||||
// all good
|
||||
session_->setFilename(filename);
|
||||
}
|
||||
|
||||
|
||||
void SessionCreator::loadConfig(XMLElement *viewsNode)
|
||||
{
|
||||
if (viewsNode != nullptr) {
|
||||
// ok, ready to read views
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Mixing"), *session_->config(View::MIXING));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Geometry"), *session_->config(View::GEOMETRY));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Layer"), *session_->config(View::LAYER));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Texture"), *session_->config(View::TEXTURE));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Rendering"), *session_->config(View::RENDERING));
|
||||
}
|
||||
}
|
||||
|
||||
SessionLoader::SessionLoader(Session *session, int recursion): Visitor(),
|
||||
session_(session), xmlCurrent_(nullptr), recursion_(recursion)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::map< uint64_t, Source* > SessionLoader::getSources() const
|
||||
{
|
||||
return sources_id_;
|
||||
}
|
||||
|
||||
// groups_sources_id_ is parsed in XML and contains list of groups of ids
|
||||
// Here we return the list of groups of newly created sources
|
||||
// based on correspondance map sources_id_
|
||||
// NB: importantly the list is cleared from duplicates
|
||||
std::list< SourceList > SessionLoader::getMixingGroups() const
|
||||
{
|
||||
std::list< SourceList > groups_new_sources_id;
|
||||
|
||||
// perform conversion from xml id to new id
|
||||
for (auto git = groups_sources_id_.begin(); git != groups_sources_id_.end(); git++)
|
||||
{
|
||||
SourceList new_sources;
|
||||
for (auto sit = (*git).begin(); sit != (*git).end(); sit++ ) {
|
||||
if (sources_id_.count(*sit) > 0)
|
||||
new_sources.push_back( sources_id_.at(*sit) );
|
||||
}
|
||||
new_sources.sort();
|
||||
groups_new_sources_id.push_back( new_sources );
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
groups_new_sources_id.unique();
|
||||
|
||||
return groups_new_sources_id;
|
||||
}
|
||||
|
||||
void SessionLoader::load(XMLElement *sessionNode)
|
||||
{
|
||||
sources_id_.clear();
|
||||
|
||||
if (recursion_ > MAX_SESSION_LEVEL) {
|
||||
Log::Warning("Recursive or imbricated sessions detected! Interrupting loading after %d iterations.\n", MAX_SESSION_LEVEL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionNode != nullptr && session_ != nullptr) {
|
||||
|
||||
XMLElement* sourceNode = sessionNode->FirstChildElement("Source");
|
||||
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
|
||||
{
|
||||
xmlCurrent_ = sourceNode;
|
||||
|
||||
// source to load
|
||||
Source *load_source = nullptr;
|
||||
|
||||
// check if a source with the given id exists in the session
|
||||
uint64_t id_xml_ = 0;
|
||||
xmlCurrent_->QueryUnsigned64Attribute("id", &id_xml_);
|
||||
SourceList::iterator sit = session_->find(id_xml_);
|
||||
|
||||
// no source with this id exists
|
||||
if ( sit == session_->end() ) {
|
||||
// create a new source depending on type
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if (!pType)
|
||||
continue;
|
||||
if ( std::string(pType) == "MediaSource") {
|
||||
load_source = new MediaSource;
|
||||
}
|
||||
else if ( std::string(pType) == "SessionSource") {
|
||||
load_source = new SessionFileSource;
|
||||
}
|
||||
else if ( std::string(pType) == "GroupSource") {
|
||||
load_source = new SessionGroupSource;
|
||||
}
|
||||
else if ( std::string(pType) == "RenderSource") {
|
||||
load_source = new RenderSource;
|
||||
}
|
||||
else if ( std::string(pType) == "PatternSource") {
|
||||
load_source = new PatternSource;
|
||||
}
|
||||
else if ( std::string(pType) == "DeviceSource") {
|
||||
load_source = new DeviceSource;
|
||||
}
|
||||
else if ( std::string(pType) == "NetworkSource") {
|
||||
load_source = new NetworkSource;
|
||||
}
|
||||
|
||||
// skip failed (including clones)
|
||||
if (!load_source)
|
||||
continue;
|
||||
|
||||
// add source to session
|
||||
session_->addSource(load_source);
|
||||
}
|
||||
// get reference to the existing source
|
||||
else
|
||||
load_source = *sit;
|
||||
|
||||
// apply config to source
|
||||
load_source->accept(*this);
|
||||
load_source->touch();
|
||||
|
||||
// remember
|
||||
sources_id_[id_xml_] = load_source;
|
||||
}
|
||||
|
||||
// create clones after all sources, to be able to clone a source created above
|
||||
sourceNode = sessionNode->FirstChildElement("Source");
|
||||
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
|
||||
{
|
||||
xmlCurrent_ = sourceNode;
|
||||
|
||||
// verify type of node
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if ( pType && std::string(pType) == "CloneSource") {
|
||||
|
||||
// check if a source with same id exists
|
||||
uint64_t id_xml_ = 0;
|
||||
xmlCurrent_->QueryUnsigned64Attribute("id", &id_xml_);
|
||||
SourceList::iterator sit = session_->find(id_xml_);
|
||||
|
||||
// no source clone with this id exists
|
||||
if ( sit == session_->end() ) {
|
||||
|
||||
// clone from given origin
|
||||
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
|
||||
if (originNode) {
|
||||
std::string sourcename = std::string ( originNode->GetText() );
|
||||
SourceList::iterator origin = session_->find(sourcename);
|
||||
// found the orign source
|
||||
if (origin != session_->end()) {
|
||||
// create a new source of type Clone
|
||||
Source *clone_source = (*origin)->clone();
|
||||
|
||||
// add source to session
|
||||
session_->addSource(clone_source);
|
||||
|
||||
// apply config to source
|
||||
clone_source->accept(*this);
|
||||
clone_source->touch();
|
||||
|
||||
// remember
|
||||
sources_id_[id_xml_] = clone_source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, bool clone_duplicates)
|
||||
{
|
||||
xmlCurrent_ = sourceNode;
|
||||
|
||||
// source to load
|
||||
Source *load_source = nullptr;
|
||||
bool is_clone = false;
|
||||
|
||||
SourceList::iterator sit = session_->end();
|
||||
// check if a source with the given id exists in the session
|
||||
if (clone_duplicates) {
|
||||
uint64_t id__ = 0;
|
||||
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
|
||||
sit = session_->find(id__);
|
||||
}
|
||||
|
||||
// no source with this id exists
|
||||
if ( sit == session_->end() ) {
|
||||
// create a new source depending on type
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if (pType) {
|
||||
if ( std::string(pType) == "MediaSource") {
|
||||
load_source = new MediaSource;
|
||||
}
|
||||
else if ( std::string(pType) == "SessionSource") {
|
||||
load_source = new SessionFileSource;
|
||||
}
|
||||
else if ( std::string(pType) == "GroupSource") {
|
||||
load_source = new SessionGroupSource;
|
||||
}
|
||||
else if ( std::string(pType) == "RenderSource") {
|
||||
load_source = new RenderSource;
|
||||
}
|
||||
else if ( std::string(pType) == "PatternSource") {
|
||||
load_source = new PatternSource;
|
||||
}
|
||||
else if ( std::string(pType) == "DeviceSource") {
|
||||
load_source = new DeviceSource;
|
||||
}
|
||||
else if ( std::string(pType) == "NetworkSource") {
|
||||
load_source = new NetworkSource;
|
||||
}
|
||||
else if ( std::string(pType) == "CloneSource") {
|
||||
// clone from given origin
|
||||
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
|
||||
if (originNode) {
|
||||
std::string sourcename = std::string ( originNode->GetText() );
|
||||
SourceList::iterator origin = session_->find(sourcename);
|
||||
// found the orign source
|
||||
if (origin != session_->end())
|
||||
load_source = (*origin)->clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// clone existing source
|
||||
else {
|
||||
load_source = (*sit)->clone();
|
||||
is_clone = true;
|
||||
}
|
||||
|
||||
// apply config to source
|
||||
if (load_source) {
|
||||
load_source->accept(*this);
|
||||
// increment depth for clones (avoid supperposition)
|
||||
if (is_clone)
|
||||
load_source->group(View::LAYER)->translation_.z += 0.2f;
|
||||
}
|
||||
|
||||
return load_source;
|
||||
}
|
||||
|
||||
|
||||
void SessionLoader::XMLToNode(tinyxml2::XMLElement *xml, Node &n)
|
||||
{
|
||||
if (xml != nullptr){
|
||||
XMLElement *node = xml->FirstChildElement("Node");
|
||||
if ( !node || std::string(node->Name()).find("Node") == std::string::npos )
|
||||
return;
|
||||
|
||||
XMLElement *scaleNode = node->FirstChildElement("scale");
|
||||
if (scaleNode)
|
||||
tinyxml2::XMLElementToGLM( scaleNode->FirstChildElement("vec3"), n.scale_);
|
||||
XMLElement *translationNode = node->FirstChildElement("translation");
|
||||
if (translationNode)
|
||||
tinyxml2::XMLElementToGLM( translationNode->FirstChildElement("vec3"), n.translation_);
|
||||
XMLElement *rotationNode = node->FirstChildElement("rotation");
|
||||
if (rotationNode)
|
||||
tinyxml2::XMLElementToGLM( rotationNode->FirstChildElement("vec3"), n.rotation_);
|
||||
XMLElement *cropNode = node->FirstChildElement("crop");
|
||||
if (cropNode)
|
||||
tinyxml2::XMLElementToGLM( cropNode->FirstChildElement("vec3"), n.crop_);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLoader::visit(Node &n)
|
||||
{
|
||||
XMLToNode(xmlCurrent_, n);
|
||||
}
|
||||
|
||||
void SessionLoader::visit(MediaPlayer &n)
|
||||
{
|
||||
XMLElement* mediaplayerNode = xmlCurrent_->FirstChildElement("MediaPlayer");
|
||||
|
||||
if (mediaplayerNode) {
|
||||
uint64_t id__ = -1;
|
||||
mediaplayerNode->QueryUnsigned64Attribute("id", &id__);
|
||||
|
||||
// timeline
|
||||
XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline");
|
||||
if (timelineelement) {
|
||||
Timeline tl;
|
||||
tl.setTiming( n.timeline()->interval(), n.timeline()->step());
|
||||
XMLElement *gapselement = timelineelement->FirstChildElement("Gaps");
|
||||
if (gapselement) {
|
||||
XMLElement* gap = gapselement->FirstChildElement("Interval");
|
||||
for( ; gap ; gap = gap->NextSiblingElement())
|
||||
{
|
||||
GstClockTime a = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime b = GST_CLOCK_TIME_NONE;
|
||||
gap->QueryUnsigned64Attribute("begin", &a);
|
||||
gap->QueryUnsigned64Attribute("end", &b);
|
||||
tl.addGap( a, b );
|
||||
}
|
||||
}
|
||||
XMLElement *fadingselement = timelineelement->FirstChildElement("Fading");
|
||||
if (fadingselement) {
|
||||
XMLElement* array = fadingselement->FirstChildElement("array");
|
||||
XMLElementDecodeArray(array, tl.fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
}
|
||||
n.setTimeline(tl);
|
||||
}
|
||||
|
||||
// change play status only if different id (e.g. new media player)
|
||||
if ( n.id() != id__ ) {
|
||||
|
||||
double speed = 1.0;
|
||||
mediaplayerNode->QueryDoubleAttribute("speed", &speed);
|
||||
n.setPlaySpeed(speed);
|
||||
|
||||
int loop = 1;
|
||||
mediaplayerNode->QueryIntAttribute("loop", &loop);
|
||||
n.setLoop( (MediaPlayer::LoopMode) loop);
|
||||
|
||||
bool play = true;
|
||||
mediaplayerNode->QueryBoolAttribute("play", &play);
|
||||
n.play(play);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLoader::visit(Shader &n)
|
||||
{
|
||||
XMLElement* color = xmlCurrent_->FirstChildElement("color");
|
||||
if ( color ) {
|
||||
tinyxml2::XMLElementToGLM( color->FirstChildElement("vec4"), n.color);
|
||||
XMLElement* blending = xmlCurrent_->FirstChildElement("blending");
|
||||
if (blending) {
|
||||
int blend = 0;
|
||||
blending->QueryIntAttribute("mode", &blend);
|
||||
n.blending = (Shader::BlendMode) blend;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLoader::visit(ImageShader &n)
|
||||
{
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if ( std::string(pType) != "ImageShader" )
|
||||
return;
|
||||
|
||||
XMLElement* uniforms = xmlCurrent_->FirstChildElement("uniforms");
|
||||
if (uniforms) {
|
||||
uniforms->QueryFloatAttribute("stipple", &n.stipple);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLoader::visit(MaskShader &n)
|
||||
{
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if ( std::string(pType) != "MaskShader" )
|
||||
return;
|
||||
|
||||
xmlCurrent_->QueryUnsignedAttribute("mode", &n.mode);
|
||||
xmlCurrent_->QueryUnsignedAttribute("shape", &n.shape);
|
||||
|
||||
XMLElement* uniforms = xmlCurrent_->FirstChildElement("uniforms");
|
||||
if (uniforms) {
|
||||
uniforms->QueryFloatAttribute("blur", &n.blur);
|
||||
uniforms->QueryIntAttribute("option", &n.option);
|
||||
XMLElement* size = uniforms->FirstChildElement("size");
|
||||
if (size)
|
||||
tinyxml2::XMLElementToGLM( size->FirstChildElement("vec2"), n.size);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLoader::visit(ImageProcessingShader &n)
|
||||
{
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if ( std::string(pType) != "ImageProcessingShader" )
|
||||
return;
|
||||
|
||||
XMLElement* uniforms = xmlCurrent_->FirstChildElement("uniforms");
|
||||
if (uniforms) {
|
||||
uniforms->QueryFloatAttribute("brightness", &n.brightness);
|
||||
uniforms->QueryFloatAttribute("contrast", &n.contrast);
|
||||
uniforms->QueryFloatAttribute("saturation", &n.saturation);
|
||||
uniforms->QueryFloatAttribute("hueshift", &n.hueshift);
|
||||
uniforms->QueryFloatAttribute("threshold", &n.threshold);
|
||||
uniforms->QueryFloatAttribute("lumakey", &n.lumakey);
|
||||
uniforms->QueryIntAttribute("nbColors", &n.nbColors);
|
||||
uniforms->QueryIntAttribute("invert", &n.invert);
|
||||
uniforms->QueryFloatAttribute("chromadelta", &n.chromadelta);
|
||||
uniforms->QueryIntAttribute("filter", &n.filterid);
|
||||
}
|
||||
|
||||
XMLElement* gamma = xmlCurrent_->FirstChildElement("gamma");
|
||||
if (gamma)
|
||||
tinyxml2::XMLElementToGLM( gamma->FirstChildElement("vec4"), n.gamma);
|
||||
XMLElement* levels = xmlCurrent_->FirstChildElement("levels");
|
||||
if (levels)
|
||||
tinyxml2::XMLElementToGLM( levels->FirstChildElement("vec4"), n.levels);
|
||||
XMLElement* chromakey = xmlCurrent_->FirstChildElement("chromakey");
|
||||
if (chromakey)
|
||||
tinyxml2::XMLElementToGLM( chromakey->FirstChildElement("vec4"), n.chromakey);
|
||||
}
|
||||
|
||||
void SessionLoader::visit (Source& s)
|
||||
{
|
||||
XMLElement* sourceNode = xmlCurrent_;
|
||||
const char *pName = sourceNode->Attribute("name");
|
||||
s.setName(pName);
|
||||
bool l = false;
|
||||
sourceNode->QueryBoolAttribute("locked", &l);
|
||||
s.setLocked(l);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Mixing");
|
||||
if (xmlCurrent_) s.groupNode(View::MIXING)->accept(*this);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Geometry");
|
||||
if (xmlCurrent_) s.groupNode(View::GEOMETRY)->accept(*this);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Layer");
|
||||
if (xmlCurrent_) s.groupNode(View::LAYER)->accept(*this);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Texture");
|
||||
if (xmlCurrent_) {
|
||||
s.groupNode(View::TEXTURE)->accept(*this);
|
||||
bool m = true;
|
||||
xmlCurrent_->QueryBoolAttribute("mirrored", &m);
|
||||
s.setTextureMirrored(m);
|
||||
}
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Blending");
|
||||
if (xmlCurrent_) s.blendingShader()->accept(*this);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Mask");
|
||||
if (xmlCurrent_) {
|
||||
// read the mask shader attributes
|
||||
s.maskShader()->accept(*this);
|
||||
// if there is an Image mask stored
|
||||
XMLElement* imageNode = xmlCurrent_->FirstChildElement("Image");
|
||||
if (imageNode) {
|
||||
// if there is an internal array of data
|
||||
XMLElement* array = imageNode->FirstChildElement("array");
|
||||
if (array) {
|
||||
// create a temporary jpeg with size of the array
|
||||
FrameBufferImage::jpegBuffer jpgimg;
|
||||
array->QueryUnsignedAttribute("len", &jpgimg.len);
|
||||
// ok, we got a size of data to load
|
||||
if (jpgimg.len>0) {
|
||||
// allocate jpeg buffer
|
||||
jpgimg.buffer = (unsigned char*) malloc(jpgimg.len);
|
||||
// actual decoding of array
|
||||
if (XMLElementDecodeArray(array, jpgimg.buffer, jpgimg.len) )
|
||||
// create and set the image from jpeg
|
||||
s.setMask(new FrameBufferImage(jpgimg));
|
||||
// free temporary buffer
|
||||
if (jpgimg.buffer)
|
||||
free(jpgimg.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("ImageProcessing");
|
||||
if (xmlCurrent_) {
|
||||
bool on = xmlCurrent_->BoolAttribute("enabled", true);
|
||||
s.processingShader()->accept(*this);
|
||||
s.setImageProcessingEnabled(on);
|
||||
}
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("MixingGroup");
|
||||
if (xmlCurrent_) {
|
||||
SourceIdList idlist;
|
||||
XMLElement* sourceNode = xmlCurrent_->FirstChildElement("source");
|
||||
for ( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement()) {
|
||||
uint64_t id__ = 0;
|
||||
sourceNode->QueryUnsigned64Attribute("id", &id__);
|
||||
idlist.push_back(id__);
|
||||
}
|
||||
groups_sources_id_.push_back(idlist);
|
||||
}
|
||||
|
||||
// restore current
|
||||
xmlCurrent_ = sourceNode;
|
||||
}
|
||||
|
||||
void SessionLoader::visit (MediaSource& s)
|
||||
{
|
||||
// set uri
|
||||
XMLElement* uriNode = xmlCurrent_->FirstChildElement("uri");
|
||||
if (uriNode) {
|
||||
std::string uri = std::string ( uriNode->GetText() );
|
||||
// load only new files
|
||||
if ( uri != s.path() )
|
||||
s.setPath(uri);
|
||||
}
|
||||
|
||||
// set config media player
|
||||
s.mediaplayer()->accept(*this);
|
||||
}
|
||||
|
||||
void SessionLoader::visit (SessionFileSource& s)
|
||||
{
|
||||
// set fading
|
||||
float f = 0.f;
|
||||
xmlCurrent_->QueryFloatAttribute("fading", &f);
|
||||
s.session()->setFading(f);
|
||||
// set uri
|
||||
XMLElement* pathNode = xmlCurrent_->FirstChildElement("path");
|
||||
if (pathNode) {
|
||||
std::string path = std::string ( pathNode->GetText() );
|
||||
// load only new files
|
||||
if ( path != s.path() )
|
||||
s.load(path, recursion_ + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SessionLoader::visit (SessionGroupSource& s)
|
||||
{
|
||||
// set resolution from host session
|
||||
s.setResolution( session_->config(View::RENDERING)->scale_ );
|
||||
|
||||
// get the inside session
|
||||
XMLElement* sessionGroupNode = xmlCurrent_->FirstChildElement("Session");
|
||||
if (sessionGroupNode) {
|
||||
// only parse if newly created
|
||||
if (s.session()->empty()) {
|
||||
// load session inside group
|
||||
SessionLoader grouploader( s.session(), recursion_ + 1 );
|
||||
grouploader.load( sessionGroupNode );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLoader::visit (RenderSource& s)
|
||||
{
|
||||
s.setSession( session_ );
|
||||
}
|
||||
|
||||
void SessionLoader::visit (PatternSource& s)
|
||||
{
|
||||
uint t = xmlCurrent_->UnsignedAttribute("pattern");
|
||||
|
||||
glm::ivec2 resolution(800, 600);
|
||||
XMLElement* res = xmlCurrent_->FirstChildElement("resolution");
|
||||
if (res)
|
||||
tinyxml2::XMLElementToGLM( res->FirstChildElement("ivec2"), resolution);
|
||||
|
||||
// change only if different pattern
|
||||
if ( t != s.pattern()->type() )
|
||||
s.setPattern(t, resolution);
|
||||
}
|
||||
|
||||
void SessionLoader::visit (DeviceSource& s)
|
||||
{
|
||||
std::string devname = std::string ( xmlCurrent_->Attribute("device") );
|
||||
|
||||
// change only if different device
|
||||
if ( devname != s.device() )
|
||||
s.setDevice(devname);
|
||||
}
|
||||
|
||||
|
||||
void SessionLoader::visit (NetworkSource& s)
|
||||
{
|
||||
std::string connect = std::string ( xmlCurrent_->Attribute("connection") );
|
||||
|
||||
// change only if different device
|
||||
if ( connect != s.connection() )
|
||||
s.setConnection(connect);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#ifndef SESSIONCREATOR_H
|
||||
#define SESSIONCREATOR_H
|
||||
|
||||
#include <map>
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include "Visitor.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
class Session;
|
||||
|
||||
|
||||
class SessionLoader : public Visitor {
|
||||
|
||||
public:
|
||||
|
||||
SessionLoader(Session *session, int recursion = 0);
|
||||
inline Session *session() const { return session_; }
|
||||
|
||||
void load(tinyxml2::XMLElement *sessionNode);
|
||||
std::map< uint64_t, Source* > getSources() const;
|
||||
std::list< SourceList > getMixingGroups() const;
|
||||
|
||||
Source *createSource(tinyxml2::XMLElement *sourceNode, bool clone_duplicates = true);
|
||||
|
||||
// Elements of Scene
|
||||
void visit (Node& n) override;
|
||||
void visit (Scene&) override {}
|
||||
void visit (Group&) override {}
|
||||
void visit (Switch&) override {}
|
||||
void visit (Primitive&) override {}
|
||||
|
||||
// Elements with attributes
|
||||
void visit (MediaPlayer& n) override;
|
||||
void visit (Shader& n) override;
|
||||
void visit (ImageShader& n) override;
|
||||
void visit (MaskShader& n) override;
|
||||
void visit (ImageProcessingShader& n) override;
|
||||
|
||||
// Sources
|
||||
void visit (Source& s) override;
|
||||
void visit (MediaSource& s) override;
|
||||
void visit (SessionFileSource& s) override;
|
||||
void visit (SessionGroupSource& s) override;
|
||||
void visit (RenderSource& s) override;
|
||||
void visit (PatternSource& s) override;
|
||||
void visit (DeviceSource& s) override;
|
||||
void visit (NetworkSource& s) override;
|
||||
|
||||
protected:
|
||||
// result created session
|
||||
Session *session_;
|
||||
// parsing current xml
|
||||
tinyxml2::XMLElement *xmlCurrent_;
|
||||
// level of loading recursion
|
||||
int recursion_;
|
||||
// map of correspondance from xml source id (key) to new source pointer (value)
|
||||
std::map< uint64_t, Source* > sources_id_;
|
||||
// list of groups (lists of xml source id)
|
||||
std::list< SourceIdList > groups_sources_id_;
|
||||
|
||||
static void XMLToNode(tinyxml2::XMLElement *xml, Node &n);
|
||||
};
|
||||
|
||||
class SessionCreator : public SessionLoader {
|
||||
|
||||
tinyxml2::XMLDocument xmlDoc_;
|
||||
|
||||
void loadConfig(tinyxml2::XMLElement *viewsNode);
|
||||
|
||||
public:
|
||||
SessionCreator(int recursion = 0);
|
||||
|
||||
void load(const std::string& filename);
|
||||
|
||||
static std::string info(const std::string& filename);
|
||||
};
|
||||
|
||||
#endif // SESSIONCREATOR_H
|
||||
@@ -1,386 +0,0 @@
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#include "SessionSource.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Decorations.h"
|
||||
#include "SearchVisitor.h"
|
||||
#include "Session.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "Mixer.h"
|
||||
|
||||
|
||||
SessionSource::SessionSource() : Source()
|
||||
{
|
||||
failed_ = false;
|
||||
session_ = new Session;
|
||||
}
|
||||
|
||||
SessionSource::~SessionSource()
|
||||
{
|
||||
// delete session
|
||||
if (session_)
|
||||
delete session_;
|
||||
}
|
||||
|
||||
Session *SessionSource::detach()
|
||||
{
|
||||
// remember pointer to give away
|
||||
Session *giveaway = session_;
|
||||
|
||||
// work on a new session
|
||||
session_ = new Session;
|
||||
|
||||
// make disabled
|
||||
initialized_ = false;
|
||||
|
||||
// ask to delete me
|
||||
failed_ = true;
|
||||
|
||||
// lost ref to previous session: to be deleted elsewhere...
|
||||
return giveaway;
|
||||
}
|
||||
|
||||
bool SessionSource::failed() const
|
||||
{
|
||||
return failed_;
|
||||
}
|
||||
|
||||
uint SessionSource::texture() const
|
||||
{
|
||||
if (session_ && session_->frame())
|
||||
return session_->frame()->texture();
|
||||
else
|
||||
return Resource::getTextureBlack();
|
||||
}
|
||||
|
||||
void SessionSource::setActive (bool on)
|
||||
{
|
||||
Source::setActive(on);
|
||||
|
||||
// change status of session (recursive change of internal sources)
|
||||
if (session_ != nullptr)
|
||||
session_->setActive(active_);
|
||||
}
|
||||
|
||||
void SessionSource::update(float dt)
|
||||
{
|
||||
if (session_ == nullptr)
|
||||
return;
|
||||
|
||||
// update content
|
||||
if (active_)
|
||||
session_->update(dt);
|
||||
|
||||
// delete a source which failed
|
||||
if (session_->failedSource() != nullptr) {
|
||||
session_->deleteSource(session_->failedSource());
|
||||
// fail session if all sources failed
|
||||
if ( session_->numSource() < 1)
|
||||
failed_ = true;
|
||||
}
|
||||
|
||||
Source::update(dt);
|
||||
}
|
||||
|
||||
|
||||
SessionFileSource::SessionFileSource() : SessionSource(), path_("")
|
||||
{
|
||||
// specific node for transition view
|
||||
groups_[View::TRANSITION]->visible_ = false;
|
||||
groups_[View::TRANSITION]->scale_ = glm::vec3(0.1f, 0.1f, 1.f);
|
||||
groups_[View::TRANSITION]->translation_ = glm::vec3(-1.f, 0.f, 0.f);
|
||||
|
||||
frames_[View::TRANSITION] = new Switch;
|
||||
Frame *frame = new Frame(Frame::ROUND, Frame::THIN, Frame::DROP);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
frames_[View::TRANSITION]->attach(frame);
|
||||
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::DROP);
|
||||
frame->translation_.z = 0.01;
|
||||
frame->color = glm::vec4( COLOR_TRANSITION_SOURCE, 1.f);
|
||||
frames_[View::TRANSITION]->attach(frame);
|
||||
groups_[View::TRANSITION]->attach(frames_[View::TRANSITION]);
|
||||
|
||||
overlays_[View::TRANSITION] = new Group;
|
||||
overlays_[View::TRANSITION]->translation_.z = 0.1;
|
||||
overlays_[View::TRANSITION]->visible_ = false;
|
||||
|
||||
Symbol *loader = new Symbol(Symbol::DOTS);
|
||||
loader->scale_ = glm::vec3(2.f, 2.f, 1.f);
|
||||
loader->update_callbacks_.push_back(new InfiniteGlowCallback);
|
||||
overlays_[View::TRANSITION]->attach(loader);
|
||||
Symbol *center = new Symbol(Symbol::CIRCLE_POINT, glm::vec3(0.f, -1.05f, 0.1f));
|
||||
overlays_[View::TRANSITION]->attach(center);
|
||||
groups_[View::TRANSITION]->attach(overlays_[View::TRANSITION]);
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::SESSION, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
|
||||
wait_for_sources_ = false;
|
||||
}
|
||||
|
||||
void SessionFileSource::load(const std::string &p, uint recursion)
|
||||
{
|
||||
path_ = p;
|
||||
|
||||
// delete session
|
||||
if (session_) {
|
||||
delete session_;
|
||||
session_ = nullptr;
|
||||
}
|
||||
|
||||
// init session
|
||||
if ( path_.empty() ) {
|
||||
// empty session
|
||||
session_ = new Session;
|
||||
Log::Warning("Empty Session filename provided.");
|
||||
}
|
||||
else {
|
||||
// launch a thread to load the session file
|
||||
sessionLoader_ = std::async(std::launch::async, Session::load, path_, recursion);
|
||||
Log::Notify("Opening %s", p.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void SessionFileSource::init()
|
||||
{
|
||||
// init is first about getting the loaded session
|
||||
if (session_ == nullptr) {
|
||||
// did the loader finish ?
|
||||
if (sessionLoader_.wait_for(std::chrono::milliseconds(4)) == std::future_status::ready) {
|
||||
session_ = sessionLoader_.get();
|
||||
if (session_ == nullptr)
|
||||
failed_ = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
session_->update(dt_);
|
||||
|
||||
if (wait_for_sources_) {
|
||||
|
||||
// force update of of all sources
|
||||
active_ = true;
|
||||
touch();
|
||||
|
||||
// check that every source is ready..
|
||||
bool ready = true;
|
||||
for (SourceList::iterator iter = session_->begin(); iter != session_->end(); iter++)
|
||||
{
|
||||
// interrupt if any source is NOT ready
|
||||
if ( !(*iter)->ready() ){
|
||||
ready = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if all sources are ready, done with initialization!
|
||||
if (ready) {
|
||||
// done init
|
||||
wait_for_sources_ = false;
|
||||
initialized_ = true;
|
||||
Log::Info("Source Session %s loaded %d sources.", path_.c_str(), session_->numSource());
|
||||
}
|
||||
}
|
||||
else if ( !failed_ ) {
|
||||
|
||||
// set resolution
|
||||
session_->setResolution( session_->config(View::RENDERING)->scale_ );
|
||||
|
||||
// update to draw framebuffer
|
||||
session_->update(dt_);
|
||||
|
||||
// get the texture index from framebuffer of session, apply it to the surface
|
||||
texturesurface_->setTextureIndex( session_->frame()->texture() );
|
||||
|
||||
// create Frame buffer matching size of session
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( session_->frame()->resolution() );
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// wait for all sources to init
|
||||
if (session_->numSource() > 0)
|
||||
wait_for_sources_ = true;
|
||||
else {
|
||||
initialized_ = true;
|
||||
Log::Info("New Session created (%d x %d).", renderbuffer->width(), renderbuffer->height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (initialized_)
|
||||
{
|
||||
// remove the loading icon
|
||||
Node *loader = overlays_[View::TRANSITION]->back();
|
||||
overlays_[View::TRANSITION]->detach(loader);
|
||||
delete loader;
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
}
|
||||
}
|
||||
|
||||
void SessionFileSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
SessionGroupSource::SessionGroupSource() : SessionSource(), resolution_(glm::vec3(0.f))
|
||||
{
|
||||
// // redo frame for layers view
|
||||
// frames_[View::LAYER]->clear();
|
||||
|
||||
// // Groups in LAYER have an additional border
|
||||
// Group *group = new Group;
|
||||
// Frame *frame = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
|
||||
// frame->translation_.z = 0.1;
|
||||
// frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
// group->attach(frame);
|
||||
// Frame *persp = new Frame(Frame::GROUP, Frame::THIN, Frame::NONE);
|
||||
// persp->translation_.z = 0.1;
|
||||
// persp->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
// group->attach(persp);
|
||||
// frames_[View::LAYER]->attach(group);
|
||||
|
||||
// group = new Group;
|
||||
// frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::PERSPECTIVE);
|
||||
// frame->translation_.z = 0.1;
|
||||
// frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
// group->attach(frame);
|
||||
// persp = new Frame(Frame::GROUP, Frame::LARGE, Frame::NONE);
|
||||
// persp->translation_.z = 0.1;
|
||||
// persp->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
// group->attach(persp);
|
||||
// frames_[View::LAYER]->attach(group);
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::GROUP, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
void SessionGroupSource::init()
|
||||
{
|
||||
if ( resolution_.x > 0.f && resolution_.y > 0.f ) {
|
||||
|
||||
session_->setResolution( resolution_ );
|
||||
|
||||
// update to draw framebuffer
|
||||
session_->update( dt_ );
|
||||
|
||||
// get the texture index from framebuffer of session, apply it to the surface
|
||||
texturesurface_->setTextureIndex( session_->frame()->texture() );
|
||||
|
||||
// create Frame buffer matching size of session
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( session_->frame()->resolution() );
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
Log::Info("Source Group (%d x %d).", int(renderbuffer->resolution().x), int(renderbuffer->resolution().y) );
|
||||
}
|
||||
}
|
||||
|
||||
bool SessionGroupSource::import(Source *source)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if ( session_ )
|
||||
{
|
||||
SourceList::iterator its = session_->addSource(source);
|
||||
if (its != session_->end())
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SessionGroupSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
RenderSource::RenderSource() : Source(), session_(nullptr)
|
||||
{
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::RENDER, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
|
||||
bool RenderSource::failed() const
|
||||
{
|
||||
if (initialized_ && session_!=nullptr)
|
||||
return renderbuffer_->resolution() != session_->frame()->resolution();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint RenderSource::texture() const
|
||||
{
|
||||
if (session_ && session_->frame())
|
||||
return session_->frame()->texture();
|
||||
else
|
||||
return Resource::getTextureBlack(); // getTextureTransparent ?
|
||||
}
|
||||
|
||||
void RenderSource::init()
|
||||
{
|
||||
if (session_ && session_->frame() && session_->frame()->texture() != Resource::getTextureBlack()) {
|
||||
|
||||
FrameBuffer *fb = session_->frame();
|
||||
|
||||
// get the texture index from framebuffer of view, apply it to the surface
|
||||
texturesurface_->setTextureIndex( fb->texture() );
|
||||
|
||||
// create Frame buffer matching size of output session
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( fb->resolution() );
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
Log::Info("Source Render linked to session (%d x %d).", int(fb->resolution().x), int(fb->resolution().y) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
glm::vec3 RenderSource::resolution() const
|
||||
{
|
||||
if (initialized_)
|
||||
return renderbuffer_->resolution();
|
||||
else if (session_ && session_->frame())
|
||||
return session_->frame()->resolution();
|
||||
else
|
||||
return glm::vec3(0.f);
|
||||
}
|
||||
|
||||
void RenderSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
// if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
#include "SessionVisitor.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "defines.h"
|
||||
#include "Scene.h"
|
||||
#include "Decorations.h"
|
||||
#include "Source.h"
|
||||
#include "MediaSource.h"
|
||||
#include "Session.h"
|
||||
#include "SessionSource.h"
|
||||
#include "PatternSource.h"
|
||||
#include "DeviceSource.h"
|
||||
#include "NetworkSource.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "MixingGroup.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
using namespace tinyxml2;
|
||||
|
||||
|
||||
bool SessionVisitor::saveSession(const std::string& filename, Session *session)
|
||||
{
|
||||
// creation of XML doc
|
||||
XMLDocument xmlDoc;
|
||||
|
||||
XMLElement *rootnode = xmlDoc.NewElement(APP_NAME);
|
||||
rootnode->SetAttribute("major", XML_VERSION_MAJOR);
|
||||
rootnode->SetAttribute("minor", XML_VERSION_MINOR);
|
||||
rootnode->SetAttribute("size", session->numSource());
|
||||
rootnode->SetAttribute("date", SystemToolkit::date_time_string().c_str());
|
||||
rootnode->SetAttribute("resolution", session->frame()->info().c_str());
|
||||
xmlDoc.InsertEndChild(rootnode);
|
||||
|
||||
// 1. list of sources
|
||||
XMLElement *sessionNode = xmlDoc.NewElement("Session");
|
||||
xmlDoc.InsertEndChild(sessionNode);
|
||||
SessionVisitor sv(&xmlDoc, sessionNode);
|
||||
for (auto iter = session->begin(); iter != session->end(); iter++, sv.setRoot(sessionNode) )
|
||||
// source visitor
|
||||
(*iter)->accept(sv);
|
||||
|
||||
// 2. config of views
|
||||
XMLElement *views = xmlDoc.NewElement("Views");
|
||||
xmlDoc.InsertEndChild(views);
|
||||
{
|
||||
XMLElement *mixing = xmlDoc.NewElement( "Mixing" );
|
||||
mixing->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::MIXING), &xmlDoc));
|
||||
views->InsertEndChild(mixing);
|
||||
|
||||
XMLElement *geometry = xmlDoc.NewElement( "Geometry" );
|
||||
geometry->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::GEOMETRY), &xmlDoc));
|
||||
views->InsertEndChild(geometry);
|
||||
|
||||
XMLElement *layer = xmlDoc.NewElement( "Layer" );
|
||||
layer->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::LAYER), &xmlDoc));
|
||||
views->InsertEndChild(layer);
|
||||
|
||||
XMLElement *appearance = xmlDoc.NewElement( "Texture" );
|
||||
appearance->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::TEXTURE), &xmlDoc));
|
||||
views->InsertEndChild(appearance);
|
||||
|
||||
XMLElement *render = xmlDoc.NewElement( "Rendering" );
|
||||
render->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::RENDERING), &xmlDoc));
|
||||
views->InsertEndChild(render);
|
||||
}
|
||||
|
||||
// save file to disk
|
||||
return ( XMLSaveDoc(&xmlDoc, filename) );
|
||||
}
|
||||
|
||||
SessionVisitor::SessionVisitor(tinyxml2::XMLDocument *doc,
|
||||
tinyxml2::XMLElement *root,
|
||||
bool recursive) : Visitor(), recursive_(recursive), xmlCurrent_(root)
|
||||
{
|
||||
if (doc == nullptr)
|
||||
xmlDoc_ = new XMLDocument;
|
||||
else
|
||||
xmlDoc_ = doc;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument *doc)
|
||||
{
|
||||
XMLElement *newelement = doc->NewElement("Node");
|
||||
newelement->SetAttribute("visible", n.visible_);
|
||||
newelement->SetAttribute("id", n.id());
|
||||
|
||||
XMLElement *scale = doc->NewElement("scale");
|
||||
scale->InsertEndChild( XMLElementFromGLM(doc, n.scale_) );
|
||||
newelement->InsertEndChild(scale);
|
||||
|
||||
XMLElement *translation = doc->NewElement("translation");
|
||||
translation->InsertEndChild( XMLElementFromGLM(doc, n.translation_) );
|
||||
newelement->InsertEndChild(translation);
|
||||
|
||||
XMLElement *rotation = doc->NewElement("rotation");
|
||||
rotation->InsertEndChild( XMLElementFromGLM(doc, n.rotation_) );
|
||||
newelement->InsertEndChild(rotation);
|
||||
|
||||
XMLElement *crop = doc->NewElement("crop");
|
||||
crop->InsertEndChild( XMLElementFromGLM(doc, n.crop_) );
|
||||
newelement->InsertEndChild(crop);
|
||||
|
||||
return newelement;
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Node &n)
|
||||
{
|
||||
XMLElement *newelement = NodeToXML(n, xmlDoc_);
|
||||
|
||||
// insert into hierarchy
|
||||
xmlCurrent_->InsertEndChild(newelement);
|
||||
|
||||
// parent for next visits
|
||||
xmlCurrent_ = newelement;
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Group &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "Group");
|
||||
|
||||
if (recursive_) {
|
||||
// loop over members of a group
|
||||
XMLElement *group = xmlCurrent_;
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
|
||||
(*node)->accept(*this);
|
||||
// revert to group as current
|
||||
xmlCurrent_ = group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Switch &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "Switch");
|
||||
xmlCurrent_->SetAttribute("active", n.active());
|
||||
|
||||
if (recursive_) {
|
||||
// loop over members of the group
|
||||
XMLElement *group = xmlCurrent_;
|
||||
for(uint i = 0; i < n.numChildren(); i++) {
|
||||
n.child(i)->accept(*this);
|
||||
// revert to group as current
|
||||
xmlCurrent_ = group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Primitive &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "Primitive");
|
||||
|
||||
if (recursive_) {
|
||||
// go over members of a primitive
|
||||
XMLElement *Primitive = xmlCurrent_;
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement("Shader");
|
||||
n.shader()->accept(*this);
|
||||
Primitive->InsertEndChild(xmlCurrent_);
|
||||
|
||||
// revert to primitive as current
|
||||
xmlCurrent_ = Primitive;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SessionVisitor::visit(Surface &)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(ImageSurface &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "ImageSurface");
|
||||
|
||||
XMLText *filename = xmlDoc_->NewText( n.resource().c_str() );
|
||||
XMLElement *image = xmlDoc_->NewElement("resource");
|
||||
image->InsertEndChild(filename);
|
||||
xmlCurrent_->InsertEndChild(image);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(FrameBufferSurface &)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "FrameBufferSurface");
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(MediaSurface &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "MediaSurface");
|
||||
|
||||
n.mediaPlayer()->accept(*this);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(MediaPlayer &n)
|
||||
{
|
||||
XMLElement *newelement = xmlDoc_->NewElement("MediaPlayer");
|
||||
newelement->SetAttribute("id", n.id());
|
||||
|
||||
if (!n.isImage()) {
|
||||
newelement->SetAttribute("play", n.isPlaying());
|
||||
newelement->SetAttribute("loop", (int) n.loop());
|
||||
newelement->SetAttribute("speed", n.playSpeed());
|
||||
|
||||
// timeline
|
||||
XMLElement *timelineelement = xmlDoc_->NewElement("Timeline");
|
||||
|
||||
// gaps in timeline
|
||||
XMLElement *gapselement = xmlDoc_->NewElement("Gaps");
|
||||
TimeIntervalSet gaps = n.timeline()->gaps();
|
||||
for( auto it = gaps.begin(); it!= gaps.end(); it++) {
|
||||
XMLElement *g = xmlDoc_->NewElement("Interval");
|
||||
g->SetAttribute("begin", (*it).begin);
|
||||
g->SetAttribute("end", (*it).end);
|
||||
gapselement->InsertEndChild(g);
|
||||
}
|
||||
timelineelement->InsertEndChild(gapselement);
|
||||
|
||||
// fading in timeline
|
||||
XMLElement *fadingelement = xmlDoc_->NewElement("Fading");
|
||||
XMLElement *array = XMLElementEncodeArray(xmlDoc_, n.timeline()->fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
fadingelement->InsertEndChild(array);
|
||||
timelineelement->InsertEndChild(fadingelement);
|
||||
newelement->InsertEndChild(timelineelement);
|
||||
}
|
||||
|
||||
xmlCurrent_->InsertEndChild(newelement);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Shader &n)
|
||||
{
|
||||
// Shader of a simple type
|
||||
xmlCurrent_->SetAttribute("type", "Shader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
|
||||
XMLElement *color = xmlDoc_->NewElement("color");
|
||||
color->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.color) );
|
||||
xmlCurrent_->InsertEndChild(color);
|
||||
|
||||
XMLElement *blend = xmlDoc_->NewElement("blending");
|
||||
blend->SetAttribute("mode", int(n.blending) );
|
||||
xmlCurrent_->InsertEndChild(blend);
|
||||
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(ImageShader &n)
|
||||
{
|
||||
// Shader of a textured type
|
||||
xmlCurrent_->SetAttribute("type", "ImageShader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
|
||||
XMLElement *uniforms = xmlDoc_->NewElement("uniforms");
|
||||
uniforms->SetAttribute("stipple", n.stipple);
|
||||
xmlCurrent_->InsertEndChild(uniforms);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(MaskShader &n)
|
||||
{
|
||||
// Shader of a mask type
|
||||
xmlCurrent_->SetAttribute("type", "MaskShader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
xmlCurrent_->SetAttribute("mode", n.mode);
|
||||
xmlCurrent_->SetAttribute("shape", n.shape);
|
||||
|
||||
XMLElement *uniforms = xmlDoc_->NewElement("uniforms");
|
||||
uniforms->SetAttribute("blur", n.blur);
|
||||
uniforms->SetAttribute("option", n.option);
|
||||
XMLElement *size = xmlDoc_->NewElement("size");
|
||||
size->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.size) );
|
||||
uniforms->InsertEndChild(size);
|
||||
xmlCurrent_->InsertEndChild(uniforms);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(ImageProcessingShader &n)
|
||||
{
|
||||
// Shader of a textured type
|
||||
xmlCurrent_->SetAttribute("type", "ImageProcessingShader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
|
||||
XMLElement *filter = xmlDoc_->NewElement("uniforms");
|
||||
filter->SetAttribute("brightness", n.brightness);
|
||||
filter->SetAttribute("contrast", n.contrast);
|
||||
filter->SetAttribute("saturation", n.saturation);
|
||||
filter->SetAttribute("hueshift", n.hueshift);
|
||||
filter->SetAttribute("threshold", n.threshold);
|
||||
filter->SetAttribute("lumakey", n.lumakey);
|
||||
filter->SetAttribute("nbColors", n.nbColors);
|
||||
filter->SetAttribute("invert", n.invert);
|
||||
filter->SetAttribute("chromadelta", n.chromadelta);
|
||||
filter->SetAttribute("filter", n.filterid);
|
||||
xmlCurrent_->InsertEndChild(filter);
|
||||
|
||||
XMLElement *gamma = xmlDoc_->NewElement("gamma");
|
||||
gamma->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.gamma) );
|
||||
xmlCurrent_->InsertEndChild(gamma);
|
||||
|
||||
XMLElement *levels = xmlDoc_->NewElement("levels");
|
||||
levels->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.levels) );
|
||||
xmlCurrent_->InsertEndChild(levels);
|
||||
|
||||
XMLElement *chromakey = xmlDoc_->NewElement("chromakey");
|
||||
chromakey->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.chromakey) );
|
||||
xmlCurrent_->InsertEndChild(chromakey);
|
||||
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(LineStrip &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "LineStrip");
|
||||
|
||||
XMLElement *points_node = xmlDoc_->NewElement("points");
|
||||
std::vector<glm::vec2> path = n.path();
|
||||
for(size_t i = 0; i < path.size(); ++i)
|
||||
{
|
||||
XMLElement *p = XMLElementFromGLM(xmlDoc_, path[i]);
|
||||
p->SetAttribute("index", (int) i);
|
||||
points_node->InsertEndChild(p);
|
||||
}
|
||||
xmlCurrent_->InsertEndChild(points_node);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(LineSquare &)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "LineSquare");
|
||||
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Mesh &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "Mesh");
|
||||
|
||||
XMLText *filename = xmlDoc_->NewText( n.meshPath().c_str() );
|
||||
XMLElement *obj = xmlDoc_->NewElement("resource");
|
||||
obj->InsertEndChild(filename);
|
||||
xmlCurrent_->InsertEndChild(obj);
|
||||
|
||||
filename = xmlDoc_->NewText( n.texturePath().c_str() );
|
||||
XMLElement *tex = xmlDoc_->NewElement("texture");
|
||||
tex->InsertEndChild(filename);
|
||||
xmlCurrent_->InsertEndChild(tex);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Frame &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "Frame");
|
||||
|
||||
XMLElement *color = xmlDoc_->NewElement("color");
|
||||
color->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.color) );
|
||||
xmlCurrent_->InsertEndChild(color);
|
||||
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Scene &n)
|
||||
{
|
||||
XMLElement *xmlRoot = xmlDoc_->NewElement("Scene");
|
||||
xmlDoc_->InsertEndChild(xmlRoot);
|
||||
|
||||
// start recursive traverse from root node
|
||||
recursive_ = true;
|
||||
xmlCurrent_ = xmlRoot;
|
||||
n.root()->accept(*this);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (Source& s)
|
||||
{
|
||||
XMLElement *sourceNode = xmlDoc_->NewElement( "Source" );
|
||||
sourceNode->SetAttribute("id", s.id());
|
||||
sourceNode->SetAttribute("name", s.name().c_str() );
|
||||
sourceNode->SetAttribute("locked", s.locked() );
|
||||
|
||||
// insert into hierarchy
|
||||
xmlCurrent_->InsertFirstChild(sourceNode);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Mixing" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.groupNode(View::MIXING)->accept(*this);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Geometry" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.groupNode(View::GEOMETRY)->accept(*this);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Layer" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.groupNode(View::LAYER)->accept(*this);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Texture" );
|
||||
xmlCurrent_->SetAttribute("mirrored", s.textureMirrored() );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.groupNode(View::TEXTURE)->accept(*this);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Blending" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.blendingShader()->accept(*this);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Mask" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.maskShader()->accept(*this);
|
||||
// if we are saving a pain mask
|
||||
if (s.maskShader()->mode == MaskShader::PAINT) {
|
||||
// get the mask previously stored
|
||||
FrameBufferImage *img = s.getMask();
|
||||
if (img != nullptr) {
|
||||
// get the jpeg encoded buffer
|
||||
FrameBufferImage::jpegBuffer jpgimg = img->getJpeg();
|
||||
if (jpgimg.buffer != nullptr) {
|
||||
// fill the xml array with jpeg buffer
|
||||
XMLElement *array = XMLElementEncodeArray(xmlDoc_, jpgimg.buffer, jpgimg.len);
|
||||
// free the buffer
|
||||
free(jpgimg.buffer);
|
||||
// if we could create the array
|
||||
if (array) {
|
||||
// create an Image node to store the mask image
|
||||
XMLElement *imageelement = xmlDoc_->NewElement("Image");
|
||||
imageelement->InsertEndChild(array);
|
||||
xmlCurrent_->InsertEndChild(imageelement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "ImageProcessing" );
|
||||
xmlCurrent_->SetAttribute("enabled", s.imageProcessingEnabled());
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.processingShader()->accept(*this);
|
||||
|
||||
if (s.mixingGroup()) {
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "MixingGroup" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.mixingGroup()->accept(*this);
|
||||
}
|
||||
|
||||
xmlCurrent_ = sourceNode; // parent for next visits (other subtypes of Source)
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (MediaSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "MediaSource");
|
||||
|
||||
XMLElement *uri = xmlDoc_->NewElement("uri");
|
||||
xmlCurrent_->InsertEndChild(uri);
|
||||
XMLText *text = xmlDoc_->NewText( s.path().c_str() );
|
||||
uri->InsertEndChild( text );
|
||||
|
||||
s.mediaplayer()->accept(*this);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (SessionFileSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "SessionSource");
|
||||
if (s.session() != nullptr)
|
||||
xmlCurrent_->SetAttribute("fading", s.session()->fading());
|
||||
|
||||
XMLElement *path = xmlDoc_->NewElement("path");
|
||||
xmlCurrent_->InsertEndChild(path);
|
||||
XMLText *text = xmlDoc_->NewText( s.path().c_str() );
|
||||
path->InsertEndChild( text );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (SessionGroupSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "GroupSource");
|
||||
|
||||
Session *se = s.session();
|
||||
|
||||
XMLElement *sessionNode = xmlDoc_->NewElement("Session");
|
||||
xmlCurrent_->InsertEndChild(sessionNode);
|
||||
|
||||
for (auto iter = se->begin(); iter != se->end(); iter++){
|
||||
setRoot(sessionNode);
|
||||
(*iter)->accept(*this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (RenderSource&)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "RenderSource");
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (CloneSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "CloneSource");
|
||||
|
||||
XMLElement *origin = xmlDoc_->NewElement("origin");
|
||||
xmlCurrent_->InsertEndChild(origin);
|
||||
XMLText *text = xmlDoc_->NewText( s.origin()->name().c_str() );
|
||||
origin->InsertEndChild( text );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (PatternSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "PatternSource");
|
||||
xmlCurrent_->SetAttribute("pattern", s.pattern()->type() );
|
||||
|
||||
XMLElement *resolution = xmlDoc_->NewElement("resolution");
|
||||
resolution->InsertEndChild( XMLElementFromGLM(xmlDoc_, s.pattern()->resolution() ) );
|
||||
xmlCurrent_->InsertEndChild(resolution);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (DeviceSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "DeviceSource");
|
||||
xmlCurrent_->SetAttribute("device", s.device().c_str() );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (NetworkSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "NetworkSource");
|
||||
xmlCurrent_->SetAttribute("connection", s.connection().c_str() );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (MixingGroup& g)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("size", g.size());
|
||||
|
||||
for (auto it = g.begin(); it != g.end(); it++) {
|
||||
XMLElement *sour = xmlDoc_->NewElement("source");
|
||||
sour->SetAttribute("id", (*it)->id());
|
||||
xmlCurrent_->InsertEndChild(sour);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
#ifndef XMLVISITOR_H
|
||||
#define XMLVISITOR_H
|
||||
|
||||
#include "Visitor.h"
|
||||
#include "tinyxml2Toolkit.h"
|
||||
|
||||
class Session;
|
||||
|
||||
class SessionVisitor : public Visitor {
|
||||
|
||||
bool recursive_;
|
||||
tinyxml2::XMLDocument *xmlDoc_;
|
||||
tinyxml2::XMLElement *xmlCurrent_;
|
||||
|
||||
public:
|
||||
SessionVisitor(tinyxml2::XMLDocument *doc = nullptr,
|
||||
tinyxml2::XMLElement *root = nullptr,
|
||||
bool recursive = false);
|
||||
|
||||
inline void setRoot(tinyxml2::XMLElement *root) { xmlCurrent_ = root; }
|
||||
|
||||
static bool saveSession(const std::string& filename, Session *session);
|
||||
|
||||
// Elements of Scene
|
||||
void visit(Scene& n) override;
|
||||
void visit(Node& n) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
void visit(Primitive& n) override;
|
||||
void visit(Surface&) override;
|
||||
void visit(ImageSurface& n) override;
|
||||
void visit(MediaSurface& n) override;
|
||||
void visit(FrameBufferSurface&) override;
|
||||
void visit(LineStrip& n) override;
|
||||
void visit(LineSquare&) override;
|
||||
void visit(Mesh& n) override;
|
||||
void visit(Frame& n) override;
|
||||
|
||||
// Elements with attributes
|
||||
void visit(MediaPlayer& n) override;
|
||||
void visit(Shader& n) override;
|
||||
void visit(ImageShader& n) override;
|
||||
void visit(MaskShader& n) override;
|
||||
void visit(ImageProcessingShader& n) override;
|
||||
|
||||
// Sources
|
||||
void visit (Source& s) override;
|
||||
void visit (MediaSource& s) override;
|
||||
void visit (SessionFileSource& s) override;
|
||||
void visit (SessionGroupSource& s) override;
|
||||
void visit (RenderSource&) override;
|
||||
void visit (CloneSource& s) override;
|
||||
void visit (PatternSource& s) override;
|
||||
void visit (DeviceSource& s) override;
|
||||
void visit (NetworkSource& s) override;
|
||||
void visit (MixingGroup& s) override;
|
||||
|
||||
protected:
|
||||
static tinyxml2::XMLElement *NodeToXML(Node &n, tinyxml2::XMLDocument *doc);
|
||||
};
|
||||
|
||||
#endif // XMLVISITOR_H
|
||||
500
Settings.cpp
@@ -1,500 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
using namespace tinyxml2;
|
||||
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
|
||||
Settings::Application Settings::application;
|
||||
|
||||
static string settingsFilename = "";
|
||||
|
||||
void Settings::Save()
|
||||
{
|
||||
XMLDocument xmlDoc;
|
||||
XMLDeclaration *pDec = xmlDoc.NewDeclaration();
|
||||
xmlDoc.InsertFirstChild(pDec);
|
||||
|
||||
XMLElement *pRoot = xmlDoc.NewElement(application.name.c_str());
|
||||
#ifdef VIMIX_VERSION_MAJOR
|
||||
pRoot->SetAttribute("major", VIMIX_VERSION_MAJOR);
|
||||
pRoot->SetAttribute("minor", VIMIX_VERSION_MINOR);
|
||||
xmlDoc.InsertEndChild(pRoot);
|
||||
#endif
|
||||
|
||||
string comment = "Settings for " + application.name;
|
||||
XMLComment *pComment = xmlDoc.NewComment(comment.c_str());
|
||||
pRoot->InsertEndChild(pComment);
|
||||
|
||||
// block: windows
|
||||
{
|
||||
XMLElement *windowsNode = xmlDoc.NewElement( "Windows" );
|
||||
|
||||
for (int i = 0; i < application.windows.size(); i++)
|
||||
{
|
||||
const Settings::WindowConfig& w = application.windows[i];
|
||||
|
||||
XMLElement *window = xmlDoc.NewElement( "Window" );
|
||||
window->SetAttribute("id", i);
|
||||
window->SetAttribute("name", w.name.c_str());
|
||||
window->SetAttribute("x", w.x);
|
||||
window->SetAttribute("y", w.y);
|
||||
window->SetAttribute("w", w.w);
|
||||
window->SetAttribute("h", w.h);
|
||||
window->SetAttribute("f", w.fullscreen);
|
||||
window->SetAttribute("m", w.monitor.c_str());
|
||||
windowsNode->InsertEndChild(window);
|
||||
}
|
||||
|
||||
pRoot->InsertEndChild(windowsNode);
|
||||
}
|
||||
|
||||
// General application preferences
|
||||
XMLElement *applicationNode = xmlDoc.NewElement( "Application" );
|
||||
applicationNode->SetAttribute("scale", application.scale);
|
||||
applicationNode->SetAttribute("accent_color", application.accent_color);
|
||||
applicationNode->SetAttribute("pannel_stick", application.pannel_stick);
|
||||
applicationNode->SetAttribute("smooth_transition", application.smooth_transition);
|
||||
applicationNode->SetAttribute("smooth_cursor", application.smooth_cursor);
|
||||
applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view);
|
||||
applicationNode->SetAttribute("accept_connections", application.accept_connections);
|
||||
pRoot->InsertEndChild(applicationNode);
|
||||
|
||||
// Widgets
|
||||
XMLElement *widgetsNode = xmlDoc.NewElement( "Widgets" );
|
||||
widgetsNode->SetAttribute("preview", application.widget.preview);
|
||||
widgetsNode->SetAttribute("history", application.widget.history);
|
||||
widgetsNode->SetAttribute("media_player", application.widget.media_player);
|
||||
widgetsNode->SetAttribute("shader_editor", application.widget.shader_editor);
|
||||
widgetsNode->SetAttribute("stats", application.widget.stats);
|
||||
widgetsNode->SetAttribute("stats_timer", application.widget.stats_timer);
|
||||
widgetsNode->SetAttribute("stats_corner", application.widget.stats_corner);
|
||||
widgetsNode->SetAttribute("logs", application.widget.logs);
|
||||
widgetsNode->SetAttribute("toolbox", application.widget.toolbox);
|
||||
pRoot->InsertEndChild(widgetsNode);
|
||||
|
||||
// Render
|
||||
XMLElement *RenderNode = xmlDoc.NewElement( "Render" );
|
||||
RenderNode->SetAttribute("vsync", application.render.vsync);
|
||||
RenderNode->SetAttribute("multisampling", application.render.multisampling);
|
||||
RenderNode->SetAttribute("blit", application.render.blit);
|
||||
RenderNode->SetAttribute("gpu_decoding", application.render.gpu_decoding);
|
||||
RenderNode->SetAttribute("ratio", application.render.ratio);
|
||||
RenderNode->SetAttribute("res", application.render.res);
|
||||
pRoot->InsertEndChild(RenderNode);
|
||||
|
||||
// Record
|
||||
XMLElement *RecordNode = xmlDoc.NewElement( "Record" );
|
||||
RecordNode->SetAttribute("path", application.record.path.c_str());
|
||||
RecordNode->SetAttribute("profile", application.record.profile);
|
||||
RecordNode->SetAttribute("timeout", application.record.timeout);
|
||||
pRoot->InsertEndChild(RecordNode);
|
||||
|
||||
// Transition
|
||||
XMLElement *TransitionNode = xmlDoc.NewElement( "Transition" );
|
||||
TransitionNode->SetAttribute("auto_open", application.transition.auto_open);
|
||||
TransitionNode->SetAttribute("hide_windows", application.transition.hide_windows);
|
||||
TransitionNode->SetAttribute("cross_fade", application.transition.cross_fade);
|
||||
TransitionNode->SetAttribute("duration", application.transition.duration);
|
||||
TransitionNode->SetAttribute("profile", application.transition.profile);
|
||||
pRoot->InsertEndChild(TransitionNode);
|
||||
|
||||
// Source
|
||||
XMLElement *SourceConfNode = xmlDoc.NewElement( "Source" );
|
||||
SourceConfNode->SetAttribute("new_type", application.source.new_type);
|
||||
SourceConfNode->SetAttribute("ratio", application.source.ratio);
|
||||
SourceConfNode->SetAttribute("res", application.source.res);
|
||||
pRoot->InsertEndChild(SourceConfNode);
|
||||
|
||||
// Brush
|
||||
XMLElement *BrushNode = xmlDoc.NewElement( "Brush" );
|
||||
BrushNode->InsertEndChild( XMLElementFromGLM(&xmlDoc, application.brush) );
|
||||
pRoot->InsertEndChild(BrushNode);
|
||||
|
||||
// bloc connections
|
||||
{
|
||||
XMLElement *connectionsNode = xmlDoc.NewElement( "Connections" );
|
||||
|
||||
// map<int, std::string>::iterator iter;
|
||||
// for (iter=application.instance_names.begin(); iter != application.instance_names.end(); iter++)
|
||||
// {
|
||||
// XMLElement *connection = xmlDoc.NewElement( "Instance" );
|
||||
// connection->SetAttribute("name", iter->second.c_str());
|
||||
// connection->SetAttribute("id", iter->first);
|
||||
// connectionsNode->InsertEndChild(connection);
|
||||
// }
|
||||
pRoot->InsertEndChild(connectionsNode);
|
||||
}
|
||||
|
||||
// bloc views
|
||||
{
|
||||
XMLElement *viewsNode = xmlDoc.NewElement( "Views" );
|
||||
// save current view only if [mixing, geometry, layers, appearance]
|
||||
int v = application.current_view > 4 ? 1 : application.current_view;
|
||||
viewsNode->SetAttribute("current", v);
|
||||
viewsNode->SetAttribute("workspace", application.current_workspace);
|
||||
|
||||
map<int, Settings::ViewConfig>::iterator iter;
|
||||
for (iter=application.views.begin(); iter != application.views.end(); iter++)
|
||||
{
|
||||
const Settings::ViewConfig& v = iter->second;
|
||||
|
||||
XMLElement *view = xmlDoc.NewElement( "View" );
|
||||
view->SetAttribute("name", v.name.c_str());
|
||||
view->SetAttribute("id", iter->first);
|
||||
|
||||
XMLElement *scale = xmlDoc.NewElement("default_scale");
|
||||
scale->InsertEndChild( XMLElementFromGLM(&xmlDoc, v.default_scale) );
|
||||
view->InsertEndChild(scale);
|
||||
XMLElement *translation = xmlDoc.NewElement("default_translation");
|
||||
translation->InsertEndChild( XMLElementFromGLM(&xmlDoc, v.default_translation) );
|
||||
view->InsertEndChild(translation);
|
||||
|
||||
viewsNode->InsertEndChild(view);
|
||||
}
|
||||
|
||||
pRoot->InsertEndChild(viewsNode);
|
||||
}
|
||||
|
||||
// bloc history
|
||||
{
|
||||
XMLElement *recent = xmlDoc.NewElement( "Recent" );
|
||||
|
||||
XMLElement *recentsession = xmlDoc.NewElement( "Session" );
|
||||
recentsession->SetAttribute("path", application.recentSessions.path.c_str());
|
||||
recentsession->SetAttribute("autoload", application.recentSessions.load_at_start);
|
||||
recentsession->SetAttribute("autosave", application.recentSessions.save_on_exit);
|
||||
recentsession->SetAttribute("valid", application.recentSessions.front_is_valid);
|
||||
for(auto it = application.recentSessions.filenames.begin();
|
||||
it != application.recentSessions.filenames.end(); it++) {
|
||||
XMLElement *fileNode = xmlDoc.NewElement("path");
|
||||
XMLText *text = xmlDoc.NewText( (*it).c_str() );
|
||||
fileNode->InsertEndChild( text );
|
||||
recentsession->InsertFirstChild(fileNode);
|
||||
};
|
||||
recent->InsertEndChild(recentsession);
|
||||
|
||||
XMLElement *recentfolder = xmlDoc.NewElement( "Folder" );
|
||||
for(auto it = application.recentFolders.filenames.begin();
|
||||
it != application.recentFolders.filenames.end(); it++) {
|
||||
XMLElement *fileNode = xmlDoc.NewElement("path");
|
||||
XMLText *text = xmlDoc.NewText( (*it).c_str() );
|
||||
fileNode->InsertEndChild( text );
|
||||
recentfolder->InsertFirstChild(fileNode);
|
||||
};
|
||||
recent->InsertEndChild(recentfolder);
|
||||
|
||||
XMLElement *recentmedia = xmlDoc.NewElement( "Import" );
|
||||
recentmedia->SetAttribute("path", application.recentImport.path.c_str());
|
||||
for(auto it = application.recentImport.filenames.begin();
|
||||
it != application.recentImport.filenames.end(); it++) {
|
||||
XMLElement *fileNode = xmlDoc.NewElement("path");
|
||||
XMLText *text = xmlDoc.NewText( (*it).c_str() );
|
||||
fileNode->InsertEndChild( text );
|
||||
recentmedia->InsertFirstChild(fileNode);
|
||||
}
|
||||
recent->InsertEndChild(recentmedia);
|
||||
|
||||
pRoot->InsertEndChild(recent);
|
||||
}
|
||||
|
||||
|
||||
// First save : create filename
|
||||
if (settingsFilename.empty())
|
||||
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
|
||||
|
||||
XMLError eResult = xmlDoc.SaveFile(settingsFilename.c_str());
|
||||
XMLResultError(eResult);
|
||||
|
||||
}
|
||||
|
||||
void Settings::Load()
|
||||
{
|
||||
XMLDocument xmlDoc;
|
||||
if (settingsFilename.empty())
|
||||
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
|
||||
XMLError eResult = xmlDoc.LoadFile(settingsFilename.c_str());
|
||||
|
||||
// do not warn if non existing file
|
||||
if (eResult == XML_ERROR_FILE_NOT_FOUND)
|
||||
return;
|
||||
// warn and return on other error
|
||||
else if (XMLResultError(eResult))
|
||||
return;
|
||||
|
||||
XMLElement *pRoot = xmlDoc.FirstChildElement(application.name.c_str());
|
||||
if (pRoot == nullptr) return;
|
||||
|
||||
// cancel on different root name
|
||||
if (application.name.compare( string( pRoot->Value() ) ) != 0 )
|
||||
return;
|
||||
|
||||
#ifdef VIMIX_VERSION_MAJOR
|
||||
// cancel on different version
|
||||
int version_major = -1, version_minor = -1;
|
||||
pRoot->QueryIntAttribute("major", &version_major);
|
||||
pRoot->QueryIntAttribute("minor", &version_minor);
|
||||
if (version_major != VIMIX_VERSION_MAJOR || version_minor != VIMIX_VERSION_MINOR)
|
||||
return;
|
||||
#endif
|
||||
|
||||
XMLElement * applicationNode = pRoot->FirstChildElement("Application");
|
||||
if (applicationNode != nullptr) {
|
||||
applicationNode->QueryFloatAttribute("scale", &application.scale);
|
||||
applicationNode->QueryIntAttribute("accent_color", &application.accent_color);
|
||||
applicationNode->QueryBoolAttribute("pannel_stick", &application.pannel_stick);
|
||||
applicationNode->QueryBoolAttribute("smooth_transition", &application.smooth_transition);
|
||||
applicationNode->QueryBoolAttribute("smooth_cursor", &application.smooth_cursor);
|
||||
applicationNode->QueryBoolAttribute("action_history_follow_view", &application.action_history_follow_view);
|
||||
applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections);
|
||||
}
|
||||
|
||||
// Widgets
|
||||
XMLElement * widgetsNode = pRoot->FirstChildElement("Widgets");
|
||||
if (widgetsNode != nullptr) {
|
||||
widgetsNode->QueryBoolAttribute("preview", &application.widget.preview);
|
||||
widgetsNode->QueryBoolAttribute("history", &application.widget.history);
|
||||
widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player);
|
||||
widgetsNode->QueryBoolAttribute("shader_editor", &application.widget.shader_editor);
|
||||
widgetsNode->QueryBoolAttribute("stats", &application.widget.stats);
|
||||
widgetsNode->QueryBoolAttribute("stats_timer", &application.widget.stats_timer);
|
||||
widgetsNode->QueryIntAttribute("stats_corner", &application.widget.stats_corner);
|
||||
widgetsNode->QueryBoolAttribute("logs", &application.widget.logs);
|
||||
widgetsNode->QueryBoolAttribute("toolbox", &application.widget.toolbox);
|
||||
}
|
||||
|
||||
// Render
|
||||
XMLElement * rendernode = pRoot->FirstChildElement("Render");
|
||||
if (rendernode != nullptr) {
|
||||
rendernode->QueryIntAttribute("vsync", &application.render.vsync);
|
||||
rendernode->QueryIntAttribute("multisampling", &application.render.multisampling);
|
||||
rendernode->QueryBoolAttribute("blit", &application.render.blit);
|
||||
rendernode->QueryBoolAttribute("gpu_decoding", &application.render.gpu_decoding);
|
||||
rendernode->QueryIntAttribute("ratio", &application.render.ratio);
|
||||
rendernode->QueryIntAttribute("res", &application.render.res);
|
||||
}
|
||||
|
||||
// Record
|
||||
XMLElement * recordnode = pRoot->FirstChildElement("Record");
|
||||
if (recordnode != nullptr) {
|
||||
recordnode->QueryIntAttribute("profile", &application.record.profile);
|
||||
recordnode->QueryFloatAttribute("timeout", &application.record.timeout);
|
||||
|
||||
const char *path_ = recordnode->Attribute("path");
|
||||
if (path_)
|
||||
application.record.path = std::string(path_);
|
||||
else
|
||||
application.record.path = SystemToolkit::home_path();
|
||||
}
|
||||
|
||||
// Source
|
||||
XMLElement * sourceconfnode = pRoot->FirstChildElement("Source");
|
||||
if (sourceconfnode != nullptr) {
|
||||
sourceconfnode->QueryIntAttribute("new_type", &application.source.new_type);
|
||||
sourceconfnode->QueryIntAttribute("ratio", &application.source.ratio);
|
||||
sourceconfnode->QueryIntAttribute("res", &application.source.res);
|
||||
}
|
||||
|
||||
// Transition
|
||||
XMLElement * transitionnode = pRoot->FirstChildElement("Transition");
|
||||
if (transitionnode != nullptr) {
|
||||
transitionnode->QueryBoolAttribute("hide_windows", &application.transition.hide_windows);
|
||||
transitionnode->QueryBoolAttribute("auto_open", &application.transition.auto_open);
|
||||
transitionnode->QueryBoolAttribute("cross_fade", &application.transition.cross_fade);
|
||||
transitionnode->QueryFloatAttribute("duration", &application.transition.duration);
|
||||
transitionnode->QueryIntAttribute("profile", &application.transition.profile);
|
||||
}
|
||||
|
||||
// bloc windows
|
||||
{
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Windows");
|
||||
if (pElement)
|
||||
{
|
||||
XMLElement* windowNode = pElement->FirstChildElement("Window");
|
||||
for( ; windowNode ; windowNode=windowNode->NextSiblingElement())
|
||||
{
|
||||
Settings::WindowConfig w;
|
||||
w.name = std::string(windowNode->Attribute("name"));
|
||||
windowNode->QueryIntAttribute("x", &w.x); // If this fails, original value is left as-is
|
||||
windowNode->QueryIntAttribute("y", &w.y);
|
||||
windowNode->QueryIntAttribute("w", &w.w);
|
||||
windowNode->QueryIntAttribute("h", &w.h);
|
||||
windowNode->QueryBoolAttribute("f", &w.fullscreen);
|
||||
w.monitor = std::string(windowNode->Attribute("m"));
|
||||
|
||||
int i = 0;
|
||||
windowNode->QueryIntAttribute("id", &i);
|
||||
application.windows[i] = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Brush
|
||||
XMLElement * brushnode = pRoot->FirstChildElement("Brush");
|
||||
if (brushnode != nullptr) {
|
||||
tinyxml2::XMLElementToGLM( brushnode->FirstChildElement("vec3"), application.brush);
|
||||
}
|
||||
|
||||
// bloc views
|
||||
{
|
||||
application.views.clear(); // trash existing list
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Views");
|
||||
if (pElement)
|
||||
{
|
||||
pElement->QueryIntAttribute("current", &application.current_view);
|
||||
pElement->QueryIntAttribute("workspace", &application.current_workspace);
|
||||
|
||||
XMLElement* viewNode = pElement->FirstChildElement("View");
|
||||
for( ; viewNode ; viewNode=viewNode->NextSiblingElement())
|
||||
{
|
||||
int id = 0;
|
||||
viewNode->QueryIntAttribute("id", &id);
|
||||
application.views[id].name = viewNode->Attribute("name");
|
||||
|
||||
XMLElement* scaleNode = viewNode->FirstChildElement("default_scale");
|
||||
tinyxml2::XMLElementToGLM( scaleNode->FirstChildElement("vec3"),
|
||||
application.views[id].default_scale);
|
||||
|
||||
XMLElement* translationNode = viewNode->FirstChildElement("default_translation");
|
||||
tinyxml2::XMLElementToGLM( translationNode->FirstChildElement("vec3"),
|
||||
application.views[id].default_translation);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// bloc Connections
|
||||
{
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Connections");
|
||||
if (pElement)
|
||||
{
|
||||
// XMLElement* connectionNode = pElement->FirstChildElement("Instance");
|
||||
// for( ; connectionNode ; connectionNode=connectionNode->NextSiblingElement())
|
||||
// {
|
||||
// int id = 0;
|
||||
// connectionNode->QueryIntAttribute("id", &id);
|
||||
// application.instance_names[id] = connectionNode->Attribute("name");
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// bloc history of recent
|
||||
{
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Recent");
|
||||
if (pElement)
|
||||
{
|
||||
// recent session filenames
|
||||
XMLElement * pSession = pElement->FirstChildElement("Session");
|
||||
if (pSession)
|
||||
{
|
||||
const char *path_ = pSession->Attribute("path");
|
||||
if (path_)
|
||||
application.recentSessions.path = std::string(path_);
|
||||
else
|
||||
application.recentSessions.path = SystemToolkit::home_path();
|
||||
application.recentSessions.filenames.clear();
|
||||
XMLElement* path = pSession->FirstChildElement("path");
|
||||
for( ; path ; path = path->NextSiblingElement())
|
||||
{
|
||||
const char *p = path->GetText();
|
||||
if (p)
|
||||
application.recentSessions.push( std::string (p) );
|
||||
}
|
||||
pSession->QueryBoolAttribute("autoload", &application.recentSessions.load_at_start);
|
||||
pSession->QueryBoolAttribute("autosave", &application.recentSessions.save_on_exit);
|
||||
pSession->QueryBoolAttribute("valid", &application.recentSessions.front_is_valid);
|
||||
}
|
||||
// recent session folders
|
||||
XMLElement * pFolder = pElement->FirstChildElement("Folder");
|
||||
if (pFolder)
|
||||
{
|
||||
application.recentFolders.filenames.clear();
|
||||
XMLElement* path = pFolder->FirstChildElement("path");
|
||||
for( ; path ; path = path->NextSiblingElement())
|
||||
{
|
||||
const char *p = path->GetText();
|
||||
if (p)
|
||||
application.recentFolders.push( std::string (p) );
|
||||
}
|
||||
}
|
||||
// recent media uri
|
||||
XMLElement * pImport = pElement->FirstChildElement("Import");
|
||||
if (pImport)
|
||||
{
|
||||
const char *path_ = pImport->Attribute("path");
|
||||
if (path_)
|
||||
application.recentImport.path = std::string(path_);
|
||||
else
|
||||
application.recentImport.path = SystemToolkit::home_path();
|
||||
application.recentImport.filenames.clear();
|
||||
XMLElement* path = pImport->FirstChildElement("path");
|
||||
for( ; path ; path = path->NextSiblingElement())
|
||||
{
|
||||
const char *p = path->GetText();
|
||||
if (p)
|
||||
application.recentImport.push( std::string (p) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Settings::Lock()
|
||||
{
|
||||
|
||||
std::string lockfile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "lock");
|
||||
application.fresh_start = false;
|
||||
|
||||
FILE *file = fopen(lockfile.c_str(), "r");
|
||||
int l = 0;
|
||||
if (file) {
|
||||
if ( fscanf(file, "%d", &l) < 1)
|
||||
l = 0;
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
// not locked or file not existing
|
||||
if ( l < 1 ) {
|
||||
file = fopen(lockfile.c_str(), "w");
|
||||
if (file) {
|
||||
fprintf(file, "1");
|
||||
fclose(file);
|
||||
}
|
||||
application.fresh_start = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Settings::Unlock()
|
||||
{
|
||||
std::string lockfile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "lock");
|
||||
FILE *file = fopen(lockfile.c_str(), "w");
|
||||
if (file) {
|
||||
fprintf(file, "0");
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Settings::Check()
|
||||
{
|
||||
Settings::Save();
|
||||
|
||||
XMLDocument xmlDoc;
|
||||
XMLError eResult = xmlDoc.LoadFile(settingsFilename.c_str());
|
||||
if (XMLResultError(eResult))
|
||||
return;
|
||||
|
||||
xmlDoc.Print();
|
||||
}
|
||||
|
||||
253
Settings.h
@@ -1,253 +0,0 @@
|
||||
#ifndef __SETTINGS_H_
|
||||
#define __SETTINGS_H_
|
||||
|
||||
#include "defines.h"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace Settings {
|
||||
|
||||
struct WidgetsConfig
|
||||
{
|
||||
bool stats;
|
||||
int stats_corner;
|
||||
bool stats_timer;
|
||||
bool logs;
|
||||
bool preview;
|
||||
bool history;
|
||||
bool media_player;
|
||||
bool media_player_view;
|
||||
bool shader_editor;
|
||||
bool toolbox;
|
||||
|
||||
WidgetsConfig() {
|
||||
stats = false;
|
||||
stats_timer = false;
|
||||
stats_corner = 1;
|
||||
logs = false;
|
||||
preview = false;
|
||||
history = false;
|
||||
media_player = false;
|
||||
media_player_view = true;
|
||||
shader_editor = false;
|
||||
toolbox = false;
|
||||
}
|
||||
};
|
||||
|
||||
struct WindowConfig
|
||||
{
|
||||
std::string name;
|
||||
int x,y,w,h;
|
||||
bool fullscreen;
|
||||
std::string monitor;
|
||||
|
||||
WindowConfig() : name(""), x(15), y(15), w(1280), h(720), fullscreen(false), monitor("") { }
|
||||
|
||||
};
|
||||
|
||||
struct ViewConfig
|
||||
{
|
||||
std::string name;
|
||||
glm::vec3 default_scale;
|
||||
glm::vec3 default_translation;
|
||||
|
||||
ViewConfig() : name("") {
|
||||
default_scale = glm::vec3(1.f);
|
||||
default_translation = glm::vec3(0.f);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#define RECORD_MAX_TIMEOUT 1800.f
|
||||
|
||||
struct RecordConfig
|
||||
{
|
||||
std::string path;
|
||||
int profile;
|
||||
float timeout;
|
||||
|
||||
RecordConfig() : path("") {
|
||||
profile = 0;
|
||||
timeout = RECORD_MAX_TIMEOUT;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct History
|
||||
{
|
||||
std::string path;
|
||||
std::list<std::string> filenames;
|
||||
bool front_is_valid;
|
||||
bool load_at_start;
|
||||
bool save_on_exit;
|
||||
|
||||
History() {
|
||||
path = IMGUI_LABEL_RECENT_FILES;
|
||||
front_is_valid = false;
|
||||
load_at_start = false;
|
||||
save_on_exit = false;
|
||||
}
|
||||
void push(const std::string &filename) {
|
||||
if (filename.empty()) {
|
||||
front_is_valid = false;
|
||||
return;
|
||||
}
|
||||
filenames.remove(filename);
|
||||
filenames.push_front(filename);
|
||||
if (filenames.size() > MAX_RECENT_HISTORY)
|
||||
filenames.pop_back();
|
||||
front_is_valid = true;
|
||||
}
|
||||
void remove(const std::string &filename) {
|
||||
if (filename.empty())
|
||||
return;
|
||||
if (filenames.front() == filename)
|
||||
front_is_valid = false;
|
||||
filenames.remove(filename);
|
||||
}
|
||||
};
|
||||
|
||||
struct TransitionConfig
|
||||
{
|
||||
bool cross_fade;
|
||||
bool auto_open;
|
||||
bool hide_windows;
|
||||
float duration;
|
||||
int profile;
|
||||
|
||||
TransitionConfig() {
|
||||
cross_fade = true;
|
||||
auto_open = true;
|
||||
hide_windows = true;
|
||||
duration = 1.f;
|
||||
profile = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderConfig
|
||||
{
|
||||
bool blit;
|
||||
int vsync;
|
||||
int multisampling;
|
||||
int ratio;
|
||||
int res;
|
||||
float fading;
|
||||
bool gpu_decoding;
|
||||
|
||||
RenderConfig() {
|
||||
blit = false;
|
||||
vsync = 1;
|
||||
multisampling = 2;
|
||||
ratio = 3;
|
||||
res = 1;
|
||||
fading = 0.0;
|
||||
gpu_decoding = true;
|
||||
}
|
||||
};
|
||||
|
||||
struct SourceConfig
|
||||
{
|
||||
int new_type;
|
||||
int ratio;
|
||||
int res;
|
||||
|
||||
SourceConfig() {
|
||||
new_type = 0;
|
||||
ratio = 3;
|
||||
res = 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Application
|
||||
{
|
||||
// instance check
|
||||
bool fresh_start;
|
||||
int instance_id;
|
||||
|
||||
// Verification
|
||||
std::string name;
|
||||
std::string executable;
|
||||
|
||||
// Global settings Application interface
|
||||
float scale;
|
||||
int accent_color;
|
||||
bool pannel_stick;
|
||||
bool smooth_transition;
|
||||
bool smooth_cursor;
|
||||
bool action_history_follow_view;
|
||||
|
||||
// connection settings
|
||||
bool accept_connections;
|
||||
// std::map<int, std::string> instance_names;
|
||||
|
||||
// Settings of widgets
|
||||
WidgetsConfig widget;
|
||||
|
||||
// Settings of Views
|
||||
int current_view;
|
||||
int current_workspace;
|
||||
std::map<int, ViewConfig> views;
|
||||
|
||||
// settings brush texture paint
|
||||
glm::vec3 brush;
|
||||
|
||||
// settings render
|
||||
RenderConfig render;
|
||||
|
||||
// settings exporters
|
||||
RecordConfig record;
|
||||
|
||||
// settings new source
|
||||
SourceConfig source;
|
||||
|
||||
// settings transition
|
||||
TransitionConfig transition;
|
||||
|
||||
// multiple windows handling
|
||||
std::vector<WindowConfig> windows;
|
||||
|
||||
// recent files histories
|
||||
History recentSessions;
|
||||
History recentFolders;
|
||||
History recentImport;
|
||||
|
||||
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
|
||||
scale = 1.f;
|
||||
accent_color = 0;
|
||||
pannel_stick = false;
|
||||
smooth_transition = true;
|
||||
smooth_cursor = false;
|
||||
action_history_follow_view = false;
|
||||
accept_connections = false;
|
||||
current_view = 1;
|
||||
current_workspace= 1;
|
||||
brush = glm::vec3(0.5f, 0.1f, 0.f);
|
||||
windows = std::vector<WindowConfig>(3);
|
||||
windows[0].name = APP_NAME APP_TITLE;
|
||||
windows[0].w = 1600;
|
||||
windows[0].h = 900;
|
||||
windows[1].name = APP_NAME " -- Output";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// minimal implementation of settings
|
||||
// Can be accessed r&w anywhere
|
||||
extern Application application;
|
||||
|
||||
// Save and Load store settings in XML file
|
||||
void Save();
|
||||
void Load();
|
||||
void Lock();
|
||||
void Unlock();
|
||||
void Check();
|
||||
|
||||
}
|
||||
|
||||
#endif /* __SETTINGS_H_ */
|
||||
267
Shader.cpp
@@ -1,267 +0,0 @@
|
||||
#include "Shader.h"
|
||||
#include "Resource.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Log.h"
|
||||
#include "Visitor.h"
|
||||
#include "RenderingManager.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
|
||||
// Globals
|
||||
ShadingProgram *ShadingProgram::currentProgram_ = nullptr;
|
||||
ShadingProgram simpleShadingProgram("shaders/simple.vs", "shaders/simple.fs");
|
||||
|
||||
// Blending presets for matching with Shader::BlendModes:
|
||||
GLenum blending_equation[9] = { GL_FUNC_ADD, // normal
|
||||
GL_FUNC_ADD, // screen
|
||||
GL_FUNC_REVERSE_SUBTRACT, // subtract
|
||||
GL_FUNC_ADD, // multiply
|
||||
GL_FUNC_ADD, // soft light
|
||||
GL_FUNC_ADD, // hard light
|
||||
GL_FUNC_REVERSE_SUBTRACT, // soft subtract
|
||||
GL_MAX, // lighten only
|
||||
GL_FUNC_ADD};
|
||||
GLenum blending_source_function[9] = { GL_ONE, // normal
|
||||
GL_ONE, // screen
|
||||
GL_SRC_COLOR, // subtract (can be GL_ONE)
|
||||
GL_DST_COLOR, // multiply : src x dst color
|
||||
GL_DST_COLOR, // soft light : src x dst color
|
||||
GL_SRC_COLOR, // hard light : src x src color
|
||||
GL_DST_COLOR, // soft subtract
|
||||
GL_ONE, // lighten only
|
||||
GL_ONE};
|
||||
GLenum blending_destination_function[9] = {GL_ONE_MINUS_SRC_ALPHA,// normal
|
||||
GL_ONE, // screen
|
||||
GL_ONE, // subtract
|
||||
GL_ONE_MINUS_SRC_ALPHA, // multiply
|
||||
GL_ONE, // soft light
|
||||
GL_ONE, // hard light
|
||||
GL_ONE, // soft subtract
|
||||
GL_ONE, // lighten only
|
||||
GL_ZERO};
|
||||
|
||||
ShadingProgram::ShadingProgram(const std::string& vertex_file, const std::string& fragment_file) : vertex_id_(0), fragment_id_(0), id_(0)
|
||||
{
|
||||
vertex_file_ = vertex_file;
|
||||
fragment_file_ = fragment_file;
|
||||
}
|
||||
|
||||
void ShadingProgram::init()
|
||||
{
|
||||
vertex_code_ = Resource::getText(vertex_file_);
|
||||
fragment_code_ = Resource::getText(fragment_file_);
|
||||
compile();
|
||||
link();
|
||||
}
|
||||
|
||||
bool ShadingProgram::initialized()
|
||||
{
|
||||
return (id_ != 0);
|
||||
}
|
||||
|
||||
void ShadingProgram::compile()
|
||||
{
|
||||
const char* vcode = vertex_code_.c_str();
|
||||
vertex_id_ = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vertex_id_, 1, &vcode, NULL);
|
||||
glCompileShader(vertex_id_);
|
||||
|
||||
const char* fcode = fragment_code_.c_str();
|
||||
fragment_id_ = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fragment_id_, 1, &fcode, NULL);
|
||||
glCompileShader(fragment_id_);
|
||||
|
||||
checkCompileErr();
|
||||
}
|
||||
|
||||
void ShadingProgram::link()
|
||||
{
|
||||
id_ = glCreateProgram();
|
||||
glAttachShader(id_, vertex_id_);
|
||||
glAttachShader(id_, fragment_id_);
|
||||
glLinkProgram(id_);
|
||||
checkLinkingErr();
|
||||
glUseProgram(id_);
|
||||
glUniform1i(glGetUniformLocation(id_, "iChannel0"), 0);
|
||||
glUniform1i(glGetUniformLocation(id_, "iChannel1"), 1);
|
||||
glUseProgram(0);
|
||||
glDeleteShader(vertex_id_);
|
||||
glDeleteShader(fragment_id_);
|
||||
}
|
||||
|
||||
void ShadingProgram::use()
|
||||
{
|
||||
if (currentProgram_ == nullptr || currentProgram_ != this)
|
||||
{
|
||||
currentProgram_ = this;
|
||||
glUseProgram(id_);
|
||||
}
|
||||
}
|
||||
|
||||
void ShadingProgram::enduse()
|
||||
{
|
||||
glUseProgram(0);
|
||||
currentProgram_ = nullptr ;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<int>(const std::string& name, int val) {
|
||||
glUniform1i(glGetUniformLocation(id_, name.c_str()), val);
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<bool>(const std::string& name, bool val) {
|
||||
glUniform1i(glGetUniformLocation(id_, name.c_str()), val);
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<float>(const std::string& name, float val) {
|
||||
glUniform1f(glGetUniformLocation(id_, name.c_str()), val);
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<float>(const std::string& name, float val1, float val2) {
|
||||
glUniform2f(glGetUniformLocation(id_, name.c_str()), val1, val2);
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<float>(const std::string& name, float val1, float val2, float val3) {
|
||||
glUniform3f(glGetUniformLocation(id_, name.c_str()), val1, val2, val3);
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<glm::vec2>(const std::string& name, glm::vec2 val) {
|
||||
glm::vec2 v(val);
|
||||
glUniform2fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<glm::vec3>(const std::string& name, glm::vec3 val) {
|
||||
glm::vec3 v(val);
|
||||
glUniform3fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<glm::vec4>(const std::string& name, glm::vec4 val) {
|
||||
glm::vec4 v(val);
|
||||
glUniform4fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<glm::mat4>(const std::string& name, glm::mat4 val) {
|
||||
glm::mat4 m(val);
|
||||
glUniformMatrix4fv(glGetUniformLocation(id_, name.c_str()), 1, GL_FALSE, glm::value_ptr(m));
|
||||
}
|
||||
|
||||
|
||||
// template<>
|
||||
// void ShadingProgram::setUniform<float*>(const std::string& name, float* val) {
|
||||
// glUniformMatrix4fv(glGetUniformLocation(id_, name.c_str()), 1, GL_FALSE, val);
|
||||
// }
|
||||
|
||||
void ShadingProgram::checkCompileErr()
|
||||
{
|
||||
int success;
|
||||
char infoLog[1024];
|
||||
glGetShaderiv(vertex_id_, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
glGetShaderInfoLog(vertex_id_, 1024, NULL, infoLog);
|
||||
Log::Warning("Error compiling Vertex ShadingProgram:\n%s", infoLog);
|
||||
}
|
||||
glGetShaderiv(fragment_id_, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
glGetShaderInfoLog(fragment_id_, 1024, NULL, infoLog);
|
||||
Log::Warning("Error compiling Fragment ShadingProgram:\n%s", infoLog);
|
||||
}
|
||||
}
|
||||
|
||||
void ShadingProgram::checkLinkingErr()
|
||||
{
|
||||
int success;
|
||||
char infoLog[1024];
|
||||
glGetProgramiv(id_, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
glGetProgramInfoLog(id_, 1024, NULL, infoLog);
|
||||
Log::Warning("Error linking ShadingProgram:\n%s", infoLog);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Shader::force_blending_opacity = false;
|
||||
|
||||
Shader::Shader() : blending(BLEND_OPACITY)
|
||||
{
|
||||
// create unique id
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
|
||||
program_ = &simpleShadingProgram;
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
void Shader::operator = (const Shader &S )
|
||||
{
|
||||
color = S.color;
|
||||
blending = S.blending;
|
||||
iTransform = S.iTransform;
|
||||
}
|
||||
|
||||
void Shader::accept(Visitor& v) {
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
void Shader::use()
|
||||
{
|
||||
// initialization on first use
|
||||
if (!program_->initialized())
|
||||
program_->init();
|
||||
|
||||
// Use program
|
||||
program_->use();
|
||||
|
||||
// set uniforms
|
||||
program_->setUniform("projection", projection);
|
||||
program_->setUniform("modelview", modelview);
|
||||
program_->setUniform("iTransform", iTransform);
|
||||
program_->setUniform("color", color);
|
||||
|
||||
glm::vec3 iResolution = glm::vec3( Rendering::manager().currentAttrib().viewport, 0.f);
|
||||
program_->setUniform("iResolution", iResolution);
|
||||
|
||||
// Blending Function
|
||||
if (force_blending_opacity) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
else if ( blending < BLEND_NONE ) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquationSeparate(blending_equation[blending], GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(blending_source_function[blending], blending_destination_function[blending], GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
else
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
|
||||
void Shader::reset()
|
||||
{
|
||||
projection = glm::identity<glm::mat4>();
|
||||
modelview = glm::identity<glm::mat4>();
|
||||
iTransform = glm::identity<glm::mat4>();
|
||||
color = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
|
||||
81
Shader.h
@@ -1,81 +0,0 @@
|
||||
#ifndef __SHADER_H_
|
||||
#define __SHADER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// Forward declare classes referenced
|
||||
class Visitor;
|
||||
class FrameBuffer;
|
||||
|
||||
class ShadingProgram
|
||||
{
|
||||
public:
|
||||
ShadingProgram(const std::string& vertex_file, const std::string& fragment_file);
|
||||
void init();
|
||||
bool initialized();
|
||||
void use();
|
||||
template<typename T> void setUniform(const std::string& name, T val);
|
||||
template<typename T> void setUniform(const std::string& name, T val1, T val2);
|
||||
template<typename T> void setUniform(const std::string& name, T val1, T val2, T val3);
|
||||
|
||||
static void enduse();
|
||||
|
||||
private:
|
||||
void checkCompileErr();
|
||||
void checkLinkingErr();
|
||||
void compile();
|
||||
void link();
|
||||
unsigned int vertex_id_, fragment_id_, id_;
|
||||
std::string vertex_code_;
|
||||
std::string fragment_code_;
|
||||
std::string vertex_file_;
|
||||
std::string fragment_file_;
|
||||
|
||||
static ShadingProgram *currentProgram_;
|
||||
};
|
||||
|
||||
class Shader
|
||||
{
|
||||
uint64_t id_;
|
||||
|
||||
public:
|
||||
Shader();
|
||||
virtual ~Shader() {}
|
||||
|
||||
// unique identifyer generated at instanciation
|
||||
inline uint64_t id () const { return id_; }
|
||||
|
||||
virtual void use();
|
||||
virtual void reset();
|
||||
virtual void accept(Visitor& v);
|
||||
|
||||
void operator = (const Shader &D );
|
||||
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 iTransform;
|
||||
glm::vec4 color;
|
||||
|
||||
typedef enum {
|
||||
BLEND_OPACITY = 0,
|
||||
BLEND_SCREEN,
|
||||
BLEND_SUBTRACT,
|
||||
BLEND_MULTIPLY,
|
||||
BLEND_SOFT_LIGHT,
|
||||
BLEND_HARD_LIGHT,
|
||||
BLEND_SOFT_SUBTRACT,
|
||||
BLEND_LIGHTEN_ONLY,
|
||||
BLEND_NONE
|
||||
} BlendMode;
|
||||
BlendMode blending;
|
||||
|
||||
static bool force_blending_opacity;
|
||||
|
||||
protected:
|
||||
ShadingProgram *program_;
|
||||
};
|
||||
|
||||
|
||||
#endif /* __SHADER_H_ */
|
||||
853
Source.cpp
@@ -1,853 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
#include <tinyxml2.h>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "defines.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Decorations.h"
|
||||
#include "Resource.h"
|
||||
#include "SearchVisitor.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "Log.h"
|
||||
#include "MixingGroup.h"
|
||||
|
||||
#include "Source.h"
|
||||
|
||||
Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_(false), need_update_(true), workspace_(STAGE)
|
||||
{
|
||||
// create unique id
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
|
||||
sprintf(initials_, "__");
|
||||
name_ = "Source";
|
||||
mode_ = Source::UNINITIALIZED;
|
||||
|
||||
// create groups and overlays for each view
|
||||
|
||||
// default rendering node
|
||||
groups_[View::RENDERING] = new Group;
|
||||
groups_[View::RENDERING]->visible_ = false;
|
||||
|
||||
// default mixing nodes
|
||||
groups_[View::MIXING] = new Group;
|
||||
groups_[View::MIXING]->visible_ = false;
|
||||
groups_[View::MIXING]->scale_ = glm::vec3(0.15f, 0.15f, 1.f);
|
||||
groups_[View::MIXING]->translation_ = glm::vec3(DEFAULT_MIXING_TRANSLATION, 0.f);
|
||||
|
||||
frames_[View::MIXING] = new Switch;
|
||||
Frame *frame = new Frame(Frame::ROUND, Frame::THIN, Frame::DROP);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
frames_[View::MIXING]->attach(frame);
|
||||
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::DROP);
|
||||
frame->translation_.z = 0.01;
|
||||
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
frames_[View::MIXING]->attach(frame);
|
||||
groups_[View::MIXING]->attach(frames_[View::MIXING]);
|
||||
|
||||
overlays_[View::MIXING] = new Group;
|
||||
overlays_[View::MIXING]->translation_.z = 0.1;
|
||||
overlays_[View::MIXING]->visible_ = false;
|
||||
Symbol *center = new Symbol(Symbol::CIRCLE_POINT, glm::vec3(0.f, 0.f, 0.1f));
|
||||
overlays_[View::MIXING]->attach(center);
|
||||
groups_[View::MIXING]->attach(overlays_[View::MIXING]);
|
||||
|
||||
overlay_mixinggroup_ = new Switch;
|
||||
overlay_mixinggroup_->translation_.z = 0.1;
|
||||
center = new Symbol(Symbol::CIRCLE_POINT, glm::vec3(0.f, 0.f, 0.1f));
|
||||
center->scale_= glm::vec3(1.6f, 1.6f, 1.f);
|
||||
center->color = glm::vec4( COLOR_MIXING_GROUP, 0.96f);
|
||||
overlay_mixinggroup_->attach(center);
|
||||
rotation_mixingroup_ = new Symbol(Symbol::ROTATION, glm::vec3(0.f, 0.f, 0.1f));
|
||||
rotation_mixingroup_->color = glm::vec4( COLOR_MIXING_GROUP, 0.94f);
|
||||
rotation_mixingroup_->scale_ = glm::vec3(3.f, 3.f, 1.f);
|
||||
overlay_mixinggroup_->attach(rotation_mixingroup_);
|
||||
groups_[View::MIXING]->attach(overlay_mixinggroup_);
|
||||
|
||||
// default geometry nodes
|
||||
groups_[View::GEOMETRY] = new Group;
|
||||
groups_[View::GEOMETRY]->visible_ = false;
|
||||
|
||||
frames_[View::GEOMETRY] = new Switch;
|
||||
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.8f);
|
||||
frames_[View::GEOMETRY]->attach(frame);
|
||||
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::GLOW);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
frames_[View::GEOMETRY]->attach(frame);
|
||||
groups_[View::GEOMETRY]->attach(frames_[View::GEOMETRY]);
|
||||
|
||||
overlays_[View::GEOMETRY] = new Group;
|
||||
overlays_[View::GEOMETRY]->translation_.z = 0.15;
|
||||
overlays_[View::GEOMETRY]->visible_ = false;
|
||||
handles_[View::GEOMETRY][Handles::RESIZE] = new Handles(Handles::RESIZE);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE]);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_H]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_H]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE_H]);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_V]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_V]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE_V]);
|
||||
handles_[View::GEOMETRY][Handles::ROTATE] = new Handles(Handles::ROTATE);
|
||||
handles_[View::GEOMETRY][Handles::ROTATE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::ROTATE]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::ROTATE]);
|
||||
handles_[View::GEOMETRY][Handles::SCALE] = new Handles(Handles::SCALE);
|
||||
handles_[View::GEOMETRY][Handles::SCALE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::SCALE]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::SCALE]);
|
||||
handles_[View::GEOMETRY][Handles::MENU] = new Handles(Handles::MENU);
|
||||
handles_[View::GEOMETRY][Handles::MENU]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::MENU]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::MENU]);
|
||||
handles_[View::GEOMETRY][Handles::CROP] = new Handles(Handles::CROP);
|
||||
handles_[View::GEOMETRY][Handles::CROP]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::CROP]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::CROP]);
|
||||
|
||||
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.7f);
|
||||
overlays_[View::GEOMETRY]->attach(frame);
|
||||
groups_[View::GEOMETRY]->attach(overlays_[View::GEOMETRY]);
|
||||
|
||||
// default layer nodes
|
||||
groups_[View::LAYER] = new Group;
|
||||
groups_[View::LAYER]->visible_ = false;
|
||||
groups_[View::LAYER]->translation_.z = -1.f;
|
||||
|
||||
frames_[View::LAYER] = new Switch;
|
||||
frame = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
frames_[View::LAYER]->attach(frame);
|
||||
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::PERSPECTIVE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
frames_[View::LAYER]->attach(frame);
|
||||
groups_[View::LAYER]->attach(frames_[View::LAYER]);
|
||||
|
||||
overlays_[View::LAYER] = new Group;
|
||||
overlays_[View::LAYER]->translation_.z = 0.15;
|
||||
overlays_[View::LAYER]->visible_ = false;
|
||||
groups_[View::LAYER]->attach(overlays_[View::LAYER]);
|
||||
|
||||
// default appearance node
|
||||
groups_[View::TEXTURE] = new Group;
|
||||
groups_[View::TEXTURE]->visible_ = false;
|
||||
|
||||
frames_[View::TEXTURE] = new Switch;
|
||||
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 0.7f);
|
||||
frames_[View::TEXTURE]->attach(frame);
|
||||
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::NONE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
frames_[View::TEXTURE]->attach(frame);
|
||||
groups_[View::TEXTURE]->attach(frames_[View::TEXTURE]);
|
||||
|
||||
overlays_[View::TEXTURE] = new Group;
|
||||
overlays_[View::TEXTURE]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->visible_ = false;
|
||||
handles_[View::TEXTURE][Handles::RESIZE] = new Handles(Handles::RESIZE);
|
||||
handles_[View::TEXTURE][Handles::RESIZE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::RESIZE]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE]);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_H]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_H]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE_H]);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_V]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_V]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE_V]);
|
||||
handles_[View::TEXTURE][Handles::ROTATE] = new Handles(Handles::ROTATE);
|
||||
handles_[View::TEXTURE][Handles::ROTATE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::ROTATE]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::ROTATE]);
|
||||
handles_[View::TEXTURE][Handles::SCALE] = new Handles(Handles::SCALE);
|
||||
handles_[View::TEXTURE][Handles::SCALE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::SCALE]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::SCALE]);
|
||||
handles_[View::TEXTURE][Handles::MENU] = new Handles(Handles::MENU);
|
||||
handles_[View::TEXTURE][Handles::MENU]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::MENU]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::MENU]);
|
||||
groups_[View::TEXTURE]->attach(overlays_[View::TEXTURE]);
|
||||
|
||||
// empty transition node
|
||||
groups_[View::TRANSITION] = new Group;
|
||||
|
||||
// locker switch button : locked / unlocked icons
|
||||
locker_ = new Switch;
|
||||
lock_ = new Handles(Handles::LOCKED);
|
||||
locker_->attach(lock_);
|
||||
unlock_ = new Handles(Handles::UNLOCKED);
|
||||
locker_->attach(unlock_);
|
||||
|
||||
// create objects
|
||||
stored_status_ = new Group;
|
||||
|
||||
// simple image shader (with texturing) for blending
|
||||
blendingshader_ = new ImageShader;
|
||||
// mask produced by dedicated shader
|
||||
maskshader_ = new MaskShader;
|
||||
masksurface_ = new Surface(maskshader_);
|
||||
|
||||
// filtered image shader (with texturing and processing) for rendering
|
||||
processingshader_ = new ImageProcessingShader;
|
||||
// default rendering with image processing enabled
|
||||
renderingshader_ = (Shader *) processingshader_;
|
||||
|
||||
// for drawing in mixing view
|
||||
mixingshader_ = new ImageShader;
|
||||
mixingshader_->stipple = 1.0;
|
||||
mixinggroup_ = nullptr;
|
||||
|
||||
// create media surface:
|
||||
// - textured with original texture from media player
|
||||
// - crop & repeat UV can be managed here
|
||||
// - additional custom shader can be associated
|
||||
texturesurface_ = new Surface(renderingshader_);
|
||||
|
||||
// will be created at init
|
||||
renderbuffer_ = nullptr;
|
||||
rendersurface_ = nullptr;
|
||||
mixingsurface_ = nullptr;
|
||||
maskbuffer_ = nullptr;
|
||||
maskimage_ = nullptr;
|
||||
mask_need_update_ = false;
|
||||
}
|
||||
|
||||
|
||||
Source::~Source()
|
||||
{
|
||||
// inform clones that they lost their origin
|
||||
for (auto it = clones_.begin(); it != clones_.end(); it++)
|
||||
(*it)->detach();
|
||||
clones_.clear();
|
||||
|
||||
// delete objects
|
||||
delete stored_status_;
|
||||
if (renderbuffer_)
|
||||
delete renderbuffer_;
|
||||
if (maskbuffer_)
|
||||
delete maskbuffer_;
|
||||
if (maskimage_)
|
||||
delete maskimage_;
|
||||
|
||||
// all groups and their children are deleted in the scene
|
||||
// this includes rendersurface_, overlays, blendingshader_ and rendershader_
|
||||
delete groups_[View::RENDERING];
|
||||
delete groups_[View::MIXING];
|
||||
delete groups_[View::GEOMETRY];
|
||||
delete groups_[View::LAYER];
|
||||
delete groups_[View::TEXTURE];
|
||||
delete groups_[View::TRANSITION];
|
||||
|
||||
groups_.clear();
|
||||
frames_.clear();
|
||||
overlays_.clear();
|
||||
|
||||
// don't forget that the processing shader
|
||||
// could be created but not used
|
||||
if ( renderingshader_ != processingshader_ )
|
||||
delete processingshader_;
|
||||
|
||||
delete texturesurface_;
|
||||
}
|
||||
|
||||
void Source::setName (const std::string &name)
|
||||
{
|
||||
name_ = SystemToolkit::transliterate(name);
|
||||
|
||||
initials_[0] = std::toupper( name_.front(), std::locale("C") );
|
||||
initials_[1] = std::toupper( name_.back(), std::locale("C") );
|
||||
}
|
||||
|
||||
void Source::accept(Visitor& v)
|
||||
{
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
Source::Mode Source::mode() const
|
||||
{
|
||||
return mode_;
|
||||
}
|
||||
|
||||
void Source::setMode(Source::Mode m)
|
||||
{
|
||||
// make visible on first time
|
||||
if ( mode_ == Source::UNINITIALIZED ) {
|
||||
for (auto g = groups_.begin(); g != groups_.end(); g++)
|
||||
(*g).second->visible_ = true;
|
||||
}
|
||||
|
||||
// choose frame 0 if visible, 1 if selected
|
||||
uint index_frame = m == Source::VISIBLE ? 0 : 1;
|
||||
for (auto f = frames_.begin(); f != frames_.end(); f++)
|
||||
(*f).second->setActive(index_frame);
|
||||
|
||||
// show overlay if current
|
||||
bool current = m >= Source::CURRENT;
|
||||
for (auto o = overlays_.begin(); o != overlays_.end(); o++)
|
||||
(*o).second->visible_ = current & !locked_;
|
||||
|
||||
// the lock icon
|
||||
locker_->setActive( locked_ ? 0 : 1);
|
||||
|
||||
// the mixing group overlay
|
||||
overlay_mixinggroup_->visible_ = mixinggroup_!= nullptr && !locked_;
|
||||
overlay_mixinggroup_->setActive(current);
|
||||
|
||||
// show in appearance view if current
|
||||
groups_[View::TEXTURE]->visible_ = m > Source::VISIBLE;
|
||||
|
||||
mode_ = m;
|
||||
}
|
||||
|
||||
|
||||
void Source::setImageProcessingEnabled (bool on)
|
||||
{
|
||||
// avoid repeating
|
||||
if ( on == imageProcessingEnabled() )
|
||||
return;
|
||||
|
||||
// set pointer
|
||||
if (on) {
|
||||
// set the current rendering shader to be the
|
||||
// (previously prepared) processing shader
|
||||
renderingshader_ = (Shader *) processingshader_;
|
||||
}
|
||||
else {
|
||||
// clone the current Image processing shader
|
||||
// (because the one currently attached to the source
|
||||
// will be deleted in replaceRenderngShader().)
|
||||
ImageProcessingShader *tmp = new ImageProcessingShader(*processingshader_);
|
||||
// loose reference to current processing shader (to delete)
|
||||
// and keep reference to the newly created one
|
||||
// and keep it for later
|
||||
processingshader_ = tmp;
|
||||
// set the current rendering shader to a simple one
|
||||
renderingshader_ = (Shader *) new ImageShader;
|
||||
}
|
||||
|
||||
// apply to nodes in subclasses
|
||||
// this calls replaceShader() on the Primitive and
|
||||
// will delete the previously attached shader
|
||||
texturesurface_->replaceShader(renderingshader_);
|
||||
}
|
||||
|
||||
void Source::setTextureMirrored (bool on)
|
||||
{
|
||||
texturesurface_->setMirrorTexture(on);
|
||||
}
|
||||
|
||||
bool Source::textureMirrored ()
|
||||
{
|
||||
return texturesurface_->mirrorTexture();
|
||||
}
|
||||
|
||||
bool Source::imageProcessingEnabled()
|
||||
{
|
||||
return ( renderingshader_ == processingshader_ );
|
||||
}
|
||||
|
||||
void Source::render()
|
||||
{
|
||||
if (!initialized_)
|
||||
init();
|
||||
else {
|
||||
// render the view into frame buffer
|
||||
renderbuffer_->begin();
|
||||
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
|
||||
renderbuffer_->end();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Source::attach(FrameBuffer *renderbuffer)
|
||||
{
|
||||
// invalid argument
|
||||
if (renderbuffer == nullptr)
|
||||
return;
|
||||
|
||||
// replace renderbuffer_
|
||||
if (renderbuffer_)
|
||||
delete renderbuffer_;
|
||||
renderbuffer_ = renderbuffer;
|
||||
|
||||
// if a symbol is available, add it to overlay
|
||||
if (symbol_) {
|
||||
overlays_[View::MIXING]->attach( symbol_ );
|
||||
overlays_[View::LAYER]->attach( symbol_ );
|
||||
}
|
||||
|
||||
// create the surfaces to draw the frame buffer in the views
|
||||
rendersurface_ = new FrameBufferSurface(renderbuffer_, blendingshader_);
|
||||
groups_[View::RENDERING]->attach(rendersurface_);
|
||||
groups_[View::GEOMETRY]->attach(rendersurface_);
|
||||
|
||||
// for mixing and layer views, add another surface to overlay
|
||||
// (stippled view on top with transparency)
|
||||
mixingsurface_ = new FrameBufferSurface(renderbuffer_, mixingshader_);
|
||||
groups_[View::MIXING]->attach(mixingsurface_);
|
||||
groups_[View::LAYER]->attach(mixingsurface_);
|
||||
|
||||
// for views showing a scaled mixing surface, a dedicated transparent surface allows grabbing
|
||||
Surface *surfacetmp = new Surface();
|
||||
surfacetmp->setTextureIndex(Resource::getTextureTransparent());
|
||||
groups_[View::TEXTURE]->attach(surfacetmp);
|
||||
groups_[View::MIXING]->attach(surfacetmp);
|
||||
groups_[View::LAYER]->attach(surfacetmp);
|
||||
|
||||
// Transition group node is optionnal
|
||||
if (groups_[View::TRANSITION]->numChildren() > 0)
|
||||
groups_[View::TRANSITION]->attach(mixingsurface_);
|
||||
|
||||
// hack to place the symbols in the corner independently of aspect ratio
|
||||
symbol_->translation_.x += 0.1f * (renderbuffer_->aspectRatio()-1.f);
|
||||
|
||||
// add lock icon to views (displayed on front)
|
||||
groups_[View::LAYER]->attach( locker_ );
|
||||
groups_[View::MIXING]->attach( locker_ );
|
||||
groups_[View::GEOMETRY]->attach( locker_ );
|
||||
groups_[View::TEXTURE]->attach( locker_ );
|
||||
|
||||
// scale all icon nodes to match aspect ratio
|
||||
for (int v = View::MIXING; v < View::INVALID; v++) {
|
||||
NodeSet::iterator node;
|
||||
for (node = groups_[(View::Mode) v]->begin();
|
||||
node != groups_[(View::Mode) v]->end(); node++) {
|
||||
(*node)->scale_.x = renderbuffer_->aspectRatio();
|
||||
}
|
||||
}
|
||||
|
||||
// (re) create the masking buffer
|
||||
if (maskbuffer_)
|
||||
delete maskbuffer_;
|
||||
maskbuffer_ = new FrameBuffer( glm::vec3(0.5) * renderbuffer->resolution() );
|
||||
|
||||
// make the source visible
|
||||
if ( mode_ == UNINITIALIZED )
|
||||
setMode(VISIBLE);
|
||||
|
||||
// request update
|
||||
need_update_ = true;
|
||||
}
|
||||
|
||||
|
||||
void Source::setActive (bool on)
|
||||
{
|
||||
active_ = on;
|
||||
|
||||
// do not disactivate if a clone depends on it
|
||||
for(auto clone = clones_.begin(); clone != clones_.end(); clone++) {
|
||||
if ( (*clone)->active() )
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
// an inactive source is visible only in the MIXING view
|
||||
groups_[View::RENDERING]->visible_ = active_;
|
||||
groups_[View::GEOMETRY]->visible_ = active_;
|
||||
groups_[View::LAYER]->visible_ = active_;
|
||||
}
|
||||
|
||||
void Source::setLocked (bool on)
|
||||
{
|
||||
locked_ = on;
|
||||
|
||||
setMode(mode_);
|
||||
}
|
||||
|
||||
// Transfer functions from coordinates to alpha (1 - transparency)
|
||||
|
||||
// linear distance
|
||||
float linear_(float x, float y) {
|
||||
return 1.f - CLAMP( sqrt( ( x * x ) + ( y * y ) ), 0.f, 1.f );
|
||||
}
|
||||
|
||||
// quadratic distance
|
||||
float quad_(float x, float y) {
|
||||
return 1.f - CLAMP( ( x * x ) + ( y * y ), 0.f, 1.f );
|
||||
}
|
||||
|
||||
// best alpha transfer function: quadratic sinusoidal shape
|
||||
float sin_quad_(float x, float y) {
|
||||
float D = sqrt( ( x * x ) + ( y * y ) );
|
||||
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
|
||||
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ), 0.f, 1.f ) );
|
||||
}
|
||||
|
||||
|
||||
float Source::depth() const
|
||||
{
|
||||
return group(View::RENDERING)->translation_.z;
|
||||
}
|
||||
|
||||
void Source::setDepth(float d)
|
||||
{
|
||||
groups_[View::LAYER]->translation_.z = CLAMP(d, MIN_DEPTH, MAX_DEPTH);
|
||||
touch();
|
||||
}
|
||||
|
||||
float Source::alpha() const
|
||||
{
|
||||
return blendingShader()->color.a;
|
||||
}
|
||||
|
||||
void Source::setAlpha(float a)
|
||||
{
|
||||
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
|
||||
glm::vec2 step = glm::normalize(glm::vec2(1.f, 1.f));// step in diagonal by default
|
||||
|
||||
// step in direction of source translation if possible
|
||||
if ( glm::length(dist) > DELTA_ALPHA)
|
||||
step = glm::normalize(dist);
|
||||
|
||||
// converge to reduce the difference of alpha
|
||||
// using dichotomic algorithm
|
||||
float delta = sin_quad_(dist.x, dist.y) - CLAMP(a, 0.f, 1.f);
|
||||
while ( glm::abs(delta) > DELTA_ALPHA ){
|
||||
dist += step * (delta / 2.f);
|
||||
delta = sin_quad_(dist.x, dist.y) - CLAMP(a, 0.f, 1.f);
|
||||
}
|
||||
|
||||
// apply new mixing coordinates
|
||||
groups_[View::MIXING]->translation_.x = dist.x;
|
||||
groups_[View::MIXING]->translation_.y = dist.y;
|
||||
touch();
|
||||
}
|
||||
|
||||
void Source::update(float dt)
|
||||
{
|
||||
// keep delta-t
|
||||
dt_ = dt;
|
||||
|
||||
// update nodes if needed
|
||||
if (renderbuffer_ && mixingsurface_ && maskbuffer_ && need_update_)
|
||||
{
|
||||
// ADJUST alpha based on MIXING node
|
||||
// read position of the mixing node and interpret this as transparency of render output
|
||||
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
|
||||
// use the sinusoidal transfer function
|
||||
blendingshader_->color = glm::vec4(1.f, 1.f, 1.f, sin_quad_( dist.x, dist.y ));
|
||||
mixingshader_->color = blendingshader_->color;
|
||||
|
||||
// CHANGE update status based on limbo
|
||||
bool a = glm::length(dist) < MIXING_LIMBO_SCALE;
|
||||
setActive( a );
|
||||
// adjust scale of mixing icon : smaller if not active
|
||||
groups_[View::MIXING]->scale_ = glm::vec3(MIXING_ICON_SCALE) - ( a ? glm::vec3(0.f, 0.f, 0.f) : glm::vec3(0.03f, 0.03f, 0.f) );
|
||||
|
||||
// MODIFY geometry based on GEOMETRY node
|
||||
groups_[View::RENDERING]->translation_ = groups_[View::GEOMETRY]->translation_;
|
||||
groups_[View::RENDERING]->rotation_ = groups_[View::GEOMETRY]->rotation_;
|
||||
glm::vec3 s = groups_[View::GEOMETRY]->scale_;
|
||||
// avoid any null scale
|
||||
s.x = CLAMP_SCALE(s.x);
|
||||
s.y = CLAMP_SCALE(s.y);
|
||||
s.z = 1.f;
|
||||
groups_[View::GEOMETRY]->scale_ = s;
|
||||
groups_[View::RENDERING]->scale_ = s;
|
||||
|
||||
// MODIFY CROP projection based on GEOMETRY crop
|
||||
renderbuffer_->setProjectionArea( glm::vec2(groups_[View::GEOMETRY]->crop_) );
|
||||
|
||||
// Mixing and layer icons scaled based on GEOMETRY crop
|
||||
mixingsurface_->scale_ = groups_[View::GEOMETRY]->crop_;
|
||||
mixingsurface_->scale_.x *= renderbuffer_->aspectRatio();
|
||||
mixingsurface_->update(dt_);
|
||||
|
||||
// Layers icons are displayed in Perspective (diagonal)
|
||||
groups_[View::LAYER]->translation_.x = -groups_[View::LAYER]->translation_.z;
|
||||
groups_[View::LAYER]->translation_.y = groups_[View::LAYER]->translation_.x / LAYER_PERSPECTIVE;
|
||||
|
||||
// Update workspace based on depth, and
|
||||
// adjust vertical position of icon depending on workspace
|
||||
if (groups_[View::LAYER]->translation_.x < -LAYER_FOREGROUND) {
|
||||
groups_[View::LAYER]->translation_.y -= 0.3f;
|
||||
workspace_ = Source::FOREGROUND;
|
||||
}
|
||||
else if (groups_[View::LAYER]->translation_.x < -LAYER_BACKGROUND) {
|
||||
groups_[View::LAYER]->translation_.y -= 0.15f;
|
||||
workspace_ = Source::STAGE;
|
||||
}
|
||||
else
|
||||
workspace_ = Source::BACKGROUND;
|
||||
|
||||
// MODIFY depth based on LAYER node
|
||||
groups_[View::MIXING]->translation_.z = groups_[View::LAYER]->translation_.z;
|
||||
groups_[View::GEOMETRY]->translation_.z = groups_[View::LAYER]->translation_.z;
|
||||
groups_[View::RENDERING]->translation_.z = groups_[View::LAYER]->translation_.z;
|
||||
|
||||
// MODIFY texture projection based on APPEARANCE node
|
||||
// UV to node coordinates
|
||||
static glm::mat4 UVtoScene = GlmToolkit::transform(glm::vec3(1.f, -1.f, 0.f),
|
||||
glm::vec3(0.f, 0.f, 0.f),
|
||||
glm::vec3(-2.f, 2.f, 1.f));
|
||||
// Aspect Ratio correction transform : coordinates of Appearance Frame are scaled by render buffer width
|
||||
glm::mat4 Ar = glm::scale(glm::identity<glm::mat4>(), glm::vec3(renderbuffer_->aspectRatio(), 1.f, 1.f) );
|
||||
// Translation : same as Appearance Frame (modified by Ar)
|
||||
glm::mat4 Tra = glm::translate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->translation_);
|
||||
// Scaling : inverse scaling (larger UV when smaller Appearance Frame)
|
||||
glm::vec2 scale = glm::vec2(groups_[View::TEXTURE]->scale_.x,groups_[View::TEXTURE]->scale_.y);
|
||||
scale = glm::sign(scale) * glm::max( glm::vec2(glm::epsilon<float>()), glm::abs(scale));
|
||||
glm::mat4 Sca = glm::scale(glm::identity<glm::mat4>(), glm::vec3(scale, 1.f));
|
||||
// Rotation : same angle than Appearance Frame, inverted axis
|
||||
glm::mat4 Rot = glm::rotate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->rotation_.z, glm::vec3(0.f, 0.f, -1.f) );
|
||||
// Combine transformations (non transitive) in this order:
|
||||
// 1. switch to Scene coordinate system
|
||||
// 2. Apply the aspect ratio correction
|
||||
// 3. Apply the translation
|
||||
// 4. Apply the rotation (centered after translation)
|
||||
// 5. Revert aspect ration correction
|
||||
// 6. Apply the Scaling (independent of aspect ratio)
|
||||
// 7. switch back to UV coordinate system
|
||||
texturesurface_->shader()->iTransform = glm::inverse(UVtoScene) * glm::inverse(Sca) * glm::inverse(Ar) * Rot * Tra * Ar * UVtoScene;
|
||||
|
||||
// if a mask image was given to be updated
|
||||
if (mask_need_update_) {
|
||||
// fill the mask buffer (once)
|
||||
if (maskbuffer_->fill(maskimage_) )
|
||||
mask_need_update_ = false;
|
||||
}
|
||||
// otherwise, render the mask buffer
|
||||
else
|
||||
{
|
||||
// draw mask in mask frame buffer
|
||||
maskbuffer_->begin(false);
|
||||
// loopback maskbuffer texture for painting
|
||||
masksurface_->setTextureIndex(maskbuffer_->texture());
|
||||
// fill surface with mask texture
|
||||
masksurface_->draw(glm::identity<glm::mat4>(), maskbuffer_->projection());
|
||||
maskbuffer_->end();
|
||||
}
|
||||
|
||||
// set the rendered mask as mask for blending
|
||||
blendingshader_->mask_texture = maskbuffer_->texture();
|
||||
|
||||
// inform mixing group
|
||||
if (mixinggroup_)
|
||||
mixinggroup_->setAction(MixingGroup::ACTION_UPDATE);
|
||||
|
||||
// do not update next frame
|
||||
need_update_ = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FrameBuffer *Source::frame() const
|
||||
{
|
||||
if (initialized_ && renderbuffer_)
|
||||
{
|
||||
return renderbuffer_;
|
||||
}
|
||||
else {
|
||||
static FrameBuffer *black = new FrameBuffer(640,480);
|
||||
return black;
|
||||
}
|
||||
}
|
||||
|
||||
bool Source::contains(Node *node) const
|
||||
{
|
||||
if ( node == nullptr )
|
||||
return false;
|
||||
|
||||
hasNode tester(node);
|
||||
return tester(this);
|
||||
}
|
||||
|
||||
|
||||
void Source::storeMask(FrameBufferImage *img)
|
||||
{
|
||||
// free the output mask storage
|
||||
if (maskimage_ != nullptr) {
|
||||
delete maskimage_;
|
||||
maskimage_ = nullptr;
|
||||
}
|
||||
|
||||
// if no image is provided
|
||||
if (img == nullptr) {
|
||||
// if ready
|
||||
if (maskbuffer_!=nullptr) {
|
||||
// get & store image from mask buffer
|
||||
maskimage_ = maskbuffer_->image();
|
||||
}
|
||||
}
|
||||
else
|
||||
// store the given image
|
||||
maskimage_ = img;
|
||||
|
||||
// maskimage_ can now be accessed with Source::getStoredMask
|
||||
}
|
||||
|
||||
void Source::setMask(FrameBufferImage *img)
|
||||
{
|
||||
// if a valid image is given
|
||||
if (img != nullptr && img->width>0 && img->height>0) {
|
||||
|
||||
// remember this new image as the current mask
|
||||
// NB: will be freed when replaced
|
||||
storeMask(img);
|
||||
|
||||
// ask Source::update to use it at next update for filling mask buffer
|
||||
mask_need_update_ = true;
|
||||
|
||||
// ask to update the source
|
||||
touch();
|
||||
}
|
||||
else
|
||||
mask_need_update_ = false;
|
||||
}
|
||||
|
||||
|
||||
std::string Source::xml(Source *s)
|
||||
{
|
||||
std::string x = "";
|
||||
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
|
||||
selectionNode->SetAttribute("size", 1);
|
||||
xmlDoc.InsertEndChild(selectionNode);
|
||||
|
||||
SessionVisitor sv(&xmlDoc, selectionNode);
|
||||
s->accept(sv);
|
||||
|
||||
// get compact string
|
||||
tinyxml2::XMLPrinter xmlPrint(0, true);
|
||||
xmlDoc.Print( &xmlPrint );
|
||||
x = xmlPrint.CStr();
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
bool Source::hasNode::operator()(const Source* elem) const
|
||||
{
|
||||
if (_n && elem)
|
||||
{
|
||||
// quick case (most frequent and easy to answer)
|
||||
if (elem->rendersurface_ == _n)
|
||||
return true;
|
||||
|
||||
// general case: traverse tree of all Groups recursively using a SearchVisitor
|
||||
SearchVisitor sv(_n);
|
||||
// search in groups for all views
|
||||
for (auto g = elem->groups_.begin(); g != elem->groups_.end(); g++) {
|
||||
(*g).second->accept(sv);
|
||||
if (sv.found())
|
||||
return true;
|
||||
}
|
||||
// search in overlays for all views
|
||||
for (auto g = elem->overlays_.begin(); g != elem->overlays_.end(); g++) {
|
||||
(*g).second->accept(sv);
|
||||
if (sv.found())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void Source::clearMixingGroup()
|
||||
{
|
||||
mixinggroup_ = nullptr;
|
||||
overlay_mixinggroup_->visible_ = false;
|
||||
}
|
||||
|
||||
CloneSource *Source::clone()
|
||||
{
|
||||
CloneSource *s = new CloneSource(this);
|
||||
|
||||
clones_.push_back(s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
CloneSource::CloneSource(Source *origin) : Source(), origin_(origin)
|
||||
{
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::CLONE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
CloneSource::~CloneSource()
|
||||
{
|
||||
if (origin_)
|
||||
origin_->clones_.remove(this);
|
||||
}
|
||||
|
||||
CloneSource *CloneSource::clone()
|
||||
{
|
||||
// do not clone a clone : clone the original instead
|
||||
if (origin_)
|
||||
return origin_->clone();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CloneSource::init()
|
||||
{
|
||||
if (origin_ && origin_->ready()) {
|
||||
|
||||
// get the texture index from framebuffer of view, apply it to the surface
|
||||
texturesurface_->setTextureIndex( origin_->texture() );
|
||||
|
||||
// create Frame buffer matching size of session
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( origin_->frame()->resolution(), true);
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
Log::Info("Source %s cloning source %s.", name().c_str(), origin_->name().c_str() );
|
||||
}
|
||||
}
|
||||
|
||||
void CloneSource::setActive (bool on)
|
||||
{
|
||||
active_ = on;
|
||||
|
||||
groups_[View::RENDERING]->visible_ = active_;
|
||||
groups_[View::GEOMETRY]->visible_ = active_;
|
||||
groups_[View::LAYER]->visible_ = active_;
|
||||
|
||||
if (initialized_ && origin_ != nullptr)
|
||||
origin_->touch();
|
||||
}
|
||||
|
||||
|
||||
uint CloneSource::texture() const
|
||||
{
|
||||
if (initialized_ && origin_ != nullptr)
|
||||
return origin_->texture();
|
||||
else
|
||||
return Resource::getTextureBlack();
|
||||
}
|
||||
|
||||
void CloneSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
120
SourceList.cpp
@@ -1,120 +0,0 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <glm/gtx/rotate_vector.hpp>
|
||||
|
||||
#include "Source.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
// utility to sort Sources by depth
|
||||
bool compare_depth (Source * first, Source * second)
|
||||
{
|
||||
return ( first->depth() < second->depth() );
|
||||
}
|
||||
|
||||
SourceList depth_sorted(SourceList list)
|
||||
{
|
||||
SourceList sl = list;
|
||||
sl.sort(compare_depth);
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
// utility to sort Sources in MixingView in a clockwise order
|
||||
// in reference to a center point
|
||||
struct clockwise_centered {
|
||||
clockwise_centered(glm::vec2 c) : center(c) { }
|
||||
bool operator() (Source * first, Source * second) {
|
||||
glm::vec2 pos_first = glm::vec2(first->group(View::MIXING)->translation_)-center;
|
||||
float angle_first = glm::orientedAngle( glm::normalize(pos_first), glm::vec2(1.f, 0.f) );
|
||||
glm::vec2 pos_second = glm::vec2(second->group(View::MIXING)->translation_)-center;
|
||||
float angle_second = glm::orientedAngle( glm::normalize(pos_second), glm::vec2(1.f, 0.f) );
|
||||
return (angle_first < angle_second);
|
||||
}
|
||||
glm::vec2 center;
|
||||
};
|
||||
|
||||
SourceList mixing_sorted(SourceList list, glm::vec2 center)
|
||||
{
|
||||
SourceList sl = list;
|
||||
sl.sort(clockwise_centered(center));
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
|
||||
SourceIdList ids (SourceList list)
|
||||
{
|
||||
SourceIdList idlist;
|
||||
|
||||
for( auto sit = list.begin(); sit != list.end(); sit++)
|
||||
idlist.push_back( (*sit)->id() );
|
||||
|
||||
// make sure no duplicate
|
||||
idlist.unique();
|
||||
|
||||
return idlist;
|
||||
}
|
||||
|
||||
|
||||
SourceListCompare compare (SourceList first, SourceList second)
|
||||
{
|
||||
SourceListCompare ret = SOURCELIST_DISTINCT;
|
||||
if (first.empty() || first.empty())
|
||||
return ret;
|
||||
|
||||
// a new test list: start with the second list and remove all commons with first list
|
||||
SourceList test = second;
|
||||
for (auto it = first.begin(); it != first.end(); it++){
|
||||
test.remove(*it);
|
||||
}
|
||||
|
||||
// all sources of the second list were in the first list
|
||||
if (test.empty()) {
|
||||
// same size, therefore they are the same!
|
||||
if (first.size() == second.size())
|
||||
ret = SOURCELIST_EQUAL;
|
||||
// otherwise, first list contains all sources of the second list.
|
||||
else
|
||||
ret = SOURCELIST_SECOND_IN_FIRST;
|
||||
}
|
||||
// some sources of the second list were in the first
|
||||
else if ( second.size() != test.size() ){
|
||||
// if the number of sources removed from second is the number of sources in the first
|
||||
if (second.size() - test.size() == first.size())
|
||||
ret = SOURCELIST_FIRST_IN_SECOND;
|
||||
// else, there is a patrial intersection
|
||||
else
|
||||
ret = SOURCELIST_INTERSECT;
|
||||
}
|
||||
// else no intersection, lists are distinct (return detault)
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
SourceList intersect (SourceList first, SourceList second)
|
||||
{
|
||||
// take second list and remove all elements also in first list
|
||||
// -> builds the list of what remains in second list
|
||||
SourceList l1 = second;
|
||||
for (auto it = first.begin(); it != first.end(); it++)
|
||||
l1.remove(*it);
|
||||
// take second list and remove all elements in the remainer list
|
||||
// -> builds the list of what is in second list and was part of the first list
|
||||
SourceList l2 = second;
|
||||
for (auto it = l1.begin(); it != l1.end(); it++)
|
||||
l2.remove(*it);
|
||||
return l2;
|
||||
}
|
||||
|
||||
|
||||
SourceList join (SourceList first, SourceList second)
|
||||
{
|
||||
SourceList l = second;
|
||||
for (auto it = first.begin(); it != first.end(); it++)
|
||||
l.push_back(*it);
|
||||
l.unique();
|
||||
return l;
|
||||
}
|
||||
|
||||
28
SourceList.h
@@ -1,28 +0,0 @@
|
||||
#ifndef SOURCELIST_H
|
||||
#define SOURCELIST_H
|
||||
|
||||
#include <list>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class Source;
|
||||
|
||||
typedef std::list<Source *> SourceList;
|
||||
|
||||
SourceList depth_sorted (SourceList list);
|
||||
SourceList mixing_sorted (SourceList list, glm::vec2 center = glm::vec2(0.f, 0.f));
|
||||
SourceList intersect (SourceList first, SourceList second);
|
||||
SourceList join (SourceList first, SourceList second);
|
||||
|
||||
typedef enum {
|
||||
SOURCELIST_DISTINCT = 0,
|
||||
SOURCELIST_INTERSECT = 1,
|
||||
SOURCELIST_EQUAL = 2,
|
||||
SOURCELIST_FIRST_IN_SECOND = 3,
|
||||
SOURCELIST_SECOND_IN_FIRST = 4
|
||||
} SourceListCompare;
|
||||
SourceListCompare compare (SourceList first, SourceList second);
|
||||
|
||||
typedef std::list<uint64_t> SourceIdList;
|
||||
SourceIdList ids (SourceList list);
|
||||
|
||||
#endif // SOURCELIST_H
|
||||
740
Stream.cpp
@@ -1,740 +0,0 @@
|
||||
#include <thread>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
|
||||
// vmix
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Resource.h"
|
||||
#include "Visitor.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
#include "Stream.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define STREAM_DEBUG
|
||||
#endif
|
||||
|
||||
#define USE_GST_APPSINK_CALLBACKS_
|
||||
|
||||
|
||||
Stream::Stream()
|
||||
{
|
||||
// create unique id
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
|
||||
description_ = "undefined";
|
||||
pipeline_ = nullptr;
|
||||
|
||||
width_ = -1;
|
||||
height_ = -1;
|
||||
single_frame_ = false;
|
||||
live_ = false;
|
||||
ready_ = false;
|
||||
failed_ = false;
|
||||
enabled_ = true;
|
||||
desired_state_ = GST_STATE_PAUSED;
|
||||
|
||||
// start index in frame_ stack
|
||||
write_index_ = 0;
|
||||
last_index_ = 0;
|
||||
|
||||
// no PBO by default
|
||||
pbo_[0] = pbo_[1] = 0;
|
||||
pbo_size_ = 0;
|
||||
pbo_index_ = 0;
|
||||
pbo_next_index_ = 0;
|
||||
|
||||
// OpenGL texture
|
||||
textureindex_ = 0;
|
||||
}
|
||||
|
||||
Stream::~Stream()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void Stream::accept(Visitor& v) {
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
guint Stream::texture() const
|
||||
{
|
||||
if (textureindex_ == 0)
|
||||
return Resource::getTextureBlack();
|
||||
|
||||
return textureindex_;
|
||||
}
|
||||
|
||||
|
||||
void Stream::open(const std::string &gstreamer_description, int w, int h)
|
||||
{
|
||||
// set gstreamer pipeline source
|
||||
description_ = gstreamer_description;
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
// close before re-openning
|
||||
if (isOpen())
|
||||
close();
|
||||
|
||||
execute_open();
|
||||
}
|
||||
|
||||
|
||||
std::string Stream::description() const
|
||||
{
|
||||
return description_;
|
||||
}
|
||||
|
||||
void Stream::execute_open()
|
||||
{
|
||||
// reset
|
||||
ready_ = false;
|
||||
|
||||
// Add custom app sink to the gstreamer pipeline
|
||||
string description = description_;
|
||||
description += " ! appsink name=sink";
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("Stream %s Could not construct pipeline %s:\n%s", std::to_string(id_).c_str(), description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL);
|
||||
|
||||
// GstCaps *caps = gst_static_caps_get (&frame_render_caps);
|
||||
string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(width_) +
|
||||
",height=" + std::to_string(height_);
|
||||
GstCaps *caps = gst_caps_from_string(capstring.c_str());
|
||||
if (!caps || !gst_video_info_from_caps (&v_frame_video_info_, caps)) {
|
||||
Log::Warning("Stream %d Could not configure video frame info", id_);
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// setup appsink
|
||||
GstElement *sink = gst_bin_get_by_name (GST_BIN (pipeline_), "sink");
|
||||
if (!sink) {
|
||||
Log::Warning("Stream %s Could not configure sink", std::to_string(id_).c_str());
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// instruct sink to use the required caps
|
||||
gst_app_sink_set_caps (GST_APP_SINK(sink), caps);
|
||||
|
||||
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached.
|
||||
gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 30);
|
||||
gst_app_sink_set_drop (GST_APP_SINK(sink), true);
|
||||
|
||||
#ifdef USE_GST_APPSINK_CALLBACKS_
|
||||
// set the callbacks
|
||||
GstAppSinkCallbacks callbacks;
|
||||
if (single_frame_) {
|
||||
callbacks.new_preroll = callback_new_preroll;
|
||||
callbacks.eos = NULL;
|
||||
callbacks.new_sample = NULL;
|
||||
Log::Info("Stream %s contains a single frame", std::to_string(id_).c_str());
|
||||
}
|
||||
else {
|
||||
callbacks.new_preroll = callback_new_preroll;
|
||||
callbacks.eos = callback_end_of_stream;
|
||||
callbacks.new_sample = callback_new_sample;
|
||||
}
|
||||
gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, this, NULL);
|
||||
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), false);
|
||||
#else
|
||||
// connect signals callbacks
|
||||
g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (callback_new_preroll), this);
|
||||
if (!single_frame_) {
|
||||
g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (callback_new_sample), this);
|
||||
g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (callback_end_of_stream), this);
|
||||
}
|
||||
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), true);
|
||||
#endif
|
||||
|
||||
// set to desired state (PLAY or PAUSE)
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("Stream %s Could not open '%s'", std::to_string(id_).c_str(), description_.c_str());
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
|
||||
Log::Info("Stream %s is a live stream", std::to_string(id_).c_str());
|
||||
live_ = true;
|
||||
}
|
||||
|
||||
// instruct the sink to send samples synched in time if not live source
|
||||
gst_base_sink_set_sync (GST_BASE_SINK(sink), !live_);
|
||||
|
||||
// all good
|
||||
Log::Info("Stream %s Opened '%s' (%d x %d)", std::to_string(id_).c_str(), description.c_str(), width_, height_);
|
||||
ready_ = true;
|
||||
|
||||
// done with refs
|
||||
gst_object_unref (sink);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
bool Stream::isOpen() const
|
||||
{
|
||||
return ready_;
|
||||
}
|
||||
|
||||
bool Stream::failed() const
|
||||
{
|
||||
return failed_;
|
||||
}
|
||||
|
||||
void Stream::close()
|
||||
{
|
||||
// not openned?
|
||||
if (!ready_) {
|
||||
// nothing else to change
|
||||
return;
|
||||
}
|
||||
|
||||
// un-ready
|
||||
ready_ = false;
|
||||
|
||||
// clean up GST
|
||||
if (pipeline_ != nullptr) {
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
if (ret == GST_STATE_CHANGE_ASYNC) {
|
||||
GstState state;
|
||||
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
|
||||
}
|
||||
gst_object_unref (pipeline_);
|
||||
pipeline_ = nullptr;
|
||||
}
|
||||
desired_state_ = GST_STATE_PAUSED;
|
||||
|
||||
// cleanup eventual remaining frame memory
|
||||
for(guint i = 0; i < N_FRAME; i++){
|
||||
if ( frame_[i].full ) {
|
||||
gst_video_frame_unmap(&frame_[i].vframe);
|
||||
frame_[i].status = INVALID;
|
||||
}
|
||||
}
|
||||
write_index_ = 0;
|
||||
last_index_ = 0;
|
||||
|
||||
// cleanup opengl texture
|
||||
if (textureindex_)
|
||||
glDeleteTextures(1, &textureindex_);
|
||||
textureindex_ = 0;
|
||||
|
||||
// cleanup picture buffer
|
||||
if (pbo_[0])
|
||||
glDeleteBuffers(2, pbo_);
|
||||
pbo_size_ = 0;
|
||||
}
|
||||
|
||||
|
||||
guint Stream::width() const
|
||||
{
|
||||
return width_;
|
||||
}
|
||||
|
||||
guint Stream::height() const
|
||||
{
|
||||
return height_;
|
||||
}
|
||||
|
||||
float Stream::aspectRatio() const
|
||||
{
|
||||
return static_cast<float>(width_) / static_cast<float>(height_);
|
||||
}
|
||||
|
||||
void Stream::enable(bool on)
|
||||
{
|
||||
if ( !ready_ || pipeline_ == nullptr)
|
||||
return;
|
||||
|
||||
if ( enabled_ != on ) {
|
||||
|
||||
enabled_ = on;
|
||||
|
||||
// default to pause
|
||||
GstState requested_state = GST_STATE_PAUSED;
|
||||
|
||||
// unpause only if enabled
|
||||
if (enabled_) {
|
||||
requested_state = desired_state_;
|
||||
}
|
||||
|
||||
// apply state change
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, requested_state);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("Stream %s Failed to enable", std::to_string(id_).c_str());
|
||||
failed_ = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool Stream::enabled() const
|
||||
{
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
bool Stream::singleFrame() const
|
||||
{
|
||||
return single_frame_;
|
||||
}
|
||||
|
||||
bool Stream::live() const
|
||||
{
|
||||
return live_;
|
||||
}
|
||||
|
||||
void Stream::play(bool on)
|
||||
{
|
||||
// ignore if disabled, and cannot play an image
|
||||
if (!enabled_)
|
||||
return;
|
||||
|
||||
// request state
|
||||
GstState requested_state = on ? GST_STATE_PLAYING : GST_STATE_PAUSED;
|
||||
|
||||
// ignore if requesting twice same state
|
||||
if (desired_state_ == requested_state)
|
||||
return;
|
||||
|
||||
// accept request to the desired state
|
||||
desired_state_ = requested_state;
|
||||
|
||||
// if not ready yet, the requested state will be handled later
|
||||
if ( pipeline_ == nullptr )
|
||||
return;
|
||||
|
||||
// all ready, apply state change immediately
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("Stream %s Failed to play", std::to_string(id_).c_str());
|
||||
failed_ = true;
|
||||
}
|
||||
#ifdef STREAM_DEBUG
|
||||
else if (on)
|
||||
Log::Info("Stream %s Start", std::to_string(id_).c_str());
|
||||
else
|
||||
Log::Info("Stream %s Stop", std::to_string(id_).c_str());
|
||||
#endif
|
||||
|
||||
// activate live-source
|
||||
if (live_)
|
||||
gst_element_get_state (pipeline_, NULL, NULL, GST_CLOCK_TIME_NONE);
|
||||
|
||||
// reset time counter
|
||||
timecount_.reset();
|
||||
|
||||
}
|
||||
|
||||
bool Stream::isPlaying(bool testpipeline) const
|
||||
{
|
||||
// if not ready yet, answer with requested state
|
||||
if ( !testpipeline || pipeline_ == nullptr || !enabled_)
|
||||
return desired_state_ == GST_STATE_PLAYING;
|
||||
|
||||
// if ready, answer with actual state
|
||||
GstState state;
|
||||
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
|
||||
return state == GST_STATE_PLAYING;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Stream::init_texture(guint index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glGenTextures(1, &textureindex_);
|
||||
glBindTexture(GL_TEXTURE_2D, textureindex_);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width_, height_);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
// set pbo image size
|
||||
pbo_size_ = height_ * width_ * 4;
|
||||
|
||||
// create pixel buffer objects,
|
||||
if (pbo_[0])
|
||||
glDeleteBuffers(2, pbo_);
|
||||
glGenBuffers(2, pbo_);
|
||||
|
||||
for(int i = 0; i < 2; i++ ) {
|
||||
// create 2 PBOs
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[i]);
|
||||
// glBufferDataARB with NULL pointer reserves only memory space.
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW);
|
||||
// fill in with reset picture
|
||||
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
|
||||
if (ptr) {
|
||||
// update data directly on the mapped buffer
|
||||
memmove(ptr, frame_[index].vframe.data[0], pbo_size_);
|
||||
// release pointer to mapping buffer
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
else {
|
||||
// did not work, disable PBO
|
||||
glDeleteBuffers(2, pbo_);
|
||||
pbo_[0] = pbo_[1] = 0;
|
||||
pbo_size_ = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// should be good to go, wrap it up
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
pbo_index_ = 0;
|
||||
pbo_next_index_ = 1;
|
||||
|
||||
#ifdef STREAM_DEBUG
|
||||
Log::Info("Stream %s Use Pixel Buffer Object texturing.", std::to_string(id_).c_str());
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Stream::fill_texture(guint index)
|
||||
{
|
||||
// is this the first frame ?
|
||||
if (textureindex_ < 1)
|
||||
{
|
||||
// initialize texture
|
||||
init_texture(index);
|
||||
|
||||
}
|
||||
else {
|
||||
glBindTexture(GL_TEXTURE_2D, textureindex_);
|
||||
|
||||
// use dual Pixel Buffer Object
|
||||
if (pbo_size_ > 0) {
|
||||
// In dual PBO mode, increment current index first then get the next index
|
||||
pbo_index_ = (pbo_index_ + 1) % 2;
|
||||
pbo_next_index_ = (pbo_index_ + 1) % 2;
|
||||
|
||||
// bind PBO to read pixels
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_index_]);
|
||||
// copy pixels from PBO to texture object
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
// bind the next PBO to write pixels
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]);
|
||||
// See http://www.songho.ca/opengl/gl_pbo.html#map for more details
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW);
|
||||
// map the buffer object into client's memory
|
||||
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
|
||||
if (ptr) {
|
||||
// update data directly on the mapped buffer
|
||||
// NB : equivalent but faster (memmove instead of memcpy ?) than
|
||||
// glNamedBufferSubData(pboIds[nextIndex], 0, imgsize, vp->getBuffer())
|
||||
memmove(ptr, frame_[index].vframe.data[0], pbo_size_);
|
||||
|
||||
// release pointer to mapping buffer
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
// done with PBO
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
else {
|
||||
// without PBO, use standard opengl (slower)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::update()
|
||||
{
|
||||
// discard
|
||||
if (failed_)
|
||||
return;
|
||||
|
||||
// not ready yet
|
||||
if (!ready_)
|
||||
return;
|
||||
|
||||
// // prevent unnecessary updates: disabled or already filled image
|
||||
// if (!enabled_)
|
||||
// return;
|
||||
|
||||
// local variables before trying to update
|
||||
guint read_index = 0;
|
||||
bool need_loop = false;
|
||||
|
||||
// locked access to current index
|
||||
index_lock_.lock();
|
||||
// get the last frame filled from fill_frame()
|
||||
read_index = last_index_;
|
||||
// Do NOT miss and jump directly to a pre-roll
|
||||
for (guint i = 0; i < N_FRAME; ++i) {
|
||||
if (frame_[i].status == PREROLL) {
|
||||
read_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// unlock access to index change
|
||||
index_lock_.unlock();
|
||||
|
||||
// lock frame while reading it
|
||||
frame_[read_index].access.lock();
|
||||
|
||||
// do not fill a frame twice
|
||||
if (frame_[read_index].status != INVALID ) {
|
||||
|
||||
// is this an End-of-Stream frame ?
|
||||
if (frame_[read_index].status == EOS )
|
||||
{
|
||||
// will execute seek command below (after unlock)
|
||||
need_loop = true;
|
||||
}
|
||||
// otherwise just fill non-empty SAMPLE or PREROLL
|
||||
else if (frame_[read_index].full)
|
||||
{
|
||||
// fill the texture with the frame at reading index
|
||||
fill_texture(read_index);
|
||||
|
||||
// double update for pre-roll frame and dual PBO (ensure frame is displayed now)
|
||||
if (frame_[read_index].status == PREROLL && pbo_size_ > 0)
|
||||
fill_texture(read_index);
|
||||
}
|
||||
|
||||
// avoid reading it again
|
||||
frame_[read_index].status = INVALID;
|
||||
}
|
||||
|
||||
// unkock frame after reading it
|
||||
frame_[read_index].access.unlock();
|
||||
|
||||
if (need_loop) {
|
||||
// stop on end of stream
|
||||
play(false);
|
||||
}
|
||||
}
|
||||
|
||||
double Stream::updateFrameRate() const
|
||||
{
|
||||
return timecount_.frameRate();
|
||||
}
|
||||
|
||||
|
||||
// CALLBACKS
|
||||
|
||||
bool Stream::fill_frame(GstBuffer *buf, FrameStatus status)
|
||||
{
|
||||
// Log::Info("Stream fill frame");
|
||||
|
||||
// Do NOT overwrite an unread EOS
|
||||
if ( frame_[write_index_].status == EOS )
|
||||
write_index_ = (write_index_ + 1) % N_FRAME;
|
||||
|
||||
// lock access to frame
|
||||
frame_[write_index_].access.lock();
|
||||
|
||||
// always empty frame before filling it again
|
||||
if ( frame_[write_index_].full ) {
|
||||
if ( GST_MINI_OBJECT_REFCOUNT_VALUE( &frame_[write_index_].vframe.buffer->mini_object ) > 0)
|
||||
gst_video_frame_unmap(&frame_[write_index_].vframe);
|
||||
frame_[write_index_].full = false;
|
||||
}
|
||||
|
||||
// accept status of frame received
|
||||
frame_[write_index_].status = status;
|
||||
|
||||
// a buffer is given (not EOS)
|
||||
if (buf != NULL) {
|
||||
// get the frame from buffer
|
||||
if ( !gst_video_frame_map (&frame_[write_index_].vframe, &v_frame_video_info_, buf, GST_MAP_READ ) )
|
||||
{
|
||||
Log::Info("Stream %s Failed to map the video buffer", std::to_string(id_).c_str());
|
||||
// free access to frame & exit
|
||||
frame_[write_index_].status = INVALID;
|
||||
frame_[write_index_].access.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
// successfully filled the frame
|
||||
frame_[write_index_].full = true;
|
||||
|
||||
// validate frame format
|
||||
if( GST_VIDEO_INFO_IS_RGB(&(frame_[write_index_].vframe).info) && GST_VIDEO_INFO_N_PLANES(&(frame_[write_index_].vframe).info) == 1)
|
||||
{
|
||||
// set presentation time stamp
|
||||
frame_[write_index_].position = buf->pts;
|
||||
|
||||
}
|
||||
// full but invalid frame : will be deleted next iteration
|
||||
// (should never happen)
|
||||
else {
|
||||
Log::Info("Stream %s Received an Invalid frame", std::to_string(id_).c_str());
|
||||
frame_[write_index_].status = INVALID;
|
||||
frame_[write_index_].access.unlock();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// else; null buffer for EOS: give a position
|
||||
else {
|
||||
frame_[write_index_].status = EOS;
|
||||
#ifdef STREAM_DEBUG
|
||||
Log::Info("Stream %s Reached End Of Stream", std::to_string(id_).c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
// unlock access to frame
|
||||
frame_[write_index_].access.unlock();
|
||||
|
||||
// lock access to change current index (very quick)
|
||||
index_lock_.lock();
|
||||
// indicate update() that this is the last frame filled (and unlocked)
|
||||
last_index_ = write_index_;
|
||||
// unlock access to index change
|
||||
index_lock_.unlock();
|
||||
// for writing, we will access the next in stack
|
||||
write_index_ = (write_index_ + 1) % N_FRAME;
|
||||
|
||||
// calculate actual FPS of update
|
||||
timecount_.tic();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stream::callback_end_of_stream (GstAppSink *, gpointer p)
|
||||
{
|
||||
Stream *m = (Stream *)p;
|
||||
if (m && m->ready_) {
|
||||
m->fill_frame(NULL, Stream::EOS);
|
||||
}
|
||||
}
|
||||
|
||||
GstFlowReturn Stream::callback_new_preroll (GstAppSink *sink, gpointer p)
|
||||
{
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
|
||||
// blocking read pre-roll samples
|
||||
GstSample *sample = gst_app_sink_pull_preroll(sink);
|
||||
|
||||
// if got a valid sample
|
||||
if (sample != NULL) {
|
||||
// send frames to media player only if ready
|
||||
Stream *m = (Stream *)p;
|
||||
if (m && m->ready_) {
|
||||
|
||||
// get buffer from sample
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample);
|
||||
|
||||
// fill frame from buffer
|
||||
if ( !m->fill_frame(buf, Stream::PREROLL) )
|
||||
ret = GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
else
|
||||
ret = GST_FLOW_FLUSHING;
|
||||
|
||||
// release sample
|
||||
gst_sample_unref (sample);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GstFlowReturn Stream::callback_new_sample (GstAppSink *sink, gpointer p)
|
||||
{
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
|
||||
// if (gst_app_sink_is_eos (sink))
|
||||
// Log::Info("callback_new_sample got EOS");
|
||||
|
||||
// non-blocking read new sample
|
||||
GstSample *sample = gst_app_sink_pull_sample(sink);
|
||||
|
||||
// if got a valid sample
|
||||
if (sample != NULL && !gst_app_sink_is_eos (sink)) {
|
||||
|
||||
// send frames to media player only if ready
|
||||
Stream *m = (Stream *)p;
|
||||
if (m && m->ready_) {
|
||||
|
||||
// get buffer from sample (valid until sample is released)
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample) ;
|
||||
|
||||
// fill frame with buffer
|
||||
if ( !m->fill_frame(buf, Stream::SAMPLE) )
|
||||
ret = GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
else
|
||||
ret = GST_FLOW_FLUSHING;
|
||||
|
||||
// release sample
|
||||
gst_sample_unref (sample);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Stream::TimeCounter::TimeCounter() {
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Stream::TimeCounter::tic ()
|
||||
{
|
||||
// how long since last time
|
||||
GstClockTime t = gst_util_get_timestamp ();
|
||||
GstClockTime dt = t - last_time;
|
||||
|
||||
// one more frame since last time
|
||||
nbFrames++;
|
||||
|
||||
// calculate instantaneous framerate
|
||||
// Exponential moving averate with previous framerate to filter jitter (50/50)
|
||||
// The divition of frame/time is done on long integer GstClockTime, counting in microsecond
|
||||
// NB: factor 100 to get 0.01 precision
|
||||
fps = 0.5 * fps + 0.005 * static_cast<double>( ( 100 * GST_SECOND * nbFrames ) / dt );
|
||||
|
||||
// reset counter every second
|
||||
if ( dt >= GST_SECOND)
|
||||
{
|
||||
last_time = t;
|
||||
nbFrames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
GstClockTime Stream::TimeCounter::dt ()
|
||||
{
|
||||
GstClockTime t = gst_util_get_timestamp ();
|
||||
GstClockTime dt = t - tic_time;
|
||||
tic_time = t;
|
||||
|
||||
// return the instantaneous delta t
|
||||
return dt;
|
||||
}
|
||||
|
||||
void Stream::TimeCounter::reset ()
|
||||
{
|
||||
last_time = gst_util_get_timestamp ();;
|
||||
tic_time = last_time;
|
||||
nbFrames = 0;
|
||||
fps = 0.0;
|
||||
}
|
||||
|
||||
double Stream::TimeCounter::frameRate() const
|
||||
{
|
||||
return fps;
|
||||
}
|
||||
|
||||
118
StreamSource.cpp
@@ -1,118 +0,0 @@
|
||||
#include <sstream>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "StreamSource.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Decorations.h"
|
||||
#include "Stream.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
GenericStreamSource::GenericStreamSource() : StreamSource()
|
||||
{
|
||||
// create stream
|
||||
stream_ = new Stream;
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::EMPTY, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
void GenericStreamSource::setDescription(const std::string &desc)
|
||||
{
|
||||
Log::Notify("Creating Source with Stream description '%s'", desc.c_str());
|
||||
|
||||
stream_->open(desc);
|
||||
stream_->play(true);
|
||||
}
|
||||
|
||||
void GenericStreamSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
StreamSource::StreamSource() : Source(), stream_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
StreamSource::~StreamSource()
|
||||
{
|
||||
// delete stream
|
||||
if (stream_)
|
||||
delete stream_;
|
||||
}
|
||||
|
||||
bool StreamSource::failed() const
|
||||
{
|
||||
return (stream_ != nullptr && stream_->failed() );
|
||||
}
|
||||
|
||||
uint StreamSource::texture() const
|
||||
{
|
||||
if (stream_ == nullptr)
|
||||
return Resource::getTextureBlack();
|
||||
else
|
||||
return stream_->texture();
|
||||
}
|
||||
|
||||
void StreamSource::init()
|
||||
{
|
||||
if ( stream_ && stream_->isOpen() ) {
|
||||
|
||||
// update video
|
||||
stream_->update();
|
||||
|
||||
// once the texture of media player is created
|
||||
if (stream_->texture() != Resource::getTextureBlack()) {
|
||||
|
||||
// get the texture index from media player, apply it to the media surface
|
||||
texturesurface_->setTextureIndex( stream_->texture() );
|
||||
|
||||
// create Frame buffer matching size of media player
|
||||
float height = float(stream_->width()) / stream_->aspectRatio();
|
||||
FrameBuffer *renderbuffer = new FrameBuffer(stream_->width(), (uint)height, true);
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// force update of activation mode
|
||||
active_ = true;
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
Log::Info("Source '%s' linked to Stream %s", name().c_str(), std::to_string(stream_->id()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamSource::setActive (bool on)
|
||||
{
|
||||
bool was_active = active_;
|
||||
|
||||
Source::setActive(on);
|
||||
|
||||
// change status of media player (only if status changed)
|
||||
if ( active_ != was_active ) {
|
||||
if (stream_)
|
||||
stream_->enable(active_);
|
||||
}
|
||||
}
|
||||
|
||||
void StreamSource::update(float dt)
|
||||
{
|
||||
Source::update(dt);
|
||||
|
||||
// update stream
|
||||
if (stream_)
|
||||
stream_->update();
|
||||
}
|
||||
404
Streamer.cpp
@@ -1,404 +0,0 @@
|
||||
#include <thread>
|
||||
#include <sstream>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// standalone image loader
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
// gstreamer
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
//osc
|
||||
#include "osc/OscOutboundPacketStream.h"
|
||||
|
||||
#include "Settings.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "defines.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "Session.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "Connection.h"
|
||||
#include "NetworkToolkit.h"
|
||||
#include "Streamer.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define STREAMER_DEBUG
|
||||
#endif
|
||||
|
||||
void StreamingRequestListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint )
|
||||
{
|
||||
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
|
||||
remoteEndpoint.AddressAndPortAsString(sender);
|
||||
|
||||
try{
|
||||
|
||||
if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_STREAM_REQUEST) == 0 ){
|
||||
|
||||
#ifdef STREAMER_DEBUG
|
||||
Log::Info("%s wants a stream.", sender);
|
||||
#endif
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
int reply_to_port = (arg++)->AsInt32();
|
||||
const char *client_name = (arg++)->AsString();
|
||||
if (Streaming::manager().enabled())
|
||||
Streaming::manager().addStream(sender, reply_to_port, client_name);
|
||||
else
|
||||
Streaming::manager().refuseStream(sender, reply_to_port);
|
||||
}
|
||||
else if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_STREAM_DISCONNECT) == 0 ){
|
||||
|
||||
#ifdef STREAMER_DEBUG
|
||||
Log::Info("%s does not need streaming anymore.", sender);
|
||||
#endif
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
int port = (arg++)->AsInt32();
|
||||
Streaming::manager().removeStream(sender, port);
|
||||
|
||||
}
|
||||
}
|
||||
catch( osc::Exception& e ){
|
||||
// any parsing errors such as unexpected argument types, or
|
||||
// missing arguments get thrown as exceptions.
|
||||
Log::Info("error while parsing message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void wait_for_request_(UdpListeningReceiveSocket *receiver)
|
||||
{
|
||||
receiver->Run();
|
||||
}
|
||||
|
||||
Streaming::Streaming() : enabled_(false)
|
||||
{
|
||||
int port = Connection::manager().info().port_stream_request;
|
||||
receiver_ = new UdpListeningReceiveSocket(IpEndpointName( IpEndpointName::ANY_ADDRESS, port ), &listener_ );
|
||||
|
||||
std::thread(wait_for_request_, receiver_).detach();
|
||||
}
|
||||
|
||||
Streaming::~Streaming()
|
||||
{
|
||||
if (receiver_!=nullptr) {
|
||||
receiver_->Break();
|
||||
delete receiver_;
|
||||
}
|
||||
}
|
||||
|
||||
bool Streaming::busy()
|
||||
{
|
||||
bool b = false;
|
||||
|
||||
streamers_lock_.lock();
|
||||
std::vector<VideoStreamer *>::const_iterator sit = streamers_.begin();
|
||||
for (; sit != streamers_.end() && !b; sit++)
|
||||
b = (*sit)->busy() ;
|
||||
streamers_lock_.unlock();
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::string> Streaming::listStreams()
|
||||
{
|
||||
std::vector<std::string> ls;
|
||||
|
||||
streamers_lock_.lock();
|
||||
std::vector<VideoStreamer *>::const_iterator sit = streamers_.begin();
|
||||
for (; sit != streamers_.end(); sit++)
|
||||
ls.push_back( (*sit)->info() );
|
||||
streamers_lock_.unlock();
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
void Streaming::enable(bool on)
|
||||
{
|
||||
if (on) {
|
||||
// accept streaming requests
|
||||
enabled_ = true;
|
||||
Log::Info("Accepting stream requests to %s.", Connection::manager().info().name.c_str());
|
||||
}
|
||||
else {
|
||||
// refuse streaming requests
|
||||
enabled_ = false;
|
||||
// ending and removing all streaming
|
||||
streamers_lock_.lock();
|
||||
for (auto sit = streamers_.begin(); sit != streamers_.end(); sit=streamers_.erase(sit))
|
||||
(*sit)->stop();
|
||||
streamers_lock_.unlock();
|
||||
Log::Info("Refusing stream requests to %s. No streaming ongoing.", Connection::manager().info().name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Streaming::removeStream(const std::string &sender, int port)
|
||||
{
|
||||
// get ip of sender
|
||||
std::string sender_ip = sender.substr(0, sender.find_last_of(":"));
|
||||
|
||||
// parse the list for a streamers matching IP and port
|
||||
streamers_lock_.lock();
|
||||
std::vector<VideoStreamer *>::const_iterator sit = streamers_.begin();
|
||||
for (; sit != streamers_.end(); sit++){
|
||||
NetworkToolkit::StreamConfig config = (*sit)->config_;
|
||||
if (config.client_address.compare(sender_ip) == 0 && config.port == port ) {
|
||||
#ifdef STREAMER_DEBUG
|
||||
Log::Info("Ending streaming to %s:%d", config.client_address.c_str(), config.port);
|
||||
#endif
|
||||
// match: stop this streamer
|
||||
(*sit)->stop();
|
||||
// remove from list
|
||||
streamers_.erase(sit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
streamers_lock_.unlock();
|
||||
|
||||
}
|
||||
|
||||
void Streaming::removeStreams(const std::string &clientname)
|
||||
{
|
||||
// remove all streamers matching given IP
|
||||
streamers_lock_.lock();
|
||||
std::vector<VideoStreamer *>::const_iterator sit = streamers_.begin();
|
||||
while ( sit != streamers_.end() ){
|
||||
NetworkToolkit::StreamConfig config = (*sit)->config_;
|
||||
if (config.client_name.compare(clientname) == 0) {
|
||||
#ifdef STREAMER_DEBUG
|
||||
Log::Info("Ending streaming to %s:%d", config.client_address.c_str(), config.port);
|
||||
#endif
|
||||
// match: stop this streamer
|
||||
(*sit)->stop();
|
||||
// remove from list
|
||||
sit = streamers_.erase(sit);
|
||||
}
|
||||
else
|
||||
sit++;
|
||||
}
|
||||
streamers_lock_.unlock();
|
||||
}
|
||||
|
||||
void Streaming::refuseStream(const std::string &sender, int reply_to)
|
||||
{
|
||||
// get ip of client
|
||||
std::string sender_ip = sender.substr(0, sender.find_last_of(":"));
|
||||
// prepare to reply to client
|
||||
IpEndpointName host( sender_ip.c_str(), reply_to );
|
||||
UdpTransmitSocket socket( host );
|
||||
// build OSC message
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
p.Clear();
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_STREAM_REJECT );
|
||||
p << osc::EndMessage;
|
||||
// send OSC message to client
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
// inform user
|
||||
Log::Warning("A connection request for streaming came and was rejected.\nYou can Accept connections from the Output window.");
|
||||
}
|
||||
|
||||
void Streaming::addStream(const std::string &sender, int reply_to, const std::string &clientname)
|
||||
{
|
||||
// get ip of client
|
||||
std::string sender_ip = sender.substr(0, sender.find_last_of(":"));
|
||||
// get port used to send the request
|
||||
std::string sender_port = sender.substr(sender.find_last_of(":") + 1);
|
||||
|
||||
// prepare to reply to client
|
||||
IpEndpointName host( sender_ip.c_str(), reply_to );
|
||||
UdpTransmitSocket socket( host );
|
||||
|
||||
// prepare an offer
|
||||
NetworkToolkit::StreamConfig conf;
|
||||
conf.client_address = sender_ip;
|
||||
conf.client_name = clientname;
|
||||
conf.port = std::stoi(sender_port); // this port seems free, so re-use it!
|
||||
conf.width = FrameGrabbing::manager().width();
|
||||
conf.height = FrameGrabbing::manager().height();
|
||||
|
||||
// TEMP DISABLED : TODO Fix snap to allow system wide shared access
|
||||
|
||||
// offer SHM if same IP that our host IP (i.e. on the same machine)
|
||||
// if( NetworkToolkit::is_host_ip(conf.client_address) )
|
||||
// conf.protocol = NetworkToolkit::SHM_RAW;
|
||||
// // any other IP : offer network streaming
|
||||
// else
|
||||
conf.protocol = NetworkToolkit::UDP_JPEG;
|
||||
|
||||
// build OSC message
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
p.Clear();
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_STREAM_OFFER );
|
||||
p << conf.port;
|
||||
p << (int) conf.protocol;
|
||||
p << conf.width << conf.height;
|
||||
p << osc::EndMessage;
|
||||
|
||||
// send OSC message to client
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
|
||||
#ifdef STREAMER_DEBUG
|
||||
Log::Info("Replying to %s:%d", sender_ip.c_str(), reply_to);
|
||||
Log::Info("Starting streaming to %s:%d", sender_ip.c_str(), conf.port);
|
||||
#endif
|
||||
|
||||
// create streamer & remember it
|
||||
VideoStreamer *streamer = new VideoStreamer(conf);
|
||||
streamers_lock_.lock();
|
||||
streamers_.push_back(streamer);
|
||||
streamers_lock_.unlock();
|
||||
|
||||
// start streamer
|
||||
FrameGrabbing::manager().add(streamer);
|
||||
}
|
||||
|
||||
|
||||
VideoStreamer::VideoStreamer(NetworkToolkit::StreamConfig conf): FrameGrabber(), config_(conf)
|
||||
{
|
||||
}
|
||||
|
||||
void VideoStreamer::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
|
||||
// check that config matches the given buffer properties
|
||||
gint w = 0, h = 0;
|
||||
GstStructure *capstruct = gst_caps_get_structure (caps, 0);
|
||||
if ( gst_structure_has_field (capstruct, "width"))
|
||||
gst_structure_get_int (capstruct, "width", &w);
|
||||
if ( gst_structure_has_field (capstruct, "height"))
|
||||
gst_structure_get_int (capstruct, "height", &h);
|
||||
if ( config_.width != w || config_.height != h) {
|
||||
Log::Warning("Streaming cannot start: given frames (%d x %d) incompatible with stream (%d x %d)",
|
||||
w, w, config_.width, config_.height);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// prevent eroneous protocol values
|
||||
if (config_.protocol < 0 || config_.protocol >= NetworkToolkit::DEFAULT)
|
||||
config_.protocol = NetworkToolkit::UDP_JPEG;
|
||||
|
||||
// create a gstreamer pipeline
|
||||
std::string description = "appsrc name=src ! videoconvert ! ";
|
||||
description += NetworkToolkit::protocol_send_pipeline[config_.protocol];
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("VideoStreamer Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// setup streaming sink
|
||||
if (config_.protocol == NetworkToolkit::UDP_JPEG || config_.protocol == NetworkToolkit::UDP_H264) {
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"host", config_.client_address.c_str(),
|
||||
"port", config_.port, NULL);
|
||||
}
|
||||
else if (config_.protocol == NetworkToolkit::SHM_RAW) {
|
||||
std::string path = SystemToolkit::full_filename(SystemToolkit::temp_path(), "shm");
|
||||
path += std::to_string(config_.port);
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"socket-path", path.c_str(), NULL);
|
||||
}
|
||||
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
// "do-timestamp", TRUE,
|
||||
NULL);
|
||||
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
callbacks.need_data = FrameGrabber::callback_need_data;
|
||||
callbacks.enough_data = FrameGrabber::callback_enough_data;
|
||||
callbacks.seek_data = NULL; // stream type is not seekable
|
||||
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("VideoStreamer Could not configure capture source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// start recording
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("VideoStreamer failed");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// all good
|
||||
Log::Notify("Streaming to %s.", config_.client_name.c_str());
|
||||
|
||||
// start streaming !!
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void VideoStreamer::terminate()
|
||||
{
|
||||
// send EOS
|
||||
gst_app_src_end_of_stream (src_);
|
||||
|
||||
// make sure the shared memory socket is deleted
|
||||
if (config_.protocol == NetworkToolkit::SHM_RAW) {
|
||||
std::string path = SystemToolkit::full_filename(SystemToolkit::temp_path(), "shm");
|
||||
path += std::to_string(config_.port);
|
||||
SystemToolkit::remove_file(path);
|
||||
}
|
||||
|
||||
Log::Notify("Streaming to %s finished after %s s.", config_.client_name.c_str(),
|
||||
GstToolkit::time_to_string(timestamp_).c_str());
|
||||
}
|
||||
|
||||
void VideoStreamer::stop ()
|
||||
{
|
||||
// stop recording
|
||||
FrameGrabber::stop ();
|
||||
|
||||
// force finished
|
||||
finished_ = true;
|
||||
}
|
||||
|
||||
std::string VideoStreamer::info() const
|
||||
{
|
||||
std::ostringstream ret;
|
||||
if (active_) {
|
||||
ret << NetworkToolkit::protocol_name[config_.protocol];
|
||||
ret << " to ";
|
||||
ret << config_.client_name;
|
||||
}
|
||||
else
|
||||
ret << "Streaming terminated.";
|
||||
return ret.str();
|
||||
}
|
||||
85
Streamer.h
@@ -1,85 +0,0 @@
|
||||
#ifndef STREAMER_H
|
||||
#define STREAMER_H
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
#include "osc/OscReceivedElements.h"
|
||||
#include "osc/OscPacketListener.h"
|
||||
#include "ip/UdpSocket.h"
|
||||
|
||||
#include "NetworkToolkit.h"
|
||||
#include "FrameGrabber.h"
|
||||
|
||||
class Session;
|
||||
class VideoStreamer;
|
||||
|
||||
class StreamingRequestListener : public osc::OscPacketListener {
|
||||
|
||||
protected:
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
};
|
||||
|
||||
class Streaming
|
||||
{
|
||||
friend class StreamingRequestListener;
|
||||
|
||||
// Private Constructor
|
||||
Streaming();
|
||||
Streaming(Streaming const& copy); // Not Implemented
|
||||
Streaming& operator=(Streaming const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
|
||||
static Streaming& manager()
|
||||
{
|
||||
// The only instance
|
||||
static Streaming _instance;
|
||||
return _instance;
|
||||
}
|
||||
~Streaming();
|
||||
|
||||
void enable(bool on);
|
||||
inline bool enabled() const { return enabled_; }
|
||||
void removeStreams(const std::string &clientname);
|
||||
void removeStream(const std::string &sender, int port);
|
||||
|
||||
bool busy();
|
||||
std::vector<std::string> listStreams();
|
||||
|
||||
protected:
|
||||
void addStream(const std::string &sender, int reply_to, const std::string &clientname);
|
||||
void refuseStream(const std::string &sender, int reply_to);
|
||||
|
||||
private:
|
||||
|
||||
bool enabled_;
|
||||
StreamingRequestListener listener_;
|
||||
UdpListeningReceiveSocket *receiver_;
|
||||
|
||||
std::vector<VideoStreamer *> streamers_;
|
||||
std::mutex streamers_lock_;
|
||||
};
|
||||
|
||||
class VideoStreamer : public FrameGrabber
|
||||
{
|
||||
friend class Streaming;
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
void stop() override;
|
||||
|
||||
// connection information
|
||||
NetworkToolkit::StreamConfig config_;
|
||||
|
||||
public:
|
||||
|
||||
VideoStreamer(NetworkToolkit::StreamConfig conf);
|
||||
std::string info() const override;
|
||||
|
||||
};
|
||||
|
||||
#endif // STREAMER_H
|
||||
@@ -1,399 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
|
||||
#include <locale>
|
||||
#include <unicode/ustream.h>
|
||||
#include <unicode/translit.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#define mkdir(dir, mode) _mkdir(dir)
|
||||
#include <include/dirent.h>
|
||||
#include <sys/resource.h>
|
||||
#define PATH_SEP '\\'
|
||||
#define PATH_SETTINGS "\\\AppData\\Roaming\\"
|
||||
#elif defined(LINUX) or defined(APPLE)
|
||||
#include <sys/resource.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <pwd.h>
|
||||
#include <dirent.h>
|
||||
#define PATH_SEP '/'
|
||||
#endif
|
||||
|
||||
#if defined(APPLE)
|
||||
#define PATH_SETTINGS "/Library/Application Support/"
|
||||
#include <mach/task.h>
|
||||
#include <mach/mach_init.h>
|
||||
#elif defined(LINUX)
|
||||
#include <sys/sysinfo.h>
|
||||
#define PATH_SETTINGS "/.config/"
|
||||
#endif
|
||||
|
||||
#include "defines.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
/// The amount of memory currently being used by this process, in bytes.
|
||||
/// it will try to report the resident set in RAM
|
||||
long SystemToolkit::memory_usage()
|
||||
{
|
||||
#if defined(LINUX)
|
||||
// Grabbing info directly from the /proc pseudo-filesystem. Reading from
|
||||
// /proc/self/statm gives info on your own process, as one line of
|
||||
// numbers that are: virtual mem program size, resident set size,
|
||||
// shared pages, text/code, data/stack, library, dirty pages. The
|
||||
// mem sizes should all be multiplied by the page size.
|
||||
size_t size = 0;
|
||||
FILE *file = fopen("/proc/self/statm", "r");
|
||||
if (file) {
|
||||
unsigned long m = 0;
|
||||
int ret = 0;
|
||||
ret = fscanf (file, "%lu", &m); // virtual mem program size,
|
||||
ret = fscanf (file, "%lu", &m); // resident set size,
|
||||
fclose (file);
|
||||
if (ret>0)
|
||||
size = (size_t)m * getpagesize();
|
||||
}
|
||||
return (long)size;
|
||||
|
||||
#elif defined(APPLE)
|
||||
// Inspired from
|
||||
// http://miknight.blogspot.com/2005/11/resident-set-size-in-mac-os-x.html
|
||||
struct task_basic_info t_info;
|
||||
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
|
||||
task_info(current_task(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
|
||||
return t_info.resident_size;
|
||||
|
||||
#elif defined(WIN32)
|
||||
// According to MSDN...
|
||||
PROCESS_MEMORY_COUNTERS counters;
|
||||
if (GetProcessMemoryInfo (GetCurrentProcess(), &counters, sizeof (counters)))
|
||||
return counters.PagefileUsage;
|
||||
else return 0;
|
||||
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
long SystemToolkit::memory_max_usage() {
|
||||
|
||||
struct rusage r_usage;
|
||||
getrusage(RUSAGE_SELF,&r_usage);
|
||||
return r_usage.ru_maxrss;
|
||||
// return r_usage.ru_isrss;
|
||||
}
|
||||
|
||||
string SystemToolkit::byte_to_string(long b)
|
||||
{
|
||||
double numbytes = static_cast<double>(b);
|
||||
ostringstream oss;
|
||||
|
||||
std::list<std::string> list = {" Bytes", " KB", " MB", " GB", " TB"};
|
||||
std::list<std::string>::iterator i = list.begin();
|
||||
|
||||
while(numbytes >= 1024.0 && i != list.end())
|
||||
{
|
||||
i++;
|
||||
numbytes /= 1024.0;
|
||||
}
|
||||
oss << std::fixed << std::setprecision(2) << numbytes << *i;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
string SystemToolkit::bits_to_string(long b)
|
||||
{
|
||||
double numbytes = static_cast<double>(b);
|
||||
ostringstream oss;
|
||||
|
||||
std::list<std::string> list = {" bit", " Kbit", " Mbit", " Gbit", " Tbit"};
|
||||
std::list<std::string>::iterator i = list.begin();
|
||||
|
||||
while(numbytes >= 1000.0 && i != list.end())
|
||||
{
|
||||
i++;
|
||||
numbytes /= 1000.0;
|
||||
}
|
||||
oss << std::fixed << std::setprecision(2) << numbytes << *i;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
|
||||
string SystemToolkit::date_time_string()
|
||||
{
|
||||
chrono::system_clock::time_point now = chrono::system_clock::now();
|
||||
time_t t = chrono::system_clock::to_time_t(now);
|
||||
tm* datetime = localtime(&t);
|
||||
|
||||
auto duration = now.time_since_epoch();
|
||||
auto millis = chrono::duration_cast<chrono::milliseconds>(duration).count() % 1000;
|
||||
|
||||
ostringstream oss;
|
||||
oss << setw(4) << setfill('0') << to_string(datetime->tm_year + 1900);
|
||||
oss << setw(2) << setfill('0') << to_string(datetime->tm_mon + 1);
|
||||
oss << setw(2) << setfill('0') << to_string(datetime->tm_mday );
|
||||
oss << setw(2) << setfill('0') << to_string(datetime->tm_hour );
|
||||
oss << setw(2) << setfill('0') << to_string(datetime->tm_min );
|
||||
oss << setw(2) << setfill('0') << to_string(datetime->tm_sec );
|
||||
oss << setw(3) << setfill('0') << to_string(millis);
|
||||
|
||||
// fixed length string (17 chars) YYYYMMDDHHmmssiii
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
string SystemToolkit::filename(const string& path)
|
||||
{
|
||||
return path.substr(path.find_last_of(PATH_SEP) + 1);
|
||||
}
|
||||
|
||||
string SystemToolkit::base_filename(const string& path)
|
||||
{
|
||||
string basefilename = SystemToolkit::filename(path);
|
||||
const size_t period_idx = basefilename.rfind('.');
|
||||
if (string::npos != period_idx)
|
||||
{
|
||||
basefilename.erase(period_idx);
|
||||
}
|
||||
return basefilename;
|
||||
}
|
||||
|
||||
string SystemToolkit::path_filename(const string& path)
|
||||
{
|
||||
return path.substr(0, path.find_last_of(PATH_SEP) + 1);
|
||||
}
|
||||
|
||||
string SystemToolkit::trunc_filename(const string& path, int lenght)
|
||||
{
|
||||
string trunc = path;
|
||||
int l = path.size();
|
||||
if ( l > lenght ) {
|
||||
trunc = string("...") + path.substr( l - lenght + 3 );
|
||||
}
|
||||
return trunc;
|
||||
}
|
||||
|
||||
string SystemToolkit::extension_filename(const string& filename)
|
||||
{
|
||||
string ext = filename.substr(filename.find_last_of(".") + 1);
|
||||
return ext;
|
||||
}
|
||||
|
||||
std::string SystemToolkit::home_path()
|
||||
{
|
||||
// 1. find home
|
||||
char *mHomePath;
|
||||
// try the system user info
|
||||
// NB: avoids depending on changes of the $HOME env. variable
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd)
|
||||
mHomePath = pwd->pw_dir;
|
||||
else
|
||||
// try the $HOME environment variable
|
||||
mHomePath = getenv("HOME");
|
||||
|
||||
return string(mHomePath) + PATH_SEP;
|
||||
}
|
||||
|
||||
|
||||
std::string SystemToolkit::cwd_path()
|
||||
{
|
||||
char mCwdPath[PATH_MAX];
|
||||
|
||||
if (getcwd(mCwdPath, sizeof(mCwdPath)) != NULL)
|
||||
return string(mCwdPath) + PATH_SEP;
|
||||
else
|
||||
return string();
|
||||
}
|
||||
|
||||
std::string SystemToolkit::username()
|
||||
{
|
||||
// 1. find home
|
||||
char *user;
|
||||
// try the system user info
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd)
|
||||
user = pwd->pw_name;
|
||||
else
|
||||
// try the $USER environment variable
|
||||
user = getenv("USER");
|
||||
|
||||
return string(user);
|
||||
}
|
||||
|
||||
bool SystemToolkit::create_directory(const string& path)
|
||||
{
|
||||
return !mkdir(path.c_str(), 0755) || errno == EEXIST;
|
||||
|
||||
// TODO : verify WIN32 implementation
|
||||
}
|
||||
|
||||
bool SystemToolkit::remove_file(const string& path)
|
||||
{
|
||||
bool ret = true;
|
||||
if (file_exists(path)) {
|
||||
ret = (remove(path.c_str()) == 0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
// TODO : verify WIN32 implementation
|
||||
}
|
||||
|
||||
string SystemToolkit::settings_path()
|
||||
{
|
||||
// start from home folder
|
||||
// NB: use the env.variable $HOME to allow system to specify
|
||||
// another directory (e.g. inside a snap)
|
||||
string home(getenv("HOME"));
|
||||
|
||||
// 2. try to access user settings folder
|
||||
string settingspath = home + PATH_SETTINGS;
|
||||
if (SystemToolkit::file_exists(settingspath)) {
|
||||
// good, we have a place to put the settings file
|
||||
// settings should be in 'vimix' subfolder
|
||||
settingspath += APP_NAME;
|
||||
|
||||
// 3. create the vmix subfolder in settings folder if not existing already
|
||||
if ( !SystemToolkit::file_exists(settingspath)) {
|
||||
if ( !create_directory(settingspath) )
|
||||
// fallback to home if settings path cannot be created
|
||||
settingspath = home;
|
||||
}
|
||||
|
||||
return settingspath;
|
||||
}
|
||||
else {
|
||||
// fallback to home if settings path does not exists
|
||||
return home;
|
||||
}
|
||||
}
|
||||
|
||||
string SystemToolkit::temp_path()
|
||||
{
|
||||
string temp;
|
||||
|
||||
const char *tmpdir = getenv("TMPDIR");
|
||||
if (tmpdir)
|
||||
temp = std::string(tmpdir);
|
||||
else
|
||||
temp = std::string( P_tmpdir );
|
||||
|
||||
temp += PATH_SEP;
|
||||
return temp;
|
||||
// TODO : verify WIN32 implementation
|
||||
}
|
||||
|
||||
string SystemToolkit::full_filename(const std::string& path, const string &filename)
|
||||
{
|
||||
string fullfilename = path;
|
||||
fullfilename += PATH_SEP;
|
||||
fullfilename += filename;
|
||||
|
||||
return fullfilename;
|
||||
}
|
||||
|
||||
bool SystemToolkit::file_exists(const string& path)
|
||||
{
|
||||
if (path.empty())
|
||||
return false;
|
||||
|
||||
return access(path.c_str(), R_OK) == 0;
|
||||
|
||||
// TODO : WIN32 implementation
|
||||
}
|
||||
|
||||
|
||||
// tests if dir is a directory and return its path, empty string otherwise
|
||||
std::string SystemToolkit::path_directory(const std::string& path)
|
||||
{
|
||||
string directorypath = "";
|
||||
|
||||
DIR *dir;
|
||||
if ((dir = opendir (path.c_str())) != NULL) {
|
||||
directorypath = path + PATH_SEP;
|
||||
closedir (dir);
|
||||
}
|
||||
|
||||
return directorypath;
|
||||
}
|
||||
|
||||
list<string> SystemToolkit::list_directory(const string& path, const string& filter)
|
||||
{
|
||||
list<string> ls;
|
||||
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
if ((dir = opendir (path.c_str())) != NULL) {
|
||||
// list all the files and directories within directory
|
||||
while ((ent = readdir (dir)) != NULL) {
|
||||
if ( ent->d_type == DT_REG)
|
||||
{
|
||||
string filename = string(ent->d_name);
|
||||
if ( extension_filename(filename) == filter)
|
||||
ls.push_back( full_filename(path, filename) );
|
||||
}
|
||||
}
|
||||
closedir (dir);
|
||||
}
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
void SystemToolkit::open(const string& url)
|
||||
{
|
||||
#ifdef WIN32
|
||||
ShellExecuteA( nullptr, nullptr, url.c_str(), nullptr, nullptr, 0 );
|
||||
#elif defined APPLE
|
||||
char buf[2048];
|
||||
sprintf( buf, "open '%s'", url.c_str() );
|
||||
system( buf );
|
||||
#else
|
||||
char buf[2048];
|
||||
sprintf( buf, "xdg-open '%s'", url.c_str() );
|
||||
int r = system( buf );
|
||||
#endif
|
||||
}
|
||||
|
||||
void SystemToolkit::execute(const string& command)
|
||||
{
|
||||
#ifdef WIN32
|
||||
ShellExecuteA( nullptr, nullptr, url.c_str(), nullptr, nullptr, 0 );
|
||||
#elif defined APPLE
|
||||
int r = system( command.c_str() );
|
||||
#else
|
||||
int r = system( command.c_str() );
|
||||
#endif
|
||||
}
|
||||
// example :
|
||||
// std::thread (SystemToolkit::execute,
|
||||
// "gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink").detach();;
|
||||
|
||||
|
||||
// Using ICU transliteration :
|
||||
// https://unicode-org.github.io/icu/userguide/transforms/general/#icu-transliterators
|
||||
|
||||
std::string SystemToolkit::transliterate(std::string input)
|
||||
{
|
||||
auto ucs = icu::UnicodeString::fromUTF8(input);
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
icu::Transliterator *firstTrans = icu::Transliterator::createInstance(
|
||||
"any-NFKD ; [:Nonspacing Mark:] Remove; NFKC; Latin", UTRANS_FORWARD, status);
|
||||
firstTrans->transliterate(ucs);
|
||||
|
||||
icu::Transliterator *secondTrans = icu::Transliterator::createInstance(
|
||||
"any-NFKD ; [:Nonspacing Mark:] Remove; [@!#$*%~] Remove; NFKC", UTRANS_FORWARD, status);
|
||||
secondTrans->transliterate(ucs);
|
||||
|
||||
std::ostringstream output;
|
||||
output << ucs;
|
||||
return output.str();
|
||||
}
|
||||
1372
TextureView.cpp
352
Timeline.cpp
@@ -1,352 +0,0 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Timeline.h"
|
||||
|
||||
struct includesTime: public std::unary_function<TimeInterval, bool>
|
||||
{
|
||||
inline bool operator()(const TimeInterval s) const
|
||||
{
|
||||
return s.includes(_t);
|
||||
}
|
||||
|
||||
includesTime(GstClockTime t) : _t(t) { }
|
||||
|
||||
private:
|
||||
GstClockTime _t;
|
||||
};
|
||||
|
||||
Timeline::Timeline()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
Timeline::~Timeline()
|
||||
{
|
||||
}
|
||||
|
||||
Timeline& Timeline::operator = (const Timeline& b)
|
||||
{
|
||||
if (this != &b) {
|
||||
this->timing_ = b.timing_;
|
||||
this->step_ = b.step_;
|
||||
this->gaps_ = b.gaps_;
|
||||
this->gaps_array_need_update_ = b.gaps_array_need_update_;
|
||||
memcpy( this->gapsArray_, b.gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
memcpy( this->fadingArray_, b.fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Timeline::reset()
|
||||
{
|
||||
// reset timing
|
||||
timing_.reset();
|
||||
timing_.begin = 0;
|
||||
first_ = 0;
|
||||
step_ = GST_CLOCK_TIME_NONE;
|
||||
|
||||
clearGaps();
|
||||
clearFading();
|
||||
}
|
||||
|
||||
bool Timeline::is_valid()
|
||||
{
|
||||
return timing_.is_valid() && step_ != GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
|
||||
void Timeline::setFirst(GstClockTime first)
|
||||
{
|
||||
first_ = first;
|
||||
}
|
||||
|
||||
void Timeline::setEnd(GstClockTime end)
|
||||
{
|
||||
timing_.end = end;
|
||||
}
|
||||
|
||||
void Timeline::setStep(GstClockTime dt)
|
||||
{
|
||||
step_ = dt;
|
||||
}
|
||||
|
||||
void Timeline::setTiming(TimeInterval interval, GstClockTime step)
|
||||
{
|
||||
timing_ = interval;
|
||||
if (step != GST_CLOCK_TIME_NONE)
|
||||
step_ = step;
|
||||
}
|
||||
|
||||
GstClockTime Timeline::next(GstClockTime time) const
|
||||
{
|
||||
GstClockTime next_time = time;
|
||||
|
||||
TimeInterval gap;
|
||||
if (gapAt(time, gap) && gap.is_valid())
|
||||
next_time = gap.end;
|
||||
|
||||
return next_time;
|
||||
}
|
||||
|
||||
GstClockTime Timeline::previous(GstClockTime time) const
|
||||
{
|
||||
GstClockTime prev_time = time;
|
||||
TimeInterval gap;
|
||||
if (gapAt(time, gap) && gap.is_valid())
|
||||
prev_time = gap.begin;
|
||||
|
||||
return prev_time;
|
||||
}
|
||||
|
||||
float *Timeline::gapsArray()
|
||||
{
|
||||
if (gaps_array_need_update_) {
|
||||
fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY);
|
||||
}
|
||||
return gapsArray_;
|
||||
}
|
||||
|
||||
void Timeline::update()
|
||||
{
|
||||
updateGapsFromArray(gapsArray_, MAX_TIMELINE_ARRAY);
|
||||
gaps_array_need_update_ = false;
|
||||
}
|
||||
|
||||
bool Timeline::gapAt(const GstClockTime t, TimeInterval &gap) const
|
||||
{
|
||||
TimeIntervalSet::const_iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t));
|
||||
|
||||
if ( g != gaps_.end() ) {
|
||||
gap = (*g);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Timeline::addGap(GstClockTime begin, GstClockTime end)
|
||||
{
|
||||
return addGap( TimeInterval(begin, end) );
|
||||
}
|
||||
|
||||
bool Timeline::addGap(TimeInterval s)
|
||||
{
|
||||
if ( s.is_valid() ) {
|
||||
gaps_array_need_update_ = true;
|
||||
return gaps_.insert(s).second;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Timeline::setGaps(TimeIntervalSet g)
|
||||
{
|
||||
gaps_array_need_update_ = true;
|
||||
gaps_ = g;
|
||||
}
|
||||
|
||||
bool Timeline::removeGaptAt(GstClockTime t)
|
||||
{
|
||||
TimeIntervalSet::const_iterator s = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t));
|
||||
|
||||
if ( s != gaps_.end() ) {
|
||||
gaps_.erase(s);
|
||||
gaps_array_need_update_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
TimeIntervalSet Timeline::sections() const
|
||||
{
|
||||
TimeIntervalSet sec;
|
||||
|
||||
GstClockTime begin_sec = timing_.begin;
|
||||
|
||||
if (gaps_.size() > 0) {
|
||||
|
||||
auto it = gaps_.begin();
|
||||
if ((*it).begin == begin_sec) {
|
||||
begin_sec = (*it).end;
|
||||
++it;
|
||||
}
|
||||
|
||||
for (; it != gaps_.end(); ++it)
|
||||
{
|
||||
sec.insert( TimeInterval(begin_sec, (*it).begin) );
|
||||
begin_sec = (*it).end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (begin_sec != timing_.end)
|
||||
sec.insert( TimeInterval(begin_sec, timing_.end) );
|
||||
|
||||
return sec;
|
||||
}
|
||||
|
||||
void Timeline::clearGaps()
|
||||
{
|
||||
gaps_.clear();
|
||||
|
||||
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
|
||||
gapsArray_[i] = 0.f;
|
||||
|
||||
gaps_array_need_update_ = true;
|
||||
}
|
||||
|
||||
float Timeline::fadingAt(const GstClockTime t)
|
||||
{
|
||||
double true_index = (static_cast<double>(MAX_TIMELINE_ARRAY) * static_cast<double>(t)) / static_cast<double>(timing_.end);
|
||||
double previous_index = floor(true_index);
|
||||
float percent = static_cast<float>(true_index - previous_index);
|
||||
size_t keyframe_index = CLAMP( static_cast<size_t>(previous_index), 0, MAX_TIMELINE_ARRAY-1);
|
||||
size_t keyframe_next_index = CLAMP( keyframe_index+1, 0, MAX_TIMELINE_ARRAY-1);
|
||||
float v = fadingArray_[keyframe_index];
|
||||
v += percent * (fadingArray_[keyframe_next_index] - fadingArray_[keyframe_index]);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
void Timeline::clearFading()
|
||||
{
|
||||
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
|
||||
fadingArray_[i] = 1.f;
|
||||
}
|
||||
|
||||
void Timeline::smoothFading(uint N)
|
||||
{
|
||||
const float kernel[7] = { 2.f, 22.f, 97.f, 159.f, 97.f, 22.f, 2.f};
|
||||
float tmparray[MAX_TIMELINE_ARRAY];
|
||||
|
||||
for (uint n = 0; n < N; ++n) {
|
||||
|
||||
for (long i = 0; i < MAX_TIMELINE_ARRAY; ++i) {
|
||||
tmparray[i] = 0.f;
|
||||
float divider = 0.f;
|
||||
for( long j = 0; j < 7; ++j) {
|
||||
long k = i - 3 + j;
|
||||
if (k > -1 && k < MAX_TIMELINE_ARRAY-1) {
|
||||
tmparray[i] += fadingArray_[k] * kernel[j];
|
||||
divider += kernel[j];
|
||||
}
|
||||
}
|
||||
tmparray[i] *= 1.f / divider;
|
||||
}
|
||||
|
||||
memcpy( fadingArray_, tmparray, MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Timeline::autoFading(uint milisecond)
|
||||
{
|
||||
|
||||
GstClockTime stepduration = timing_.end / MAX_TIMELINE_ARRAY;
|
||||
stepduration = GST_TIME_AS_MSECONDS(stepduration);
|
||||
uint N = milisecond / stepduration;
|
||||
|
||||
// reset all to zero
|
||||
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
|
||||
fadingArray_[i] = 0.f;
|
||||
|
||||
// get sections (inverse of gaps)
|
||||
TimeIntervalSet sec = sections();
|
||||
|
||||
// fading for each section
|
||||
// NB : there is at least one
|
||||
for (auto it = sec.begin(); it != sec.end(); ++it)
|
||||
{
|
||||
// get index of begining of section
|
||||
size_t s = ( (*it).begin * MAX_TIMELINE_ARRAY ) / timing_.end;
|
||||
// get index of ending of section
|
||||
size_t e = ( (*it).end * MAX_TIMELINE_ARRAY ) / timing_.end;
|
||||
|
||||
// calculate size of the smooth transition in [s e] interval
|
||||
uint n = MIN( (e-s)/3, N );
|
||||
|
||||
// linear fade in starting at s
|
||||
size_t i = s;
|
||||
for (; i < s+n; ++i)
|
||||
fadingArray_[i] = static_cast<float>(i-s) / static_cast<float>(n);
|
||||
// plateau
|
||||
for (; i < e-n; ++i)
|
||||
fadingArray_[i] = 1.f;
|
||||
// linear fade out ending at e
|
||||
for (; i < e; ++i)
|
||||
fadingArray_[i] = static_cast<float>(e-i) / static_cast<float>(n);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Timeline::updateGapsFromArray(float *array, size_t array_size)
|
||||
{
|
||||
// reset gaps
|
||||
gaps_.clear();
|
||||
|
||||
// fill the gaps from array
|
||||
if (array != nullptr && array_size > 0 && timing_.is_valid()) {
|
||||
|
||||
// loop over the array to detect gaps
|
||||
float status = 0.f;
|
||||
GstClockTime begin_gap = GST_CLOCK_TIME_NONE;
|
||||
for (size_t i = 0; i < array_size; ++i) {
|
||||
// detect a change of value between two slots
|
||||
if ( array[i] != status) {
|
||||
// compute time of the event in array
|
||||
GstClockTime t = (timing_.duration() * i) / array_size;
|
||||
// change from 0.f to 1.f : begin of a gap
|
||||
if (array[i] > 0.f) {
|
||||
begin_gap = t;
|
||||
}
|
||||
// change from 1.f to 0.f : end of a gap
|
||||
else {
|
||||
addGap( begin_gap, t );
|
||||
begin_gap = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
// swap
|
||||
status = array[i];
|
||||
}
|
||||
}
|
||||
// end a potentially pending gap if reached end of array with no end of gap
|
||||
if (begin_gap != GST_CLOCK_TIME_NONE)
|
||||
addGap( begin_gap, timing_.end );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::fillArrayFromGaps(float *array, size_t array_size)
|
||||
{
|
||||
// fill the array from gaps
|
||||
if (array != nullptr && array_size > 0 && timing_.is_valid()) {
|
||||
|
||||
for(int i=0;i<array_size;++i)
|
||||
gapsArray_[i] = 0.f;
|
||||
|
||||
// for each gap
|
||||
for (auto it = gaps_.begin(); it != gaps_.end(); ++it)
|
||||
{
|
||||
size_t s = ( (*it).begin * array_size ) / timing_.end;
|
||||
size_t e = ( (*it).end * array_size ) / timing_.end;
|
||||
|
||||
// fill with 1 where there is a gap
|
||||
for (size_t i = s; i < e; ++i) {
|
||||
gapsArray_[i] = 1.f;
|
||||
}
|
||||
}
|
||||
|
||||
gaps_array_need_update_ = false;
|
||||
}
|
||||
|
||||
// // NB: less efficient algorithm :
|
||||
// TimeInterval gap;
|
||||
// for (size_t i = 0; i < array_size; ++i) {
|
||||
// GstClockTime t = (timing_.duration() * i) / array_size;
|
||||
// array[i] = gapAt(t, gap) ? 1.f : 0.f;
|
||||
// }
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
// Opengl
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "SessionSource.h"
|
||||
#include "DrawVisitor.h"
|
||||
#include "Decorations.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "TransitionView.h"
|
||||
|
||||
|
||||
TransitionView::TransitionView() : View(TRANSITION), transition_source_(nullptr)
|
||||
{
|
||||
// read default settings
|
||||
if ( Settings::application.views[mode_].name.empty() )
|
||||
{
|
||||
// no settings found: store application default
|
||||
Settings::application.views[mode_].name = "Transition";
|
||||
scene.root()->scale_ = glm::vec3(TRANSITION_DEFAULT_SCALE, TRANSITION_DEFAULT_SCALE, 1.0f);
|
||||
scene.root()->translation_ = glm::vec3(1.5f, 0.f, 0.0f);
|
||||
saveSettings();
|
||||
}
|
||||
else
|
||||
restoreSettings();
|
||||
|
||||
// Geometry Scene background
|
||||
gradient_ = new Switch;
|
||||
gradient_->attach(new ImageSurface("images/gradient_0_cross_linear.png"));
|
||||
gradient_->attach(new ImageSurface("images/gradient_1_black_linear.png"));
|
||||
gradient_->attach(new ImageSurface("images/gradient_2_cross_quad.png"));
|
||||
gradient_->attach(new ImageSurface("images/gradient_3_black_quad.png"));
|
||||
gradient_->scale_ = glm::vec3(0.501f, 0.006f, 1.f);
|
||||
gradient_->translation_ = glm::vec3(-0.5f, -0.005f, -0.01f);
|
||||
scene.fg()->attach(gradient_);
|
||||
|
||||
mark_1s_ = new Mesh("mesh/h_mark.ply");
|
||||
mark_1s_->translation_ = glm::vec3(-1.f, -0.01f, 0.0f);
|
||||
mark_1s_->shader()->color = glm::vec4( COLOR_TRANSITION_LINES, 0.9f );
|
||||
scene.fg()->attach(mark_1s_);
|
||||
|
||||
mark_100ms_ = new Mesh("mesh/h_mark.ply");
|
||||
mark_100ms_->translation_ = glm::vec3(-1.f, -0.01f, 0.0f);
|
||||
mark_100ms_->scale_ = glm::vec3(0.5f, 0.5f, 0.0f);
|
||||
mark_100ms_->shader()->color = glm::vec4( COLOR_TRANSITION_LINES, 0.9f );
|
||||
scene.fg()->attach(mark_100ms_);
|
||||
|
||||
// move the whole forground below the icons
|
||||
scene.fg()->translation_ = glm::vec3(0.f, -0.11f, 0.0f);
|
||||
|
||||
output_surface_ = new Surface;
|
||||
scene.bg()->attach(output_surface_);
|
||||
|
||||
Frame *border = new Frame(Frame::ROUND, Frame::THIN, Frame::GLOW);
|
||||
border->color = glm::vec4( COLOR_FRAME, 1.0f );
|
||||
scene.bg()->attach(border);
|
||||
|
||||
scene.bg()->scale_ = glm::vec3(0.1f, 0.1f, 1.f);
|
||||
scene.bg()->translation_ = glm::vec3(0.4f, 0.f, 0.0f);
|
||||
|
||||
}
|
||||
|
||||
void TransitionView::update(float dt)
|
||||
{
|
||||
// update scene
|
||||
View::update(dt);
|
||||
|
||||
// a more complete update is requested
|
||||
if (View::need_deep_update_ > 0) {
|
||||
|
||||
// update rendering of render frame
|
||||
FrameBuffer *output = Mixer::manager().session()->frame();
|
||||
if (output){
|
||||
float aspect_ratio = output->aspectRatio();
|
||||
for (NodeSet::iterator node = scene.bg()->begin(); node != scene.bg()->end(); node++) {
|
||||
(*node)->scale_.x = aspect_ratio;
|
||||
}
|
||||
output_surface_->setTextureIndex( output->texture() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update transition source
|
||||
if ( transition_source_ != nullptr) {
|
||||
|
||||
float d = transition_source_->group(View::TRANSITION)->translation_.x;
|
||||
|
||||
// Transfer this movement to changes in mixing
|
||||
// cross fading
|
||||
if ( Settings::application.transition.cross_fade )
|
||||
{
|
||||
float f = 0.f;
|
||||
// change alpha of session:
|
||||
if (Settings::application.transition.profile == 0)
|
||||
// linear => identical coordinates in Mixing View
|
||||
f = d;
|
||||
else {
|
||||
// quadratic => square coordinates in Mixing View
|
||||
f = (d+1.f)*(d+1.f) -1.f;
|
||||
}
|
||||
transition_source_->group(View::MIXING)->translation_.x = CLAMP(f, -1.f, 0.f);
|
||||
transition_source_->group(View::MIXING)->translation_.y = 0.f;
|
||||
|
||||
}
|
||||
// fade to black
|
||||
else
|
||||
{
|
||||
// change alpha of session ; hidden before -0.5, visible after
|
||||
transition_source_->group(View::MIXING)->translation_.x = d < -0.5f ? -1.f : 0.f;
|
||||
transition_source_->group(View::MIXING)->translation_.y = 0.f;
|
||||
|
||||
// fade to black at 50% : fade-out [-1.0 -0.5], fade-in [-0.5 0.0]
|
||||
float f = 0.f;
|
||||
if (Settings::application.transition.profile == 0)
|
||||
f = ABS(2.f * d + 1.f); // linear
|
||||
else {
|
||||
f = ( 2.f * d + 1.f); // quadratic
|
||||
f *= f;
|
||||
}
|
||||
Mixer::manager().session()->setFading( 1.f - f );
|
||||
}
|
||||
|
||||
// request update
|
||||
transition_source_->touch();
|
||||
|
||||
if (d > 0.2f && Settings::application.transition.auto_open)
|
||||
Mixer::manager().setView(View::MIXING);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void TransitionView::draw()
|
||||
{
|
||||
// update the GUI depending on changes in settings
|
||||
gradient_->setActive( 2*Settings::application.transition.profile + (Settings::application.transition.cross_fade ? 0 : 1) );
|
||||
|
||||
// draw scene of this view
|
||||
View::draw();
|
||||
|
||||
// 100ms tic marks
|
||||
int n = static_cast<int>( Settings::application.transition.duration / 0.1f );
|
||||
glm::mat4 T = glm::translate(glm::identity<glm::mat4>(), glm::vec3( 1.f / n, 0.f, 0.f));
|
||||
DrawVisitor dv(mark_100ms_, Rendering::manager().Projection());
|
||||
dv.loop(n+1, T);
|
||||
scene.accept(dv);
|
||||
|
||||
// 1s tic marks
|
||||
int N = static_cast<int>( Settings::application.transition.duration );
|
||||
T = glm::translate(glm::identity<glm::mat4>(), glm::vec3( 10.f / n, 0.f, 0.f));
|
||||
DrawVisitor dv2(mark_1s_, Rendering::manager().Projection());
|
||||
dv2.loop(N+1, T);
|
||||
scene.accept(dv2);
|
||||
|
||||
// display interface duration
|
||||
glm::vec2 P = Rendering::manager().project(glm::vec3(-0.17f, -0.14f, 0.f), scene.root()->transform_, false);
|
||||
ImGui::SetNextWindowPos(ImVec2(P.x, P.y), ImGuiCond_Always);
|
||||
if (ImGui::Begin("##Transition", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground
|
||||
| ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings
|
||||
| ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus))
|
||||
{
|
||||
|
||||
// style grey
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.27f, 0.27f, 0.27f, 0.55f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.27f, 0.27f, 0.27f, 0.79f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.27f, 0.27f, 0.27f, 0.7f));
|
||||
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.15f, 0.15f, 0.15f, 1.00f));
|
||||
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(0.10f, 0.10f, 0.10f, 1.00f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.27f, 0.27f, 0.27f, 0.55f)); // 7 colors
|
||||
|
||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
||||
ImGui::SetNextItemWidth(180.f);
|
||||
ImGui::SliderFloat("##transitionduration", &Settings::application.transition.duration,
|
||||
TRANSITION_MIN_DURATION, TRANSITION_MAX_DURATION, "%.1f s");
|
||||
ImGui::SameLine();
|
||||
if ( ImGui::Button(ICON_FA_STEP_FORWARD) )
|
||||
play(false);
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleColor(7); // 7 colors
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
P = Rendering::manager().project(glm::vec3(-0.535f, -0.14f, 0.f), scene.root()->transform_, false);
|
||||
ImGui::SetNextWindowPos(ImVec2(P.x, P.y), ImGuiCond_Always);
|
||||
if (ImGui::Begin("##TransitionType", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground
|
||||
| ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings
|
||||
| ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus))
|
||||
{
|
||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
||||
|
||||
// black background in icon 'transition to black'
|
||||
if (!Settings::application.transition.cross_fade) {
|
||||
ImVec2 draw_pos = ImGui::GetCursorScreenPos();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.0f, 0.0f, 1.f));
|
||||
ImGuiToolkit::Icon(19,1);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SetCursorScreenPos(draw_pos);
|
||||
}
|
||||
|
||||
// toggle transition mode
|
||||
const char *tooltip[2] = {"Transition to black", "Cross fading"};
|
||||
ImGuiToolkit::IconToggle(0,2,0,8, &Settings::application.transition.cross_fade, tooltip );
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TransitionView::canSelect(Source *s) {
|
||||
|
||||
return ( s!=nullptr && s == transition_source_);
|
||||
}
|
||||
|
||||
void TransitionView::attach(SessionFileSource *ts)
|
||||
{
|
||||
// store source for later (detatch & interaction)
|
||||
transition_source_ = ts;
|
||||
|
||||
if ( transition_source_ != nullptr) {
|
||||
// insert in scene
|
||||
Group *tg = transition_source_->group(View::TRANSITION);
|
||||
tg->visible_ = true;
|
||||
scene.ws()->attach(tg);
|
||||
|
||||
// in fade to black transition, start transition from current fading value
|
||||
if ( !Settings::application.transition.cross_fade) {
|
||||
|
||||
// reverse calculate x position to match actual fading of session
|
||||
float d = 0.f;
|
||||
if (Settings::application.transition.profile == 0)
|
||||
d = -1.f + 0.5f * Mixer::manager().session()->fading(); // linear
|
||||
else {
|
||||
d = -1.f - 0.5f * ( sqrt(1.f - Mixer::manager().session()->fading()) - 1.f); // quadratic
|
||||
}
|
||||
|
||||
transition_source_->group(View::TRANSITION)->translation_.x = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Session *TransitionView::detach()
|
||||
{
|
||||
// by default, nothing to return
|
||||
Session *ret = nullptr;
|
||||
|
||||
if ( transition_source_ != nullptr) {
|
||||
|
||||
// get and detatch the group node from the view workspace
|
||||
Group *tg = transition_source_->group(View::TRANSITION);
|
||||
scene.ws()->detach( tg );
|
||||
|
||||
|
||||
// test if the icon of the transition source is "Ready"
|
||||
if ( tg->translation_.x > 0.f )
|
||||
// detatch the session and return it
|
||||
ret = transition_source_->detach();
|
||||
else
|
||||
// not detached: make current
|
||||
Mixer::manager().setCurrentSource(transition_source_);
|
||||
|
||||
// done with transition
|
||||
transition_source_ = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TransitionView::zoom (float factor)
|
||||
{
|
||||
if (transition_source_ != nullptr) {
|
||||
float d = transition_source_->group(View::TRANSITION)->translation_.x;
|
||||
d += 0.1f * factor;
|
||||
transition_source_->group(View::TRANSITION)->translation_.x = CLAMP(d, -1.f, 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<Node *, glm::vec2> TransitionView::pick(glm::vec2 P)
|
||||
{
|
||||
std::pair<Node *, glm::vec2> pick = View::pick(P);
|
||||
|
||||
if (transition_source_ != nullptr) {
|
||||
// start animation when clic on target
|
||||
if (pick.first == output_surface_)
|
||||
play(true);
|
||||
// otherwise cancel animation
|
||||
else
|
||||
transition_source_->group(View::TRANSITION)->clearCallbacks();
|
||||
}
|
||||
|
||||
return pick;
|
||||
}
|
||||
|
||||
|
||||
void TransitionView::play(bool open)
|
||||
{
|
||||
if (transition_source_ != nullptr) {
|
||||
|
||||
// toggle play/stop with button
|
||||
if (!transition_source_->group(View::TRANSITION)->update_callbacks_.empty() && !open) {
|
||||
// just cancel previous animation
|
||||
transition_source_->group(View::TRANSITION)->clearCallbacks();
|
||||
return;
|
||||
}
|
||||
// else cancel previous animation and start new one
|
||||
transition_source_->group(View::TRANSITION)->clearCallbacks();
|
||||
|
||||
// if want to open session after play, target movement till end position, otherwise stop at 0
|
||||
float target_x = open ? 0.4f : 0.f;
|
||||
|
||||
// calculate how far to reach target
|
||||
float time = CLAMP(- transition_source_->group(View::TRANSITION)->translation_.x, 0.f, 1.f);
|
||||
// extra distance to reach transition if want to open
|
||||
time += open ? 0.2f : 0.f;
|
||||
// calculate remaining time on the total duration, in ms
|
||||
time *= Settings::application.transition.duration * 1000.f;
|
||||
|
||||
|
||||
// if remaining time is more than 50ms
|
||||
if (time > 50.f) {
|
||||
// start animation
|
||||
MoveToCallback *anim = new MoveToCallback(glm::vec3(target_x, 0.0, 0.0), time);
|
||||
transition_source_->group(View::TRANSITION)->update_callbacks_.push_back(anim);
|
||||
}
|
||||
// otherwise finish animation
|
||||
else
|
||||
transition_source_->group(View::TRANSITION)->translation_.x = target_x;
|
||||
}
|
||||
}
|
||||
|
||||
View::Cursor TransitionView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2>)
|
||||
{
|
||||
if (!s)
|
||||
return Cursor();
|
||||
|
||||
// unproject
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(from, scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(to, scene.root()->transform_);
|
||||
|
||||
// compute delta translation
|
||||
float d = s->stored_status_->translation_.x + gl_Position_to.x - gl_Position_from.x;
|
||||
std::ostringstream info;
|
||||
if (d > 0.2) {
|
||||
s->group(mode_)->translation_.x = 0.4;
|
||||
info << "Open session";
|
||||
}
|
||||
else {
|
||||
s->group(mode_)->translation_.x = CLAMP(d, -1.f, 0.f);
|
||||
info << "Transition " << int( 100.f * (1.f + s->group(View::TRANSITION)->translation_.x)) << "%";
|
||||
}
|
||||
|
||||
return Cursor(Cursor_ResizeEW, info.str() );
|
||||
}
|
||||
|
||||
void TransitionView::arrow (glm::vec2 movement)
|
||||
{
|
||||
Source *s = Mixer::manager().currentSource();
|
||||
if (s) {
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
float d = s->group(mode_)->translation_.x + gl_delta.x * ARROWS_MOVEMENT_FACTOR;
|
||||
s->group(mode_)->translation_.x = CLAMP(d, -1.f, 0.f);
|
||||
|
||||
// request update
|
||||
s->touch();
|
||||
}
|
||||
}
|
||||
|
||||
View::Cursor TransitionView::drag (glm::vec2 from, glm::vec2 to)
|
||||
{
|
||||
Cursor ret = View::drag(from, to);
|
||||
|
||||
// Clamp translation to acceptable area
|
||||
scene.root()->translation_ = glm::clamp(scene.root()->translation_, glm::vec3(1.f, -1.7f, 0.f), glm::vec3(2.f, 1.7f, 0.f));
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
#ifndef __UI_MANAGER_H_
|
||||
#define __UI_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#define NAV_COUNT 68
|
||||
#define NAV_MAX 64
|
||||
#define NAV_NEW 65
|
||||
#define NAV_MENU 66
|
||||
#define NAV_TRANS 67
|
||||
|
||||
struct ImVec2;
|
||||
class Source;
|
||||
class MediaPlayer;
|
||||
|
||||
class SourcePreview {
|
||||
|
||||
Source *source_;
|
||||
std::string label_;
|
||||
|
||||
public:
|
||||
SourcePreview();
|
||||
|
||||
void setSource(Source *s = nullptr, std::string label = "");
|
||||
Source *getSource();
|
||||
|
||||
void Render(float width, bool controlbutton = false);
|
||||
bool ready() const;
|
||||
inline bool filled() const { return source_ != nullptr; }
|
||||
};
|
||||
|
||||
class Navigator
|
||||
{
|
||||
// geometry left bar & pannel
|
||||
float width_;
|
||||
float height_;
|
||||
float pannel_width_;
|
||||
float sourcelist_height_;
|
||||
float padding_width_;
|
||||
|
||||
// behavior pannel
|
||||
bool pannel_visible_;
|
||||
bool view_pannel_visible;
|
||||
bool selected_button[NAV_COUNT];
|
||||
int pattern_type;
|
||||
void clearButtonSelection();
|
||||
void applyButtonSelection(int index);
|
||||
|
||||
// side pannels
|
||||
void RenderSourcePannel(Source *s);
|
||||
void RenderMainPannel();
|
||||
void RenderTransitionPannel();
|
||||
void RenderNewPannel();
|
||||
void RenderViewPannel(ImVec2 draw_pos, ImVec2 draw_size);
|
||||
|
||||
SourcePreview new_source_preview_;
|
||||
|
||||
public:
|
||||
Navigator();
|
||||
|
||||
bool pannelVisible() { return pannel_visible_; }
|
||||
void hidePannel();
|
||||
void showPannelSource(int index);
|
||||
void togglePannelMenu();
|
||||
void togglePannelNew();
|
||||
|
||||
|
||||
void Render();
|
||||
};
|
||||
|
||||
class ToolBox
|
||||
{
|
||||
bool show_demo_window;
|
||||
bool show_icons_window;
|
||||
bool show_sandbox;
|
||||
|
||||
public:
|
||||
ToolBox();
|
||||
|
||||
void Render();
|
||||
};
|
||||
|
||||
|
||||
class MediaController
|
||||
{
|
||||
MediaPlayer *mp_;
|
||||
std::string current_;
|
||||
bool follow_active_source_;
|
||||
bool media_playing_mode_;
|
||||
bool slider_pressed_;
|
||||
|
||||
public:
|
||||
MediaController();
|
||||
|
||||
void setMediaPlayer(MediaPlayer *mp = nullptr);
|
||||
void followCurrentSource();
|
||||
|
||||
void Render();
|
||||
};
|
||||
|
||||
class UserInterface
|
||||
{
|
||||
friend class Navigator;
|
||||
Navigator navigator;
|
||||
ToolBox toolbox;
|
||||
MediaController mediacontrol;
|
||||
|
||||
bool ctrl_modifier_active;
|
||||
bool alt_modifier_active;
|
||||
bool shift_modifier_active;
|
||||
bool show_vimix_about;
|
||||
bool show_imgui_about;
|
||||
bool show_gst_about;
|
||||
bool show_opengl_about;
|
||||
int show_view_navigator;
|
||||
int target_view_navigator;
|
||||
unsigned int screenshot_step;
|
||||
|
||||
// frame grabbers
|
||||
uint64_t video_recorder_;
|
||||
uint64_t webcam_emulator_;
|
||||
|
||||
// Private Constructor
|
||||
UserInterface();
|
||||
UserInterface(UserInterface const& copy); // Not Implemented
|
||||
UserInterface& operator=(UserInterface const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
|
||||
static UserInterface& manager()
|
||||
{
|
||||
// The only instance
|
||||
static UserInterface _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
// pre-loop initialization
|
||||
bool Init();
|
||||
// loop update start new frame
|
||||
void NewFrame();
|
||||
// loop update rendering
|
||||
void Render();
|
||||
// Post-loop termination
|
||||
void Terminate();
|
||||
|
||||
// status querries
|
||||
inline bool ctrlModifier() const { return ctrl_modifier_active; }
|
||||
inline bool altModifier() const { return alt_modifier_active; }
|
||||
inline bool shiftModifier() const { return shift_modifier_active; }
|
||||
|
||||
void StartScreenshot();
|
||||
void showPannel(int id = 0);
|
||||
|
||||
void showSourceEditor(Source *s);
|
||||
void showMediaPlayer(MediaPlayer *mp);
|
||||
|
||||
// TODO implement the shader editor
|
||||
std::string currentTextEdit;
|
||||
void fillShaderEditor(std::string text);
|
||||
|
||||
protected:
|
||||
|
||||
void showMenuFile();
|
||||
void showMenuEdit();
|
||||
void selectSaveFilename();
|
||||
void selectOpenFilename();
|
||||
|
||||
void RenderPreview();
|
||||
void RenderHistory();
|
||||
void RenderShaderEditor();
|
||||
int RenderViewNavigator(int* shift);
|
||||
void handleKeyboard();
|
||||
void handleMouse();
|
||||
void handleScreenshot();
|
||||
void RenderAbout(bool* p_open);
|
||||
};
|
||||
|
||||
#endif /* #define __UI_MANAGER_H_ */
|
||||
|
||||
88
Visitor.h
@@ -1,88 +0,0 @@
|
||||
#ifndef VISITOR_H
|
||||
#define VISITOR_H
|
||||
|
||||
#include <string>
|
||||
|
||||
// Forward declare different kind of Node
|
||||
class Node;
|
||||
class Group;
|
||||
class Switch;
|
||||
class Primitive;
|
||||
class Scene;
|
||||
class Surface;
|
||||
class ImageSurface;
|
||||
class MediaSurface;
|
||||
class FrameBufferSurface;
|
||||
class LineStrip;
|
||||
class LineSquare;
|
||||
class LineCircle;
|
||||
class Mesh;
|
||||
class Frame;
|
||||
class Handles;
|
||||
class Symbol;
|
||||
class Disk;
|
||||
class Stream;
|
||||
class MediaPlayer;
|
||||
class Shader;
|
||||
class ImageShader;
|
||||
class MaskShader;
|
||||
class ImageProcessingShader;
|
||||
class Source;
|
||||
class MediaSource;
|
||||
class PatternSource;
|
||||
class DeviceSource;
|
||||
class GenericStreamSource;
|
||||
class SessionFileSource;
|
||||
class SessionGroupSource;
|
||||
class RenderSource;
|
||||
class CloneSource;
|
||||
class NetworkSource;
|
||||
class MixingGroup;
|
||||
|
||||
// Declares the interface for the visitors
|
||||
class Visitor {
|
||||
|
||||
public:
|
||||
// Need to declare overloads for basic kind of Nodes to visit
|
||||
virtual void visit (Scene&) = 0;
|
||||
virtual void visit (Node&) = 0;
|
||||
virtual void visit (Primitive&) = 0;
|
||||
virtual void visit (Group&) = 0;
|
||||
virtual void visit (Switch&) = 0;
|
||||
|
||||
// not mandatory for all others
|
||||
virtual void visit (Surface&) {}
|
||||
virtual void visit (ImageSurface&) {}
|
||||
virtual void visit (MediaSurface&) {}
|
||||
virtual void visit (FrameBufferSurface&) {}
|
||||
virtual void visit (LineStrip&) {}
|
||||
virtual void visit (LineSquare&) {}
|
||||
virtual void visit (Mesh&) {}
|
||||
virtual void visit (Frame&) {}
|
||||
virtual void visit (Handles&) {}
|
||||
virtual void visit (Symbol&) {}
|
||||
virtual void visit (Disk&) {}
|
||||
virtual void visit (Stream&) {}
|
||||
virtual void visit (MediaPlayer&) {}
|
||||
virtual void visit (Shader&) {}
|
||||
virtual void visit (ImageShader&) {}
|
||||
virtual void visit (MaskShader&) {}
|
||||
virtual void visit (ImageProcessingShader&) {}
|
||||
|
||||
// utility
|
||||
virtual void visit (MixingGroup&) {}
|
||||
virtual void visit (Source&) {}
|
||||
virtual void visit (MediaSource&) {}
|
||||
virtual void visit (NetworkSource&) {}
|
||||
virtual void visit (GenericStreamSource&) {}
|
||||
virtual void visit (DeviceSource&) {}
|
||||
virtual void visit (PatternSource&) {}
|
||||
virtual void visit (SessionFileSource&) {}
|
||||
virtual void visit (SessionGroupSource&) {}
|
||||
virtual void visit (RenderSource&) {}
|
||||
virtual void visit (CloneSource&) {}
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // VISITOR_H
|
||||
41
cmake/modules/BundleInstall.cmake.in
Normal file
@@ -0,0 +1,41 @@
|
||||
set(BUNDLE_NAME @BUNDLE_NAME@)
|
||||
set(BUNDLE_LIBS_DIR @BUNDLE_LIBS_DIR@)
|
||||
set(BUNDLE_DIRS @BUNDLE_DIRS@)
|
||||
set(APPLE_CODESIGN_IDENTITY @APPLE_CODESIGN_IDENTITY@)
|
||||
set(APPLE_CODESIGN_ENTITLEMENTS @APPLE_CODESIGN_ENTITLEMENTS@)
|
||||
|
||||
set(BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}")
|
||||
|
||||
include(BundleUtilities)
|
||||
|
||||
#fixup_bundle tries to copy system libraries without this. Wtf?
|
||||
function(gp_resolved_file_type_override file type)
|
||||
if(file MATCHES "^(/usr/lib)")
|
||||
set(type "system" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
file(GLOB_RECURSE BUNDLE_LIBS "${CMAKE_INSTALL_PREFIX}/${BUNDLE_LIBS_DIR}/*.dylib")
|
||||
|
||||
set(BU_CHMOD_BUNDLE_ITEMS ON)
|
||||
fixup_bundle("${BUNDLE_PATH}" "${BUNDLE_LIBS}" "${BUNDLE_DIRS}")
|
||||
|
||||
if(DEFINED APPLE_CODESIGN_IDENTITY AND DEFINED APPLE_CODESIGN_ENTITLEMENTS)
|
||||
# execute_process(COMMAND
|
||||
# codesign --verbose=4 --deep --force --options runtime
|
||||
# --entitlements "${APPLE_CODESIGN_ENTITLEMENTS}"
|
||||
# --sign "${APPLE_CODESIGN_IDENTITY}"
|
||||
# "${BUNDLE_NAME}"
|
||||
# )
|
||||
foreach(PATH_TO_SIGN IN LISTS BUNDLE_LIBS BUNDLE_PATH)
|
||||
execute_process(COMMAND
|
||||
codesign --verbose=4 --deep --force
|
||||
--entitlements "${APPLE_CODESIGN_ENTITLEMENTS}"
|
||||
--sign "${APPLE_CODESIGN_IDENTITY}"
|
||||
"${PATH_TO_SIGN}"
|
||||
)
|
||||
endforeach()
|
||||
else()
|
||||
message(STATUS "Not signing bundle. Specify -DAPPLE_CODESIGN_IDENTITY and -DAPPLE_CODESIGN_ENTITLEMENTS to cmake before running cpack to sign")
|
||||
endif()
|
||||
|
||||
@@ -34,7 +34,7 @@ endif()
|
||||
|
||||
set(_version 2.0.0)
|
||||
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
include(CMakeParseArguments)
|
||||
|
||||
if(COMMAND cmrc_add_resource_library)
|
||||
@@ -77,6 +77,10 @@ set(hpp_content [==[
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
#if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) || defined(CMRC_NO_EXCEPTIONS))
|
||||
#define CMRC_NO_EXCEPTIONS 1
|
||||
#endif
|
||||
|
||||
namespace cmrc { namespace detail { struct dummy; } }
|
||||
|
||||
#define CMRC_DECLARE(libid) \
|
||||
@@ -101,7 +105,7 @@ public:
|
||||
iterator cbegin() const noexcept { return _begin; }
|
||||
iterator end() const noexcept { return _end; }
|
||||
iterator cend() const noexcept { return _end; }
|
||||
std::size_t size() const { return std::distance(begin(), end()); }
|
||||
std::size_t size() const { return static_cast<std::size_t>(std::distance(begin(), end())); }
|
||||
|
||||
file() = default;
|
||||
file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {}
|
||||
@@ -243,16 +247,16 @@ public:
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
iterator operator++() noexcept {
|
||||
iterator& operator++() noexcept {
|
||||
++_base_iter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) noexcept {
|
||||
auto cp = *this;
|
||||
++_base_iter;
|
||||
return cp;
|
||||
}
|
||||
|
||||
iterator& operator++(int) noexcept {
|
||||
++_base_iter;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
using const_iterator = iterator;
|
||||
@@ -275,7 +279,7 @@ inline std::string normalize_path(std::string path) {
|
||||
}
|
||||
auto off = path.npos;
|
||||
while ((off = path.find("//")) != path.npos) {
|
||||
path.erase(path.begin() + off);
|
||||
path.erase(path.begin() + static_cast<std::string::difference_type>(off));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
@@ -339,7 +343,12 @@ public:
|
||||
file open(const std::string& path) const {
|
||||
auto entry_ptr = _get(path);
|
||||
if (!entry_ptr || !entry_ptr->is_file()) {
|
||||
#ifdef CMRC_NO_EXCEPTIONS
|
||||
fprintf(stderr, "Error no such file or directory: %s\n", path.c_str());
|
||||
abort();
|
||||
#else
|
||||
throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path);
|
||||
#endif
|
||||
}
|
||||
auto& dat = entry_ptr->as_file();
|
||||
return file{dat.begin_ptr, dat.end_ptr};
|
||||
@@ -362,10 +371,20 @@ public:
|
||||
directory_iterator iterate_directory(const std::string& path) const {
|
||||
auto entry_ptr = _get(path);
|
||||
if (!entry_ptr) {
|
||||
#ifdef CMRC_NO_EXCEPTIONS
|
||||
fprintf(stderr, "Error no such file or directory: %s\n", path.c_str());
|
||||
abort();
|
||||
#else
|
||||
throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path);
|
||||
#endif
|
||||
}
|
||||
if (!entry_ptr->is_directory()) {
|
||||
#ifdef CMRC_NO_EXCEPTIONS
|
||||
fprintf(stderr, "Error not a directory: %s\n", path.c_str());
|
||||
abort();
|
||||
#else
|
||||
throw std::system_error(make_error_code(std::errc::not_a_directory), path);
|
||||
#endif
|
||||
}
|
||||
return entry_ptr->as_directory().begin();
|
||||
}
|
||||
@@ -387,14 +406,14 @@ endif()
|
||||
file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate})
|
||||
|
||||
add_library(cmrc-base INTERFACE)
|
||||
target_include_directories(cmrc-base INTERFACE "${CMRC_INCLUDE_DIR}")
|
||||
target_include_directories(cmrc-base INTERFACE $<BUILD_INTERFACE:${CMRC_INCLUDE_DIR}>)
|
||||
# Signal a basic C++11 feature to require C++11.
|
||||
target_compile_features(cmrc-base INTERFACE cxx_nullptr)
|
||||
set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF)
|
||||
add_library(cmrc::base ALIAS cmrc-base)
|
||||
|
||||
function(cmrc_add_resource_library name)
|
||||
set(args ALIAS NAMESPACE)
|
||||
set(args ALIAS NAMESPACE TYPE)
|
||||
cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}")
|
||||
# Generate the identifier for the resource library's namespace
|
||||
set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*")
|
||||
@@ -410,6 +429,14 @@ function(cmrc_add_resource_library name)
|
||||
endif()
|
||||
endif()
|
||||
set(libname "${name}")
|
||||
# Check that type is either "STATIC" or "OBJECT", or default to "STATIC" if
|
||||
# not set
|
||||
if(NOT DEFINED ARG_TYPE)
|
||||
set(ARG_TYPE STATIC)
|
||||
elseif(NOT "${ARG_TYPE}" MATCHES "^(STATIC|OBJECT)$")
|
||||
message(SEND_ERROR "${ARG_TYPE} is not a valid TYPE (STATIC and OBJECT are acceptable)")
|
||||
set(ARG_TYPE STATIC)
|
||||
endif()
|
||||
# Generate a library with the compiled in character arrays.
|
||||
string(CONFIGURE [=[
|
||||
#include <cmrc/cmrc.hpp>
|
||||
@@ -468,7 +495,7 @@ function(cmrc_add_resource_library name)
|
||||
# Generate the actual static library. Each source file is just a single file
|
||||
# with a character array compiled in containing the contents of the
|
||||
# corresponding resource file.
|
||||
add_library(${name} STATIC ${libcpp})
|
||||
add_library(${name} ${ARG_TYPE} ${libcpp})
|
||||
set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}")
|
||||
set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}")
|
||||
target_link_libraries(${name} PUBLIC cmrc::base)
|
||||
@@ -558,7 +585,7 @@ function(cmrc_add_resources name)
|
||||
endif()
|
||||
get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY)
|
||||
_cmrc_register_dirs("${name}" "${dirpath}")
|
||||
get_filename_component(abs_out "${libdir}/intermediate/${relpath}.cpp" ABSOLUTE)
|
||||
get_filename_component(abs_out "${libdir}/intermediate/${ARG_PREFIX}${relpath}.cpp" ABSOLUTE)
|
||||
# Generate a symbol name relpath the file's character array
|
||||
_cm_encode_fpath(sym "${relpath}")
|
||||
# Get the symbol name for the parent directory
|
||||
|
||||
@@ -33,9 +33,10 @@ find_package(PkgConfig)
|
||||
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKG_GSTREAMER gstreamer-${GSTREAMER_ABI_VERSION})
|
||||
exec_program(${PKG_CONFIG_EXECUTABLE}
|
||||
ARGS --variable pluginsdir gstreamer-${GSTREAMER_ABI_VERSION}
|
||||
OUTPUT_VARIABLE PKG_GSTREAMER_PLUGIN_DIR)
|
||||
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE}
|
||||
--variable pluginsdir gstreamer-${GSTREAMER_ABI_VERSION}
|
||||
OUTPUT_VARIABLE PKG_GSTREAMER_PLUGIN_DIR_TMP)
|
||||
string(STRIP ${PKG_GSTREAMER_PLUGIN_DIR_TMP} PKG_GSTREAMER_PLUGIN_DIR)
|
||||
endif()
|
||||
|
||||
find_library(GSTREAMER_LIBRARY
|
||||
|
||||
76
cmake/modules/FindGStreamerPluginsBad.cmake
Normal file
@@ -0,0 +1,76 @@
|
||||
# - Try to find gst-plugins-base
|
||||
# Once done this will define
|
||||
#
|
||||
# GSTREAMER_PLUGINS_BASE_FOUND - system has gst-plugins-base
|
||||
#
|
||||
# And for all the plugin libraries specified in the COMPONENTS
|
||||
# of find_package, this module will define:
|
||||
#
|
||||
# GSTREAMER_<plugin_lib>_LIBRARY_FOUND - system has <plugin_lib>
|
||||
# GSTREAMER_<plugin_lib>_LIBRARY - the <plugin_lib> library
|
||||
# GSTREAMER_<plugin_lib>_INCLUDE_DIR - the <plugin_lib> include directory
|
||||
#
|
||||
# Copyright (c) 2010, Collabora Ltd.
|
||||
# @author George Kiagiadakis <george.kiagiadakis@collabora.co.uk>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
set(GSTREAMER_ABI_VERSION "1.0")
|
||||
|
||||
|
||||
# Find the pkg-config file for doing the version check
|
||||
find_package(PkgConfig)
|
||||
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKG_GSTREAMER_PLUGINS_BAD gstreamer-plugins-bad-${GSTREAMER_ABI_VERSION})
|
||||
endif()
|
||||
|
||||
|
||||
# Find the plugin libraries
|
||||
include(MacroFindGStreamerLibrary)
|
||||
|
||||
macro(_find_gst_plugins_bad_component _name _header)
|
||||
find_gstreamer_library(${_name} ${_header} ${GSTREAMER_ABI_VERSION})
|
||||
set(_GSTREAMER_PLUGINS_BAD_EXTRA_VARIABLES ${_GSTREAMER_PLUGINS_BAD_EXTRA_VARIABLES}
|
||||
GSTREAMER_${_name}_LIBRARY GSTREAMER_${_name}_INCLUDE_DIR)
|
||||
endmacro()
|
||||
|
||||
foreach(_component ${GStreamerPluginsBad_FIND_COMPONENTS})
|
||||
if (${_component} STREQUAL "player")
|
||||
_find_gst_plugins_bad_component(PLAYER gstplayer.h)
|
||||
elseif (${_component} STREQUAL "webrtc")
|
||||
_find_gst_plugins_bad_component(WEBRTC webrtc.h)
|
||||
elseif (${_component} STREQUAL "srtsrc")
|
||||
_find_gst_plugins_bad_component(SRTSRC mpegts.h)
|
||||
else()
|
||||
message (AUTHOR_WARNING "FindGStreamerPluginBad.cmake: Invalid component \"${_component}\" was specified")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
get_filename_component(_GSTREAMER_BAD_LIB_DIR ${GSTREAMER_PLAYER_LIBRARY} PATH)
|
||||
set(PKG_GSTREAMER_BAD_PLUGIN_DIR ${_GSTREAMER_BAD_LIB_DIR}/gstreamer-${GSTREAMER_ABI_VERSION})
|
||||
|
||||
# Version check
|
||||
if (GStreamerPluginsBad_FIND_VERSION)
|
||||
if (PKG_GSTREAMER_PLUGINS_BAD_FOUND)
|
||||
if("${PKG_GSTREAMER_PLUGINS_BAD_VERSION}" VERSION_LESS "${GStreamerPluginsBad_FIND_VERSION}")
|
||||
message(STATUS "Found gst-plugins-base version ${PKG_GSTREAMER_PLUGINS_BAD_VERSION}, but at least version ${GStreamerPluginsBad_FIND_VERSION} is required")
|
||||
set(GSTREAMER_PLUGINS_BAD_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(GSTREAMER_PLUGINS_BAD_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
else()
|
||||
# We can't make any version checks without pkg-config, just assume version is compatible and hope...
|
||||
set(GSTREAMER_PLUGINS_BAD_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
else()
|
||||
# No version constrain was specified, thus we consider the version compatible
|
||||
set(GSTREAMER_PLUGINS_BAD_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(GStreamerPluginsBad DEFAULT_MSG
|
||||
GSTREAMER_PLUGINS_BAD_VERSION_COMPATIBLE
|
||||
${_GSTREAMER_PLUGINS_BAD_EXTRA_VARIABLES})
|
||||
@@ -64,6 +64,8 @@ foreach(_component ${GStreamerPluginsBase_FIND_COMPONENTS})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
get_filename_component(_GSTREAMER_BASE_LIB_DIR ${GSTREAMER_APP_LIBRARY} PATH)
|
||||
set(PKG_GSTREAMER_BASE_PLUGIN_DIR ${_GSTREAMER_BASE_LIB_DIR}/gstreamer-${GSTREAMER_ABI_VERSION})
|
||||
|
||||
# Version check
|
||||
if (GStreamerPluginsBase_FIND_VERSION)
|
||||
|
||||
147
cmake/modules/FindGTK.cmake
Normal file
@@ -0,0 +1,147 @@
|
||||
# - Try to find GTK+ 3.x or 4.x
|
||||
#
|
||||
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||
# Copyright (C) 2013, 2015, 2020 Igalia S.L.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
|
||||
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
FindGTK
|
||||
-------
|
||||
|
||||
Find GTK headers and libraries.
|
||||
|
||||
Optional Components
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``COMPONENTS`` (or ``OPTIONAL_COMPONENTS``) keyword can be passed to
|
||||
``find_package()``, the following GTK components can be searched for:
|
||||
|
||||
- ``unix-print``
|
||||
|
||||
|
||||
Imported Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
``GTK::GTK``
|
||||
The GTK library, if found.
|
||||
``GTK::UnixPrint``
|
||||
The GTK unix-print library, if found.
|
||||
|
||||
Result Variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This will define the following variables in your project:
|
||||
|
||||
``GTK_FOUND``
|
||||
true if (the requested version of) GTK is available.
|
||||
``GTK_UNIX_PRINT_FOUND``
|
||||
true if the ``unix-print`` component is available.
|
||||
``GTK_4``
|
||||
whether GTK 4 was detected
|
||||
``GTK_3``
|
||||
whether GTK 3 was detected
|
||||
``GTK_VERSION``
|
||||
the version of GTK.
|
||||
``GTK_SUPPORTS_BROADWAY``
|
||||
true if the Broadway target is built into GTK.
|
||||
``GTK_SUPPORTS_QUARTZ``
|
||||
true if the Quartz target is built into GTK.
|
||||
``GTK_SUPPORTS_WAYLAND``
|
||||
true if the Wayland target is built into GTK.
|
||||
``GTK_SUPPORTS_WIN32``
|
||||
true if the Windows target is built into GTK.
|
||||
``GTK_SUPPORTS_X11``
|
||||
true if the X11 target is built into GTK.
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
|
||||
if (GTK_FIND_VERSION VERSION_LESS 3.90)
|
||||
set(GTK_PC_MODULE "gtk+-3.0")
|
||||
set(GTK_PC_UNIX_PRINT_MODULE "gtk+-unix-print-3.0")
|
||||
set(GTK_4 FALSE)
|
||||
set(GTK_3 TRUE)
|
||||
else ()
|
||||
set(GTK_PC_MODULE "gtk4")
|
||||
set(GTK_PC_UNIX_PRINT_MODULE "gtk4-unix-print")
|
||||
set(GTK_4 TRUE)
|
||||
set(GTK_3 FALSE)
|
||||
endif ()
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(GTK IMPORTED_TARGET ${GTK_PC_MODULE})
|
||||
|
||||
set(GTK_VERSION_OK TRUE)
|
||||
if (GTK_VERSION)
|
||||
if (GTK_FIND_VERSION_EXACT)
|
||||
if (NOT("${GTK_FIND_VERSION}" VERSION_EQUAL "${GTK_VERSION}"))
|
||||
set(GTK_VERSION_OK FALSE)
|
||||
endif ()
|
||||
else ()
|
||||
if ("${GTK_VERSION}" VERSION_LESS "${GTK_FIND_VERSION}")
|
||||
set(GTK_VERSION_OK FALSE)
|
||||
endif ()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Set all the GTK_SUPPORTS_<target> variables to FALSE initially.
|
||||
foreach (gtk_target broadway quartz wayland win32 x11)
|
||||
string(TOUPPER "GTK_SUPPORTS_${gtk_target}" gtk_target)
|
||||
set(${gtk_target} FALSE)
|
||||
endforeach ()
|
||||
|
||||
if (GTK_VERSION AND GTK_VERSION_OK)
|
||||
# Fetch the "targets" variable and set GTK_SUPPORTS_<target>.
|
||||
pkg_get_variable(GTK_TARGETS ${GTK_PC_MODULE} targets)
|
||||
separate_arguments(GTK_TARGETS)
|
||||
foreach (gtk_target ${GTK_TARGETS})
|
||||
string(TOUPPER "GTK_SUPPORTS_${gtk_target}" gtk_target)
|
||||
set(${gtk_target} TRUE)
|
||||
endforeach ()
|
||||
endif ()
|
||||
|
||||
if (TARGET PkgConfig::GTK AND NOT TARGET GTK::GTK)
|
||||
add_library(GTK::GTK INTERFACE IMPORTED GLOBAL)
|
||||
set_property(TARGET GTK::GTK PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES PkgConfig::GTK
|
||||
)
|
||||
endif ()
|
||||
|
||||
# Try to find additional components
|
||||
foreach (gtk_component ${GTK_FIND_COMPONENTS})
|
||||
if (NOT "${gtk_component}" STREQUAL unix-print)
|
||||
message(FATAL_ERROR "Invalid component name: ${gtk_component}")
|
||||
endif ()
|
||||
pkg_check_modules(GTK_UNIX_PRINT IMPORTED_TARGET "${GTK_PC_UNIX_PRINT_MODULE}")
|
||||
if (GTK_FIND_REQUIRED_unix-print AND NOT GTK_UNIX_PRINT_FOUND)
|
||||
message(FATAL_ERROR "Component unix-print not found")
|
||||
endif ()
|
||||
if (TARGET PkgConfig::GTK_UNIX_PRINT AND NOT TARGET GTK::UnixPrint)
|
||||
add_library(GTK::UnixPrint INTERFACE IMPORTED GLOBAL)
|
||||
set_property(TARGET GTK::UnixPrint PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES PkgConfig::GTK_UNIX_PRINT)
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GTK DEFAULT_MSG GTK_VERSION GTK_VERSION_OK)
|
||||
102
defines.h
@@ -1,102 +0,0 @@
|
||||
#ifndef VMIX_DEFINES_H
|
||||
#define VMIX_DEFINES_H
|
||||
|
||||
#define APP_NAME "vimix"
|
||||
#define APP_TITLE " -- Video Live Mixer"
|
||||
#define APP_SETTINGS "vimix.xml"
|
||||
#define XML_VERSION_MAJOR 0
|
||||
#define XML_VERSION_MINOR 2
|
||||
#define MAX_RECENT_HISTORY 20
|
||||
#define MAX_SESSION_LEVEL 3
|
||||
|
||||
#define MINI(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#define MAXI(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#define ABS(a) (((a) < 0) ? -(a) : (a))
|
||||
#define ABS_DIFF(a, b) ( (a) < (b) ? (b - a) : (a - b) )
|
||||
#define SIGN(a) (((a) < 0) ? -1.0 : 1.0)
|
||||
#define SQUARE(a) ((a) * (a))
|
||||
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
|
||||
#define EPSILON 0.00001
|
||||
#define LOG100(val) (50.0/log(10.0)*log((float)val + 1.0))
|
||||
#define EXP100(val) (exp(log(10.0)/50.0*(float)(val))-1.0)
|
||||
#define EUCLIDEAN(P1, P2) sqrt((P1.x() - P2.x()) * (P1.x() - P2.x()) + (P1.y() - P2.y()) * (P1.y() - P2.y()))
|
||||
#define ROUND(val, factor) float( int( val * factor ) ) / factor;
|
||||
|
||||
#define SCENE_UNIT 5.f
|
||||
#define MIN_SCALE 0.01f
|
||||
#define MAX_SCALE 10.f
|
||||
#define CLAMP_SCALE(x) SIGN(x) * CLAMP( ABS(x), MIN_SCALE, MAX_SCALE)
|
||||
#define SCENE_DEPTH 14.f
|
||||
#define MIN_DEPTH 0.f
|
||||
#define MAX_DEPTH 12.f
|
||||
#define DELTA_DEPTH 0.05f
|
||||
#define DELTA_ALPHA 0.0005f
|
||||
#define MIXING_DEFAULT_SCALE 2.4f
|
||||
#define MIXING_MIN_SCALE 0.8f
|
||||
#define MIXING_MAX_SCALE 7.0f
|
||||
#define MIXING_LIMBO_SCALE 1.3f
|
||||
#define MIXING_ICON_SCALE 0.15f, 0.15f, 1.f
|
||||
#define GEOMETRY_DEFAULT_SCALE 1.4f
|
||||
#define GEOMETRY_MIN_SCALE 0.4f
|
||||
#define GEOMETRY_MAX_SCALE 7.0f
|
||||
#define LAYER_DEFAULT_SCALE 0.6f
|
||||
#define LAYER_MIN_SCALE 0.4f
|
||||
#define LAYER_MAX_SCALE 1.7f
|
||||
#define LAYER_PERSPECTIVE 2.0f
|
||||
#define LAYER_BACKGROUND 2.f
|
||||
#define LAYER_FOREGROUND 10.f
|
||||
#define LAYER_STEP 0.25f
|
||||
#define APPEARANCE_DEFAULT_SCALE 2.f
|
||||
#define APPEARANCE_MIN_SCALE 0.4f
|
||||
#define APPEARANCE_MAX_SCALE 7.0f
|
||||
#define BRUSH_MIN_SIZE 0.05f
|
||||
#define BRUSH_MAX_SIZE 2.f
|
||||
#define BRUSH_MIN_PRESS 0.005f
|
||||
#define BRUSH_MAX_PRESS 1.f
|
||||
#define SHAPE_MIN_BLUR 0.f
|
||||
#define SHAPE_MAX_BLUR 1.f
|
||||
#define TRANSITION_DEFAULT_SCALE 5.0f
|
||||
#define TRANSITION_MIN_DURATION 0.2f
|
||||
#define TRANSITION_MAX_DURATION 10.f
|
||||
#define ARROWS_MOVEMENT_FACTOR 4.f
|
||||
|
||||
#define IMGUI_TITLE_MAINWINDOW ICON_FA_CIRCLE_NOTCH " vimix"
|
||||
#define IMGUI_TITLE_MEDIAPLAYER ICON_FA_FILM " Player"
|
||||
#define IMGUI_TITLE_HISTORY ICON_FA_HISTORY " History"
|
||||
#define IMGUI_TITLE_TOOLBOX ICON_FA_WRENCH " Development Tools"
|
||||
#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Code"
|
||||
#define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Ouput"
|
||||
#define IMGUI_TITLE_DELETE ICON_FA_BROOM " Delete?"
|
||||
#define IMGUI_LABEL_RECENT_FILES " Select recent"
|
||||
#define IMGUI_RIGHT_ALIGN -3.5f * ImGui::GetTextLineHeightWithSpacing()
|
||||
#define IMGUI_COLOR_OVERLAY IM_COL32(5, 5, 5, 150)
|
||||
#define IMGUI_COLOR_RECORD 1.0, 0.05, 0.05
|
||||
#define IMGUI_COLOR_STREAM 0.05, 0.8, 1.0
|
||||
#define IMGUI_NOTIFICATION_DURATION 2.5f
|
||||
#ifdef APPLE
|
||||
#define CTRL_MOD "Cmd+"
|
||||
#else
|
||||
#define CTRL_MOD "Ctrl+"
|
||||
#endif
|
||||
|
||||
#define COLOR_BGROUND 0.2f, 0.2f, 0.2f
|
||||
#define COLOR_NAVIGATOR 0.1f, 0.1f, 0.1f
|
||||
#define COLOR_DEFAULT_SOURCE 0.7f, 0.7f, 0.7f
|
||||
#define COLOR_HIGHLIGHT_SOURCE 1.f, 1.f, 1.f
|
||||
#define COLOR_TRANSITION_SOURCE 1.f, 0.5f, 1.f
|
||||
#define COLOR_TRANSITION_LINES 0.9f, 0.9f, 0.9f
|
||||
#define COLOR_APPEARANCE_SOURCE 0.9f, 0.9f, 0.1f
|
||||
#define COLOR_APPEARANCE_LIGHT 1.0f, 1.0f, 0.9f
|
||||
#define COLOR_APPEARANCE_MASK 0.9f, 0.9f, 0.9f
|
||||
#define COLOR_APPEARANCE_MASK_DISABLE 0.6f, 0.6f, 0.6f
|
||||
#define COLOR_FRAME 0.75f, 0.2f, 0.75f
|
||||
#define COLOR_FRAME_LIGHT 0.9f, 0.6f, 0.9f
|
||||
#define COLOR_CIRCLE 0.75f, 0.2f, 0.75f
|
||||
#define COLOR_CIRCLE_OVER 0.8f, 0.8f, 0.8f
|
||||
#define COLOR_MIXING_GROUP 0.f, 0.95f, 0.2f
|
||||
#define COLOR_LIMBO_CIRCLE 0.16f, 0.16f, 0.16f
|
||||
#define COLOR_SLIDER_CIRCLE 0.11f, 0.11f, 0.11f
|
||||
#define COLOR_STASH_CIRCLE 0.06f, 0.06f, 0.06f
|
||||
|
||||
|
||||
#endif // VMIX_DEFINES_H
|
||||
BIN
docs/images/SRT broadcast 1.png
Normal file
|
After Width: | Height: | Size: 377 KiB |
BIN
docs/images/SRT broadcast 2.png
Normal file
|
After Width: | Height: | Size: 971 KiB |
BIN
docs/images/SRT vimix receive 1.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
docs/images/SRT vimix receive 2.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
docs/images/SRT vimix receive 3.png
Normal file
|
After Width: | Height: | Size: 381 KiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 429 KiB |
BIN
docs/images/TouchOSC Mk1 vimix current.jpg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
docs/images/TouchOSC Mk1 vimix mixing.jpg
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 0.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 1.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 2.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 3.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 4.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 5.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 6.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 7.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
docs/images/TouchOSC Mk1 vimix tuto 8.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 592 KiB After Width: | Height: | Size: 436 KiB |
|
Before Width: | Height: | Size: 772 KiB After Width: | Height: | Size: 987 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 846 KiB After Width: | Height: | Size: 745 KiB |
|
Before Width: | Height: | Size: 404 KiB After Width: | Height: | Size: 694 KiB |
|
Before Width: | Height: | Size: 582 KiB After Width: | Height: | Size: 496 KiB |