mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-07 08:20:01 +01:00
Compare commits
686 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
109e6f590a | ||
|
|
8f0491ea57 | ||
|
|
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 | ||
|
|
a7689a8f54 | ||
|
|
731a1af1a6 | ||
|
|
f53ebd4389 | ||
|
|
baa6ddb401 | ||
|
|
315a8534d5 | ||
|
|
d77bd4034d | ||
|
|
fa71797ed2 | ||
|
|
8c63552573 | ||
|
|
a18d53c637 | ||
|
|
ffe05368e8 | ||
|
|
923d84f378 | ||
|
|
e5334aae0a | ||
|
|
4675be7e2a | ||
|
|
bf3fc61ef7 | ||
|
|
ebd9fab312 | ||
|
|
d359cf33d1 | ||
|
|
14bab1e299 | ||
|
|
4c4ad144b9 | ||
|
|
1157c0b1c5 | ||
|
|
68b2c5e0c1 | ||
|
|
b97fd06f2a | ||
|
|
51f0f5bd66 | ||
|
|
66f445997d | ||
|
|
73d4f7c1ea | ||
|
|
25fc5562db | ||
|
|
4d52bcb5b3 | ||
|
|
3d2de560b0 | ||
|
|
809e30d906 | ||
|
|
ef9e41f20d | ||
|
|
1b4849f214 | ||
|
|
e123d139e4 | ||
|
|
a8abd52afb | ||
|
|
091e99f21b | ||
|
|
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 | ||
|
|
4e83cdf30f | ||
|
|
71891292b4 | ||
|
|
10ac384e7e | ||
|
|
6e7df60f2c | ||
|
|
112b583379 | ||
|
|
b07009f3ce | ||
|
|
74e9553d56 | ||
|
|
2c3d6ff02e | ||
|
|
8dd47b3a41 | ||
|
|
09f052a5d6 | ||
|
|
ac5e885fb3 | ||
|
|
dd9c8ac0b8 | ||
|
|
0de1b87028 | ||
|
|
e830a6eefe | ||
|
|
3c875a064e | ||
|
|
2227c97a57 | ||
|
|
ea211cb8ab | ||
|
|
6d2112fcd9 | ||
|
|
05cb1db020 | ||
|
|
cd4d8f02cb | ||
|
|
b8fe0d7c69 | ||
|
|
81c173e9c3 | ||
|
|
63c954dedc | ||
|
|
91d1ff1eb1 | ||
|
|
41efc572e0 | ||
|
|
77764248b5 | ||
|
|
ca0058c741 | ||
|
|
8bd74ec725 | ||
|
|
9a5983d6de | ||
|
|
ce38bf72b8 | ||
|
|
ecba54196f | ||
|
|
3b09bc877c | ||
|
|
92663aa171 | ||
|
|
c41d7ee067 | ||
|
|
5ab5f1b60f | ||
|
|
10f9c1b329 | ||
|
|
2d62ab969c | ||
|
|
7656113dcc | ||
|
|
56f0165d75 | ||
|
|
d79c4cbfe1 | ||
|
|
a55765c100 | ||
|
|
134617bbd1 | ||
|
|
2ccedd42e4 | ||
|
|
d6d1ab5099 | ||
|
|
b8d323ad59 | ||
|
|
737269bf5a | ||
|
|
e54389b79c | ||
|
|
2906c50642 | ||
|
|
8123e61e34 | ||
|
|
70cc66a7f4 | ||
|
|
13672a9d01 | ||
|
|
f2cd18f754 | ||
|
|
7e723f4142 | ||
|
|
adcd735127 | ||
|
|
70c28d4226 | ||
|
|
004e1aaead | ||
|
|
e7a5d341e4 | ||
|
|
f7b93478ed | ||
|
|
afc0c7af0e | ||
|
|
0ee5eebf91 | ||
|
|
d0fdbeb14f | ||
|
|
38f1288571 | ||
|
|
4093170599 | ||
|
|
27112a2b57 | ||
|
|
ef7722bb5c | ||
|
|
8019f4ea25 | ||
|
|
a612395ca3 | ||
|
|
4718bf166f | ||
|
|
f51bc1f1f4 | ||
|
|
64071a4a55 | ||
|
|
678bdf066e | ||
|
|
cb5562eca2 | ||
|
|
23386fccc2 | ||
|
|
4b1d6a8ac0 | ||
|
|
146408607a | ||
|
|
ffee2f067a | ||
|
|
935762506d | ||
|
|
885ce67174 | ||
|
|
e37b21760e | ||
|
|
25c2bb59f5 | ||
|
|
a1e4709910 | ||
|
|
0593e46e62 | ||
|
|
dca3033c06 | ||
|
|
d45554e162 | ||
|
|
8c4d3f3a18 | ||
|
|
6bb5c0d208 | ||
|
|
209caadd44 | ||
|
|
89fa11447a | ||
|
|
84416f566b | ||
|
|
65564065d9 | ||
|
|
79540c0232 | ||
|
|
5d23a285b4 | ||
|
|
1964a26fc3 | ||
|
|
e37a189bae | ||
|
|
5328995a79 | ||
|
|
6929eb3307 | ||
|
|
33f00f9da4 | ||
|
|
34380e8592 | ||
|
|
8185c93457 | ||
|
|
93b6bc9ca4 | ||
|
|
d76dfa4a9d | ||
|
|
d23267d333 | ||
|
|
e8a258094f | ||
|
|
ffb30bc292 | ||
|
|
fa798c8809 | ||
|
|
ac15bbc840 | ||
|
|
e26052013c | ||
|
|
9215be6bfc | ||
|
|
691c6d174b | ||
|
|
4bc9bf581e | ||
|
|
3686106dab | ||
|
|
62bc779dee | ||
|
|
843fa86c00 | ||
|
|
9bfc5b269a | ||
|
|
a7b6a67a92 | ||
|
|
9b795a0df7 | ||
|
|
29c40036b2 | ||
|
|
49e845137a | ||
|
|
394bfe2da4 | ||
|
|
6607bd319c | ||
|
|
54c5eb6155 | ||
|
|
49ec387cfa | ||
|
|
0ef6164b24 | ||
|
|
87a25ca19f | ||
|
|
e564b63f77 | ||
|
|
0e6ad3e25c | ||
|
|
c3442a1090 | ||
|
|
83e5c37b60 | ||
|
|
2dda3da8b1 | ||
|
|
7e6ee0806d | ||
|
|
9c0adb4ce6 | ||
|
|
b17136d23a | ||
|
|
edeec9568e | ||
|
|
e5ed27180f | ||
|
|
207ac11ded | ||
|
|
2bc8420c24 | ||
|
|
f4048fca04 | ||
|
|
9942d8e628 | ||
|
|
2fe282ef6a | ||
|
|
12dcd34b3d | ||
|
|
0cf4732347 | ||
|
|
4e6a402142 | ||
|
|
9449936df0 | ||
|
|
7c555465b8 | ||
|
|
5262b8ae29 | ||
|
|
1028aeea9f | ||
|
|
e02071047a | ||
|
|
227658d2fc | ||
|
|
56dc299fc9 | ||
|
|
f7e1ff14d9 | ||
|
|
9e865b3677 | ||
|
|
e4da7de06f | ||
|
|
67f45793da | ||
|
|
5a2949609e | ||
|
|
f20597656e | ||
|
|
aaf700baba | ||
|
|
a3e121d6a0 | ||
|
|
d3269e8aaa | ||
|
|
b9104df26e | ||
|
|
717f560326 | ||
|
|
6fdb93a020 | ||
|
|
767b0d8084 | ||
|
|
52eb9284f7 | ||
|
|
c355486955 | ||
|
|
0e8f87d6c6 | ||
|
|
dbd3c071e8 | ||
|
|
398995648a | ||
|
|
3fc9401d97 | ||
|
|
d31320ae4b | ||
|
|
fe54afbe1c | ||
|
|
6b5ccb4450 | ||
|
|
805baa75f4 | ||
|
|
b1dd2f0bc9 | ||
|
|
f8e926040a | ||
|
|
11690dfb8c | ||
|
|
63369223ca | ||
|
|
a1e81b58b1 | ||
|
|
cf2b6b4b39 | ||
|
|
c4e584a1da | ||
|
|
25b58b76f3 | ||
|
|
b346403887 | ||
|
|
e0cd560dfb | ||
|
|
4313e51530 | ||
|
|
e2bb90208e | ||
|
|
85d72a1c0e | ||
|
|
a073ab41dd | ||
|
|
34c24d99df | ||
|
|
69d9d9473b | ||
|
|
a58c06c617 | ||
|
|
b7a54d0512 | ||
|
|
1677582034 | ||
|
|
44b888fd04 | ||
|
|
78f9216d32 | ||
|
|
1ea0ec53af | ||
|
|
688823e63f |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -16,3 +16,13 @@ rules.ninja
|
||||
/vmix
|
||||
/vimix_*.snap
|
||||
/CMakeLists.txt.user.*
|
||||
|
||||
rsc/shaders/paint.fs
|
||||
|
||||
osx/.DS_Store
|
||||
|
||||
.DS_Store
|
||||
|
||||
osx/runvimix
|
||||
|
||||
*.autosave
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -19,3 +19,6 @@
|
||||
[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
|
||||
|
||||
@@ -1,13 +1,38 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
#include "Log.h"
|
||||
#include "View.h"
|
||||
#include "Mixer.h"
|
||||
#include "MixingGroup.h"
|
||||
#include "tinyxml2Toolkit.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "Settings.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "Interpolator.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
#include "ActionManager.h"
|
||||
|
||||
@@ -15,241 +40,476 @@
|
||||
#define ACTION_DEBUG
|
||||
#endif
|
||||
|
||||
#define HISTORY_NODE(i) std::to_string(i).insert(0,1,'H')
|
||||
#define SNAPSHOT_NODE(i) std::to_string(i).insert(0,1,'S')
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
Action::Action(): step_(0), max_step_(0)
|
||||
void captureMixerSession(tinyxml2::XMLDocument *doc, std::string node, std::string label)
|
||||
{
|
||||
}
|
||||
|
||||
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, uint64_t id)
|
||||
{
|
||||
// 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);
|
||||
// create node
|
||||
XMLElement *sessionNode = doc->NewElement( node.c_str() );
|
||||
doc->InsertEndChild(sessionNode);
|
||||
// label describes the action
|
||||
sessionNode->SetAttribute("label", label.c_str());
|
||||
// id indicates which object was modified
|
||||
sessionNode->SetAttribute("id", id);
|
||||
sessionNode->SetAttribute("label", label.c_str() );
|
||||
// label describes the action
|
||||
sessionNode->SetAttribute("date", SystemToolkit::date_time_string().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();
|
||||
|
||||
// get the thumbnail (requires one opengl update to render)
|
||||
FrameBufferImage *thumbnail = se->renderThumbnail();
|
||||
if (thumbnail) {
|
||||
XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, doc);
|
||||
if (imageelement)
|
||||
sessionNode->InsertEndChild(imageelement);
|
||||
delete thumbnail;
|
||||
}
|
||||
|
||||
// save session attributes
|
||||
sessionNode->SetAttribute("activationThreshold", se->activationThreshold());
|
||||
|
||||
// save all sources using source visitor
|
||||
SessionVisitor sv(&xmlDoc_, sessionNode);
|
||||
for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
|
||||
SessionVisitor sv(doc, sessionNode);
|
||||
for (auto iter = se->begin(); iter != se->end(); ++iter, sv.setRoot(sessionNode) )
|
||||
(*iter)->accept(sv);
|
||||
|
||||
// debug
|
||||
}
|
||||
|
||||
|
||||
Action::Action(): history_step_(0), history_max_step_(0), locked_(false),
|
||||
snapshot_id_(0), snapshot_node_(nullptr), interpolator_(nullptr), interpolator_node_(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Action::init()
|
||||
{
|
||||
// clean the history
|
||||
history_doc_.Clear();
|
||||
history_step_ = 0;
|
||||
history_max_step_ = 0;
|
||||
|
||||
// reset snapshot
|
||||
snapshot_id_ = 0;
|
||||
snapshot_node_ = nullptr;
|
||||
|
||||
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
|
||||
history_step_++;
|
||||
|
||||
// erase future
|
||||
for (uint e = history_step_; e <= history_max_step_; e++) {
|
||||
XMLElement *node = history_doc_.FirstChildElement( HISTORY_NODE(e).c_str() );
|
||||
if ( node )
|
||||
history_doc_.DeleteChild(node);
|
||||
}
|
||||
history_max_step_ = history_step_;
|
||||
|
||||
// threaded capturing state of current session
|
||||
std::thread(captureMixerSession, &history_doc_, HISTORY_NODE(history_step_), label).detach();
|
||||
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Action stored %s '%s'", nodename.c_str(), label.c_str());
|
||||
// XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml");
|
||||
Log::Info("Action stored %d '%s'", history_step_, label.c_str());
|
||||
// XMLSaveDoc(&history_doc_, "/home/bhbn/history.xml");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Action::undo()
|
||||
{
|
||||
// not possible to go to 1 -1 = 0
|
||||
if (step_ <= 1)
|
||||
if (history_step_ <= 1)
|
||||
return;
|
||||
|
||||
// what id was modified to get to this step ?
|
||||
// get history node of current step
|
||||
std::string nodename = "H" + std::to_string(step_);
|
||||
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
|
||||
uint64_t id = 0;
|
||||
sessionNode->QueryUnsigned64Attribute("id", &id);
|
||||
|
||||
// restore always changes step_ to step_ - 1
|
||||
restore( step_ - 1, id);
|
||||
restore( history_step_ - 1);
|
||||
}
|
||||
|
||||
void Action::redo()
|
||||
{
|
||||
// not possible to go to max_step_ + 1
|
||||
if (step_ >= max_step_)
|
||||
if (history_step_ >= history_max_step_)
|
||||
return;
|
||||
|
||||
// what id to modify to go to next step ?
|
||||
std::string nodename = "H" + std::to_string(step_ + 1);
|
||||
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
|
||||
uint64_t id = 0;
|
||||
sessionNode->QueryUnsigned64Attribute("id", &id);
|
||||
|
||||
// restore always changes step_ to step_ + 1
|
||||
restore( step_ + 1, id);
|
||||
restore( history_step_ + 1);
|
||||
}
|
||||
|
||||
|
||||
void Action::stepTo(uint target)
|
||||
{
|
||||
// get reasonable target
|
||||
uint t = CLAMP(target, 1, max_step_);
|
||||
uint t = CLAMP(target, 1, history_max_step_);
|
||||
|
||||
// going backward
|
||||
if ( t < step_ ) {
|
||||
// go back one step at a time
|
||||
while (t < step_)
|
||||
undo();
|
||||
}
|
||||
// step forward
|
||||
else if ( t > step_ ) {
|
||||
// go forward one step at a time
|
||||
while (t > step_)
|
||||
redo();
|
||||
}
|
||||
// ignore t == step_
|
||||
if (t != history_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");
|
||||
if (s > 0 && s <= history_max_step_) {
|
||||
const XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(s).c_str());
|
||||
if (sessionNode)
|
||||
l = sessionNode->Attribute("label");
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
void Action::restore(uint target, uint64_t id)
|
||||
FrameBufferImage *Action::thumbnail(uint s) const
|
||||
{
|
||||
FrameBufferImage *img = nullptr;
|
||||
|
||||
if (s > 0 && s <= history_max_step_) {
|
||||
const XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(s).c_str());
|
||||
if (sessionNode)
|
||||
img = SessionLoader::XMLToImage(sessionNode);
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
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() );
|
||||
history_step_ = CLAMP(target, 1, history_max_step_);
|
||||
XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(history_step_).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);
|
||||
if (sessionNode) {
|
||||
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Restore %s '%s' ", nodename.c_str(), sessionNode->Attribute("label"));
|
||||
#endif
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
std::list<uint64_t> sessionsources = 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 session), it is created in the session
|
||||
SessionLoader loader( se );
|
||||
loader.load( sessionNode );
|
||||
|
||||
// loadersources contains list of ids of all sources generated by loader
|
||||
std::list<uint64_t> loadersources = loader.getIdList();
|
||||
// for( auto it = loadersources.begin(); it != loadersources.end(); it++)
|
||||
// Log::Info("loadersources id %s", std::to_string(*it).c_str());
|
||||
|
||||
// remove intersect of both lists (sources were updated by SessionLoader)
|
||||
for( auto lsit = loadersources.begin(); lsit != loadersources.end(); ){
|
||||
auto ssit = std::find(sessionsources.begin(), sessionsources.end(), (*lsit));
|
||||
if ( ssit != sessionsources.end() ) {
|
||||
lsit = loadersources.erase(lsit);
|
||||
sessionsources.erase(ssit);
|
||||
}
|
||||
else
|
||||
lsit++;
|
||||
}
|
||||
// remaining ids in list sessionsources : to remove
|
||||
while ( !sessionsources.empty() ){
|
||||
Source *s = Mixer::manager().findSource( sessionsources.front() );
|
||||
if (s!=nullptr) {
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Delete id %s", std::to_string(sessionsources.front() ).c_str());
|
||||
#endif
|
||||
// remove the source from the mixer
|
||||
Mixer::manager().detach( s );
|
||||
// delete source from session
|
||||
se->deleteSource( s );
|
||||
}
|
||||
sessionsources.pop_front();
|
||||
}
|
||||
// remaining ids in list loadersources : to add
|
||||
while ( !loadersources.empty() ){
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Recreate id %s to %s", std::to_string(id).c_str(), std::to_string(loadersources.front()).c_str());
|
||||
#endif
|
||||
// change the history to match the new id
|
||||
replaceSourceId(id, loadersources.front());
|
||||
// add the source to the mixer
|
||||
Mixer::manager().attach( Mixer::manager().findSource( loadersources.front() ) );
|
||||
loadersources.pop_front();
|
||||
// actually restore
|
||||
Mixer::manager().restore(sessionNode);
|
||||
}
|
||||
|
||||
// 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())
|
||||
{
|
||||
// check if this history node references this id
|
||||
uint64_t id_history_ = 0;
|
||||
historyNode->QueryUnsigned64Attribute("id", &id_history_);
|
||||
if ( id_history_ == previousid )
|
||||
// change to new id
|
||||
historyNode->SetAttribute("id", newid);
|
||||
|
||||
// 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);
|
||||
void Action::snapshot(const std::string &label, bool threaded)
|
||||
{
|
||||
// ignore if locked
|
||||
if (locked_)
|
||||
return;
|
||||
|
||||
std::string snap_label = BaseToolkit::uniqueName(label, labels());
|
||||
|
||||
// create snapshot id
|
||||
u_int64_t id = BaseToolkit::uniqueId();
|
||||
|
||||
// get session to operate on
|
||||
Session *se = Mixer::manager().session();
|
||||
se->snapshots()->keys_.push_back(id);
|
||||
|
||||
if (threaded)
|
||||
// threaded capture state of current session
|
||||
std::thread(captureMixerSession, se->snapshots()->xmlDoc_, SNAPSHOT_NODE(id), snap_label).detach();
|
||||
else
|
||||
captureMixerSession(se->snapshots()->xmlDoc_, SNAPSHOT_NODE(id), snap_label);
|
||||
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Snapshot stored %d '%s'", id, snap_label.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void Action::open(uint64_t snapshotid)
|
||||
{
|
||||
if ( snapshot_id_ != snapshotid )
|
||||
{
|
||||
// get snapshot node of target in current session
|
||||
Session *se = Mixer::manager().session();
|
||||
snapshot_node_ = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
|
||||
|
||||
if (snapshot_node_)
|
||||
snapshot_id_ = snapshotid;
|
||||
else
|
||||
snapshot_id_ = 0;
|
||||
|
||||
interpolator_node_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Action::replace(uint64_t snapshotid)
|
||||
{
|
||||
// ignore if locked or if no label is given
|
||||
if (locked_)
|
||||
return;
|
||||
|
||||
if (snapshotid > 0)
|
||||
open(snapshotid);
|
||||
|
||||
if (snapshot_node_) {
|
||||
// remember label
|
||||
std::string label = snapshot_node_->Attribute("label");
|
||||
|
||||
// remove previous node
|
||||
Session *se = Mixer::manager().session();
|
||||
se->snapshots()->xmlDoc_->DeleteChild( snapshot_node_ );
|
||||
|
||||
// threaded capture state of current session
|
||||
std::thread(captureMixerSession, se->snapshots()->xmlDoc_, SNAPSHOT_NODE(snapshot_id_), label).detach();
|
||||
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Snapshot replaced %d '%s'", snapshot_id_, label.c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
std::list<uint64_t> Action::snapshots() const
|
||||
{
|
||||
return Mixer::manager().session()->snapshots()->keys_;
|
||||
}
|
||||
|
||||
std::list<std::string> Action::labels() const
|
||||
{
|
||||
std::list<std::string> names;
|
||||
|
||||
tinyxml2::XMLDocument *doc = Mixer::manager().session()->snapshots()->xmlDoc_;
|
||||
for ( XMLElement *snap = doc->FirstChildElement(); snap ; snap = snap->NextSiblingElement() )
|
||||
names.push_back( snap->Attribute("label"));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
std::string Action::label(uint64_t snapshotid) const
|
||||
{
|
||||
std::string label = "";
|
||||
|
||||
// get snapshot node of target in current session
|
||||
Session *se = Mixer::manager().session();
|
||||
const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
|
||||
|
||||
if (snap)
|
||||
label = snap->Attribute("label");
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
std::string Action::date(uint64_t snapshotid) const
|
||||
{
|
||||
std::string date = "";
|
||||
|
||||
// get snapshot node of target in current session
|
||||
Session *se = Mixer::manager().session();
|
||||
const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
|
||||
|
||||
if (snap){
|
||||
const char *d = snap->Attribute("date");
|
||||
if (d)
|
||||
date = std::string(d);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
void Action::setLabel (uint64_t snapshotid, const std::string &label)
|
||||
{
|
||||
open(snapshotid);
|
||||
|
||||
if (snapshot_node_)
|
||||
snapshot_node_->SetAttribute("label", label.c_str());
|
||||
}
|
||||
|
||||
FrameBufferImage *Action::thumbnail(uint64_t snapshotid) const
|
||||
{
|
||||
FrameBufferImage *img = nullptr;
|
||||
|
||||
// get snapshot node of target in current session
|
||||
Session *se = Mixer::manager().session();
|
||||
const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() );
|
||||
|
||||
if (snap){
|
||||
img = SessionLoader::XMLToImage(snap);
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
void Action::clearSnapshots()
|
||||
{
|
||||
Session *se = Mixer::manager().session();
|
||||
while (!se->snapshots()->keys_.empty())
|
||||
remove(se->snapshots()->keys_.front());
|
||||
}
|
||||
|
||||
void Action::remove(uint64_t snapshotid)
|
||||
{
|
||||
if (snapshotid > 0)
|
||||
open(snapshotid);
|
||||
|
||||
if (snapshot_node_) {
|
||||
// remove
|
||||
Session *se = Mixer::manager().session();
|
||||
se->snapshots()->xmlDoc_->DeleteChild( snapshot_node_ );
|
||||
se->snapshots()->keys_.remove( snapshot_id_ );
|
||||
}
|
||||
|
||||
snapshot_node_ = nullptr;
|
||||
snapshot_id_ = 0;
|
||||
}
|
||||
|
||||
void Action::restore(uint64_t snapshotid)
|
||||
{
|
||||
// lock
|
||||
locked_ = true;
|
||||
|
||||
if (snapshotid > 0)
|
||||
open(snapshotid);
|
||||
|
||||
if (snapshot_node_)
|
||||
// actually restore
|
||||
Mixer::manager().restore(snapshot_node_);
|
||||
|
||||
// free
|
||||
locked_ = false;
|
||||
|
||||
store("Snapshot " + label(snapshot_id_));
|
||||
}
|
||||
|
||||
float Action::interpolation()
|
||||
{
|
||||
float ret = 0.f;
|
||||
if ( interpolator_node_ == snapshot_node_ && interpolator_)
|
||||
ret = interpolator_->current();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Action::interpolate(float val, uint64_t snapshotid)
|
||||
{
|
||||
if (snapshotid > 0)
|
||||
open(snapshotid);
|
||||
|
||||
if (snapshot_node_) {
|
||||
|
||||
if ( interpolator_node_ != snapshot_node_ ) {
|
||||
|
||||
// change interpolator
|
||||
if (interpolator_)
|
||||
delete interpolator_;
|
||||
|
||||
// create new interpolator
|
||||
interpolator_ = new Interpolator;
|
||||
|
||||
// current session
|
||||
Session *se = Mixer::manager().session();
|
||||
|
||||
XMLElement* N = snapshot_node_->FirstChildElement("Source");
|
||||
for( ; N ; N = N->NextSiblingElement()) {
|
||||
|
||||
// check if a source with the given id exists in the session
|
||||
uint64_t id_xml_ = 0;
|
||||
N->QueryUnsigned64Attribute("id", &id_xml_);
|
||||
SourceList::iterator sit = se->find(id_xml_);
|
||||
|
||||
// a source with this id exists
|
||||
if ( sit != se->end() ) {
|
||||
// read target in the snapshot xml
|
||||
SourceCore target;
|
||||
SessionLoader::XMLToSourcecore(N, target);
|
||||
|
||||
// add an interpolator for this source
|
||||
interpolator_->add(*sit, target);
|
||||
}
|
||||
}
|
||||
|
||||
// operate interpolation on opened snapshot
|
||||
interpolator_node_ = snapshot_node_;
|
||||
}
|
||||
|
||||
if (interpolator_) {
|
||||
// Log::Info("Action::interpolate %f", val);
|
||||
interpolator_->apply( val );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// static multithreaded version saving
|
||||
static void saveSnapshot(const std::string& filename, tinyxml2::XMLElement *snapshot_node)
|
||||
{
|
||||
if (!snapshot_node){
|
||||
Log::Warning("Invalid version.", filename.c_str());
|
||||
return;
|
||||
}
|
||||
const char *l = snapshot_node->Attribute("label");
|
||||
if (!l) {
|
||||
Log::Warning("Invalid version.", filename.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// load the file: is it a session?
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
XMLError eResult = xmlDoc.LoadFile(filename.c_str());
|
||||
if ( XMLResultError(eResult)){
|
||||
Log::Warning("%s could not be openned for re-export.", 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;
|
||||
}
|
||||
|
||||
// remove all snapshots
|
||||
XMLElement* snapshotNode = xmlDoc.FirstChildElement("Snapshots");
|
||||
xmlDoc.DeleteChild(snapshotNode);
|
||||
|
||||
// swap "Session" node with version_node
|
||||
XMLElement *sessionNode = xmlDoc.FirstChildElement("Session");
|
||||
xmlDoc.DeleteChild(sessionNode);
|
||||
sessionNode = snapshot_node->DeepClone(&xmlDoc)->ToElement();
|
||||
sessionNode->SetName("Session");
|
||||
xmlDoc.InsertEndChild(sessionNode);
|
||||
|
||||
// we got a session, set a new export filename
|
||||
std::string newfilename = filename;
|
||||
newfilename.insert(filename.size()-4, "_" + std::string(l));
|
||||
|
||||
// save new file to disk
|
||||
if ( XMLSaveDoc(&xmlDoc, newfilename) )
|
||||
Log::Notify("Version exported to %s.", newfilename.c_str());
|
||||
else
|
||||
// error
|
||||
Log::Warning("Failed to export Session file %s.", newfilename.c_str());
|
||||
}
|
||||
|
||||
void Action::saveas(const std::string& filename, uint64_t snapshotid)
|
||||
{
|
||||
// ignore if locked or if no label is given
|
||||
if (locked_)
|
||||
return;
|
||||
|
||||
if (snapshotid > 0)
|
||||
open(snapshotid);
|
||||
|
||||
if (snapshot_node_) {
|
||||
// launch a thread to save the session
|
||||
std::thread (saveSnapshot, filename, snapshot_node_).detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,79 @@
|
||||
#ifndef ACTIONMANAGER_H
|
||||
#define ACTIONMANAGER_H
|
||||
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
class Interpolator;
|
||||
class FrameBufferImage;
|
||||
|
||||
class Action
|
||||
{
|
||||
// Private Constructor
|
||||
Action();
|
||||
Action(Action const& copy); // Not Implemented
|
||||
Action& operator=(Action const& copy); // Not Implemented
|
||||
Action(Action const& copy) = delete;
|
||||
Action& operator=(Action const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
static Action& manager()
|
||||
static Action& manager ()
|
||||
{
|
||||
// The only instance
|
||||
static Action _instance;
|
||||
return _instance;
|
||||
}
|
||||
void init ();
|
||||
|
||||
void store(const std::string &label, uint64_t id = 0);
|
||||
// Undo History
|
||||
void store (const std::string &label);
|
||||
void undo ();
|
||||
void redo ();
|
||||
void stepTo (uint target);
|
||||
|
||||
void clear();
|
||||
void undo();
|
||||
void redo();
|
||||
void stepTo(uint target);
|
||||
inline uint current () const { return history_step_; }
|
||||
inline uint max () const { return history_max_step_; }
|
||||
std::string label (uint s) const;
|
||||
FrameBufferImage *thumbnail (uint s) const;
|
||||
|
||||
inline uint current() const { return step_; }
|
||||
inline uint max() const { return max_step_; }
|
||||
// Snapshots
|
||||
void snapshot (const std::string &label = "", bool threaded = false);
|
||||
void clearSnapshots ();
|
||||
std::list<uint64_t> snapshots () const;
|
||||
uint64_t currentSnapshot () const { return snapshot_id_; }
|
||||
|
||||
std::string label(uint s) const;
|
||||
void open (uint64_t snapshotid);
|
||||
void replace (uint64_t snapshotid = 0);
|
||||
void restore (uint64_t snapshotid = 0);
|
||||
void remove (uint64_t snapshotid = 0);
|
||||
void saveas (const std::string& filename, uint64_t snapshotid = 0);
|
||||
|
||||
std::string label (uint64_t snapshotid) const;
|
||||
std::string date (uint64_t snapshotid) const;
|
||||
std::list<std::string> labels () const;
|
||||
void setLabel (uint64_t snapshotid, const std::string &label);
|
||||
FrameBufferImage *thumbnail (uint64_t snapshotid) const;
|
||||
|
||||
float interpolation ();
|
||||
void interpolate (float val, uint64_t snapshotid = 0);
|
||||
|
||||
private:
|
||||
|
||||
void restore(uint target, uint64_t id);
|
||||
void replaceSourceId(uint64_t previousid, uint64_t newid);
|
||||
|
||||
tinyxml2::XMLDocument xmlDoc_;
|
||||
uint step_;
|
||||
uint max_step_;
|
||||
tinyxml2::XMLDocument history_doc_;
|
||||
uint history_step_;
|
||||
uint history_max_step_;
|
||||
std::atomic<bool> locked_;
|
||||
void restore(uint target);
|
||||
|
||||
uint64_t snapshot_id_;
|
||||
tinyxml2::XMLElement *snapshot_node_;
|
||||
|
||||
Interpolator *interpolator_;
|
||||
tinyxml2::XMLElement *interpolator_node_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // ACTIONMANAGER_H
|
||||
|
||||
322
BaseToolkit.cpp
Normal file
322
BaseToolkit.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include "BaseToolkit.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <map>
|
||||
|
||||
#include <locale>
|
||||
#include <unicode/ustream.h>
|
||||
#include <unicode/translit.h>
|
||||
|
||||
uint64_t BaseToolkit::uniqueId()
|
||||
{
|
||||
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
|
||||
// 64-bit int 18446744073709551615UL
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000000000000UL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string BaseToolkit::uniqueName(const std::string &basename, std::list<std::string> existingnames)
|
||||
{
|
||||
std::string tentativename = basename;
|
||||
int count = 1;
|
||||
int max = 100;
|
||||
|
||||
// while tentativename can be found in the list of existingnames
|
||||
while ( std::find( existingnames.begin(), existingnames.end(), tentativename ) != existingnames.end() )
|
||||
{
|
||||
for( auto it = existingnames.cbegin(); it != existingnames.cend(); ++it) {
|
||||
if ( it->find(tentativename) != std::string::npos)
|
||||
++count;
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
tentativename = basename + "_" + std::to_string( count );
|
||||
else
|
||||
tentativename += "_";
|
||||
|
||||
if ( --max < 0 ) // for safety only, should never be needed
|
||||
break;
|
||||
}
|
||||
|
||||
return tentativename;
|
||||
}
|
||||
|
||||
// Using ICU transliteration :
|
||||
// https://unicode-org.github.io/icu/userguide/transforms/general/#icu-transliterators
|
||||
|
||||
std::string BaseToolkit::transliterate(const std::string &input)
|
||||
{
|
||||
// because icu::Transliterator is slow, we keep a dictionnary of already
|
||||
// transliterated texts to be faster during repeated calls (update of user interface)
|
||||
static std::map<std::string, std::string> dictionnary_;
|
||||
std::map<std::string, std::string>::const_iterator existingentry = dictionnary_.find(input);
|
||||
|
||||
if (existingentry == dictionnary_.cend()) {
|
||||
|
||||
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);
|
||||
delete firstTrans;
|
||||
|
||||
icu::Transliterator *secondTrans = icu::Transliterator::createInstance(
|
||||
"any-NFKD ; [:Nonspacing Mark:] Remove; [@!#$*%~] Remove; NFKC", UTRANS_FORWARD, status);
|
||||
secondTrans->transliterate(ucs);
|
||||
delete secondTrans;
|
||||
|
||||
std::ostringstream output;
|
||||
output << ucs;
|
||||
|
||||
// remember for future
|
||||
dictionnary_[input] = output.str();
|
||||
}
|
||||
|
||||
// return remembered transliterated text
|
||||
return dictionnary_[input];
|
||||
}
|
||||
|
||||
|
||||
std::string BaseToolkit::unspace(const std::string &input)
|
||||
{
|
||||
std::string output = input;
|
||||
std::replace( output.begin(), output.end(), ' ', '_');
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string BaseToolkit::byte_to_string(long b)
|
||||
{
|
||||
double numbytes = static_cast<double>(b);
|
||||
std::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;
|
||||
if (i != list.end()) oss << *i;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string BaseToolkit::bits_to_string(long b)
|
||||
{
|
||||
double numbytes = static_cast<double>(b);
|
||||
std::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;
|
||||
if (i != list.end()) oss << *i;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
std::string BaseToolkit::truncated(const std::string& str, int N)
|
||||
{
|
||||
std::string trunc = str;
|
||||
int l = str.size();
|
||||
if ( l > N ) {
|
||||
trunc = std::string("...") + str.substr( l - N + 3 );
|
||||
}
|
||||
return trunc;
|
||||
}
|
||||
|
||||
std::list<std::string> BaseToolkit::splitted(const std::string& str, char delim)
|
||||
{
|
||||
std::list<std::string> strings;
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
while ((start = str.find_first_not_of(delim, end)) != std::string::npos) {
|
||||
end = str.find(delim, start);
|
||||
size_t delta = start > 0 ? 1 : 0;
|
||||
strings.push_back(str.substr( start -delta, end - start + delta));
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
|
||||
std::string BaseToolkit::joinned(std::list<std::string> strlist, char separator)
|
||||
{
|
||||
std::string str;
|
||||
for (auto it = strlist.cbegin(); it != strlist.cend(); ++it)
|
||||
str += (*it) + separator;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
bool BaseToolkit::is_a_number(const std::string& str, int *val)
|
||||
{
|
||||
bool isanumber = false;
|
||||
|
||||
try {
|
||||
*val = std::stoi(str);
|
||||
isanumber = true;
|
||||
}
|
||||
catch (const std::invalid_argument&) {
|
||||
// avoids crash
|
||||
}
|
||||
|
||||
return isanumber;
|
||||
}
|
||||
|
||||
std::string BaseToolkit::common_prefix( const std::list<std::string> & allStrings )
|
||||
{
|
||||
if (allStrings.empty())
|
||||
return std::string();
|
||||
|
||||
const std::string &s0 = allStrings.front();
|
||||
auto _end = s0.cend();
|
||||
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
|
||||
{
|
||||
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
|
||||
if (std::distance(_loc.first, _end) > 0)
|
||||
_end = _loc.first;
|
||||
}
|
||||
|
||||
return std::string(s0.cbegin(), _end);
|
||||
}
|
||||
|
||||
|
||||
std::string BaseToolkit::common_suffix(const std::list<std::string> & allStrings)
|
||||
{
|
||||
if (allStrings.empty())
|
||||
return std::string();
|
||||
|
||||
const std::string &s0 = allStrings.front();
|
||||
auto r_end = s0.crend();
|
||||
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
|
||||
{
|
||||
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
|
||||
if (std::distance(r_loc.first, r_end) > 0)
|
||||
r_end = r_loc.first;
|
||||
}
|
||||
|
||||
std::string suffix = std::string(s0.crbegin(), r_end);
|
||||
std::reverse(suffix.begin(), suffix.end());
|
||||
return suffix;
|
||||
}
|
||||
|
||||
|
||||
std::string BaseToolkit::common_pattern(const std::list<std::string> &allStrings)
|
||||
{
|
||||
if (allStrings.empty())
|
||||
return std::string();
|
||||
|
||||
// find common prefix and suffix
|
||||
const std::string &s0 = allStrings.front();
|
||||
auto _end = s0.cend();
|
||||
auto r_end = s0.crend();
|
||||
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
|
||||
{
|
||||
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
|
||||
if (std::distance(_loc.first, _end) > 0)
|
||||
_end = _loc.first;
|
||||
|
||||
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
|
||||
if (std::distance(r_loc.first, r_end) > 0)
|
||||
r_end = r_loc.first;
|
||||
}
|
||||
|
||||
std::string suffix = std::string(s0.crbegin(), r_end);
|
||||
std::reverse(suffix.begin(), suffix.end());
|
||||
|
||||
return std::string(s0.cbegin(), _end) + "*" + suffix;
|
||||
}
|
||||
|
||||
std::string BaseToolkit::common_numbered_pattern(const std::list<std::string> &allStrings, int *min, int *max)
|
||||
{
|
||||
if (allStrings.empty())
|
||||
return std::string();
|
||||
|
||||
// find common prefix and suffix
|
||||
const std::string &s0 = allStrings.front();
|
||||
auto _end = s0.cend();
|
||||
auto r_end = s0.crend();
|
||||
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
|
||||
{
|
||||
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
|
||||
if (std::distance(_loc.first, _end) > 0)
|
||||
_end = _loc.first;
|
||||
|
||||
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
|
||||
if (std::distance(r_loc.first, r_end) > 0)
|
||||
r_end = r_loc.first;
|
||||
}
|
||||
|
||||
// range of middle string, after prefix and before suffix
|
||||
size_t pos_prefix = std::distance(s0.cbegin(), _end);
|
||||
size_t pos_suffix = s0.size() - pos_prefix - std::distance(s0.crbegin(), r_end);
|
||||
|
||||
int n = -1;
|
||||
*max = 0;
|
||||
*min = INT_MAX;
|
||||
// loop over all strings to verify there are numbers between prefix and suffix
|
||||
for (auto it = allStrings.cbegin(); it != allStrings.cend(); ++it)
|
||||
{
|
||||
// get middle string, after prefix and before suffix
|
||||
std::string s = it->substr(pos_prefix, pos_suffix);
|
||||
// is this central string ONLY made of digits?
|
||||
if (s.end() == std::find_if(s.begin(), s.end(), [](unsigned char c)->bool { return !isdigit(c); })) {
|
||||
// yes, validate
|
||||
*max = std::max(*max, std::atoi(s.c_str()) );
|
||||
*min = std::min(*min, std::atoi(s.c_str()) );
|
||||
if (n < 0)
|
||||
n = s.size();
|
||||
else if ( n != (int) s.size() ) {
|
||||
n = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
n = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( n < 1 )
|
||||
return std::string();
|
||||
|
||||
std::string suffix = std::string(s0.crbegin(), r_end);
|
||||
std::reverse(suffix.begin(), suffix.end());
|
||||
std::string pattern = std::string(s0.cbegin(), _end);
|
||||
pattern += "%0" + std::to_string(n) + "d";
|
||||
pattern += suffix;
|
||||
return pattern;
|
||||
}
|
||||
53
BaseToolkit.h
Normal file
53
BaseToolkit.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef BASETOOLKIT_H
|
||||
#define BASETOOLKIT_H
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
namespace BaseToolkit
|
||||
{
|
||||
|
||||
// get integer with unique id
|
||||
uint64_t uniqueId();
|
||||
|
||||
// proposes a name that is not already in the list
|
||||
std::string uniqueName(const std::string &basename, std::list<std::string> existingnames);
|
||||
|
||||
// get a transliteration to Latin of any string
|
||||
std::string transliterate(const std::string &input);
|
||||
|
||||
// replaces spaces by underscores in a string
|
||||
std::string unspace(const std::string &input);
|
||||
|
||||
// get a string to display memory size with unit KB, MB, GB, TB
|
||||
std::string byte_to_string(long b);
|
||||
|
||||
// get a string to display bit size with unit Kbit, MBit, Gbit, Tbit
|
||||
std::string bits_to_string(long b);
|
||||
|
||||
// cut a string to display the right most N characters (e.g. /home/me/toto.mpg -> ...ome/me/toto.mpg)
|
||||
std::string truncated(const std::string& str, int N);
|
||||
|
||||
// split a string into list of strings separated by delimitor (e.g. /home/me/toto.mpg -> {home, me, toto.mpg} )
|
||||
std::list<std::string> splitted(const std::string& str, char delim);
|
||||
|
||||
// rebuilds a splitted string
|
||||
std::string joinned(std::list<std::string> strlist, char separator = ' ');
|
||||
|
||||
// returns true if the string
|
||||
bool is_a_number(const std::string& str, int *val = nullptr);
|
||||
|
||||
// find common parts in a list of strings
|
||||
std::string common_prefix(const std::list<std::string> &allStrings);
|
||||
std::string common_suffix(const std::list<std::string> &allStrings);
|
||||
|
||||
// form a pattern "prefix*suffix" (e.g. file list)
|
||||
std::string common_pattern(const std::list<std::string> &allStrings);
|
||||
|
||||
// form a pattern "prefix%03dsuffix" (e.g. numbered file list)
|
||||
std::string common_numbered_pattern(const std::list<std::string> &allStrings, int *min, int *max);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // BASETOOLKIT_H
|
||||
@@ -1,14 +1,18 @@
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "Log.h"
|
||||
#include "View.h"
|
||||
#include "Primitives.h"
|
||||
#include "Source.h"
|
||||
#include "Decorations.h"
|
||||
|
||||
#include "BoundingBoxVisitor.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "Primitives.h"
|
||||
#include "Decorations.h"
|
||||
|
||||
BoundingBoxVisitor::BoundingBoxVisitor(): Visitor()
|
||||
BoundingBoxVisitor::BoundingBoxVisitor(bool force): force_(force), modelview_(glm::identity<glm::mat4>())
|
||||
{
|
||||
modelview_ = glm::identity<glm::mat4>();
|
||||
|
||||
}
|
||||
|
||||
@@ -34,8 +38,8 @@ void BoundingBoxVisitor::visit(Group &n)
|
||||
if (!n.visible_)
|
||||
return;
|
||||
glm::mat4 mv = modelview_;
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
|
||||
if ( (*node)->visible_ )
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
|
||||
if ( (*node)->visible_ || force_)
|
||||
(*node)->accept(*this);
|
||||
modelview_ = mv;
|
||||
}
|
||||
@@ -46,14 +50,13 @@ void BoundingBoxVisitor::visit(Switch &n)
|
||||
if (!n.visible_ || n.numChildren() < 1)
|
||||
return;
|
||||
glm::mat4 mv = modelview_;
|
||||
n.activeChild()->accept(*this);
|
||||
if ( n.activeChild()->visible_ || force_)
|
||||
n.activeChild()->accept(*this);
|
||||
modelview_ = mv;
|
||||
}
|
||||
|
||||
void BoundingBoxVisitor::visit(Primitive &n)
|
||||
{
|
||||
if (!n.visible_)
|
||||
return;
|
||||
|
||||
bbox_.extend(n.bbox().transformed(modelview_));
|
||||
|
||||
@@ -64,3 +67,48 @@ void BoundingBoxVisitor::visit(Scene &n)
|
||||
{
|
||||
n.ws()->accept(*this);
|
||||
}
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox BoundingBoxVisitor::AABB(SourceList l, View *view)
|
||||
{
|
||||
// calculate bbox on selection
|
||||
BoundingBoxVisitor selection_visitor_bbox;
|
||||
for (auto it = l.begin(); it != l.end(); ++it) {
|
||||
// calculate bounding box of area covered by selection
|
||||
selection_visitor_bbox.setModelview( view->scene.ws()->transform_ );
|
||||
(*it)->group( view->mode() )->accept(selection_visitor_bbox);
|
||||
}
|
||||
|
||||
return selection_visitor_bbox.bbox();
|
||||
}
|
||||
|
||||
GlmToolkit::OrientedBoundingBox BoundingBoxVisitor::OBB(SourceList l, View *view)
|
||||
{
|
||||
GlmToolkit::OrientedBoundingBox obb_;
|
||||
|
||||
// try the orientation of each source in the list
|
||||
for (auto source_it = l.begin(); source_it != l.end(); ++source_it) {
|
||||
|
||||
float angle = (*source_it)->group( view->mode() )->rotation_.z;
|
||||
glm::mat4 transform = view->scene.ws()->transform_;
|
||||
transform = glm::rotate(transform, -angle, glm::vec3(0.f, 0.f, 1.f) );
|
||||
|
||||
// calculate bbox of the list in this orientation
|
||||
BoundingBoxVisitor selection_visitor_bbox;
|
||||
for (auto it = l.begin(); it != l.end(); ++it) {
|
||||
// calculate bounding box of area covered by sources' nodes
|
||||
selection_visitor_bbox.setModelview( transform );
|
||||
(*it)->group( view->mode() )->accept(selection_visitor_bbox);
|
||||
}
|
||||
|
||||
// if not initialized or if new bbox is smaller than previous
|
||||
if ( obb_.aabb.isNull() || selection_visitor_bbox.bbox() < obb_.aabb) {
|
||||
// keep this bbox as candidate
|
||||
obb_.aabb = selection_visitor_bbox.bbox();
|
||||
obb_.orientation = glm::vec3(0.f, 0.f, angle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return obb_;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
#include "Visitor.h"
|
||||
|
||||
#include "SourceList.h"
|
||||
class View;
|
||||
|
||||
class BoundingBoxVisitor: public Visitor
|
||||
{
|
||||
glm::mat4 modelview_;
|
||||
bool force_;
|
||||
GlmToolkit::AxisAlignedBoundingBox bbox_;
|
||||
|
||||
public:
|
||||
|
||||
BoundingBoxVisitor();
|
||||
public:
|
||||
BoundingBoxVisitor(bool force = false);
|
||||
|
||||
void setModelview(glm::mat4 modelview);
|
||||
GlmToolkit::AxisAlignedBoundingBox bbox();
|
||||
@@ -23,6 +25,9 @@ public:
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
void visit(Primitive& n) override;
|
||||
|
||||
static GlmToolkit::AxisAlignedBoundingBox AABB(SourceList l, View *view);
|
||||
static GlmToolkit::OrientedBoundingBox OBB(SourceList l, View *view);
|
||||
};
|
||||
|
||||
#endif // BOUNDINGBOXVISITOR_H
|
||||
|
||||
395
CMakeLists.txt
395
CMakeLists.txt
@@ -1,11 +1,37 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.8.0)
|
||||
cmake_minimum_required(VERSION 3.8.2)
|
||||
project(vimix VERSION 0.0.1 LANGUAGES CXX C)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# use git
|
||||
find_package (Git)
|
||||
if(GIT_EXECUTABLE)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} describe --tags
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_DESCRIBE_VERSION
|
||||
RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if(NOT GIT_DESCRIBE_ERROR_CODE)
|
||||
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 0 1 VIMIX_VERSION_MAJOR)
|
||||
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 2 1 VIMIX_VERSION_MINOR)
|
||||
string(SUBSTRING ${GIT_DESCRIBE_VERSION} 4 1 VIMIX_VERSION_PATCH)
|
||||
add_definitions(-DVIMIX_VERSION_MAJOR=${VIMIX_VERSION_MAJOR})
|
||||
add_definitions(-DVIMIX_VERSION_MINOR=${VIMIX_VERSION_MINOR})
|
||||
add_definitions(-DVIMIX_VERSION_PATCH=${VIMIX_VERSION_PATCH})
|
||||
message(STATUS "Compiling vimix version ${VIMIX_VERSION_MAJOR}.${VIMIX_VERSION_MINOR}.${VIMIX_VERSION_PATCH}")
|
||||
else()
|
||||
message(STATUS "Compiling vimix (unknown version)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "ON")
|
||||
set(CMAKE_INCLUDE_CURRENTDIR ON)
|
||||
|
||||
# Find the cmake modules
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules )
|
||||
include(MacroLogFeature)
|
||||
include(MacroFindGStreamerLibrary)
|
||||
|
||||
if(UNIX)
|
||||
if (APPLE)
|
||||
@@ -14,15 +40,22 @@ if(UNIX)
|
||||
# the RPATH to be used when installing
|
||||
set(CMAKE_SKIP_RPATH TRUE)
|
||||
set(OpenGL_DIR /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/)
|
||||
set(CMAKE_OSX_ARCHITECTURES x86_64)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
|
||||
# set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X version to target for deployment")
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
||||
|
||||
# find icu4c in OSX (pretty well hidden...)
|
||||
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/icu4c/lib/pkgconfig")
|
||||
|
||||
else()
|
||||
add_definitions(-DLINUX)
|
||||
|
||||
# linux opengl
|
||||
set(OpenGL_GL_PREFERENCE "GLVND")
|
||||
|
||||
# linux dialogs use GTK
|
||||
find_package(GTK 3.0 REQUIRED)
|
||||
macro_log_feature(GTK_FOUND "GTK" "GTK cross-platform widget toolkit" "http://www.gtk.org" TRUE)
|
||||
|
||||
endif()
|
||||
add_definitions(-DUNIX)
|
||||
elseif(WIN32)
|
||||
@@ -31,8 +64,18 @@ elseif(WIN32)
|
||||
endif()
|
||||
|
||||
|
||||
# Include the CMake RC module
|
||||
include(CMakeRC)
|
||||
# Basics
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
find_package(GLIB2)
|
||||
macro_log_feature(GLIB2_FOUND "GLib" "GTK general-purpose utility library" "http://www.gtk.org" TRUE)
|
||||
|
||||
find_package(GObject)
|
||||
macro_log_feature(GOBJECT_FOUND "GObject" "GTK object-oriented framework" "http://www.gtk.org" TRUE)
|
||||
|
||||
find_package(PNG REQUIRED)
|
||||
macro_log_feature(PNG_FOUND "PNG" "Portable Network Graphics" "http://www.libpng.org" TRUE)
|
||||
|
||||
#
|
||||
# GSTREAMER
|
||||
@@ -42,64 +85,48 @@ find_package(GStreamer 1.0.0 COMPONENTS base)
|
||||
macro_log_feature(GSTREAMER_FOUND "GStreamer"
|
||||
"Open Source Multiplatform Multimedia Framework"
|
||||
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
|
||||
macro_log_feature(GSTREAMER_BASE_LIBRARY_FOUND "GStreamer base library"
|
||||
"${GSTREAMER_BASE_LIBRARY}"
|
||||
"http://gstreamer.freedesktop.org/" FALSE "1.0.0")
|
||||
|
||||
find_package(GStreamerPluginsBase 1.0.0 COMPONENTS app audio video pbutils gl)
|
||||
macro_log_feature(GSTREAMER_APP_LIBRARY_FOUND "GStreamer app library"
|
||||
"${GSTREAMER_APP_LIBRARY}"
|
||||
macro_log_feature(GSTREAMER_APP_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer app library"
|
||||
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
|
||||
|
||||
macro_log_feature(GSTREAMER_AUDIO_LIBRARY_FOUND "GStreamer audio library"
|
||||
"${GSTREAMER_AUDIO_LIBRARY}"
|
||||
macro_log_feature(GSTREAMER_AUDIO_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer audio library"
|
||||
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
|
||||
|
||||
macro_log_feature(GSTREAMER_VIDEO_LIBRARY_FOUND "GStreamer video library"
|
||||
"${GSTREAMER_VIDEO_LIBRARY}"
|
||||
macro_log_feature(GSTREAMER_VIDEO_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer video library"
|
||||
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
|
||||
|
||||
macro_log_feature(GSTREAMER_PBUTILS_LIBRARY_FOUND "GStreamer pbutils library"
|
||||
"${GSTREAMER_PBUTILS_LIBRARY}"
|
||||
macro_log_feature(GSTREAMER_PBUTILS_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer pbutils library"
|
||||
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
|
||||
|
||||
macro_log_feature(GSTREAMER_GL_LIBRARY_FOUND "GStreamer opengl library"
|
||||
"${GSTREAMER_GL_LIBRARY}"
|
||||
macro_log_feature(GSTREAMER_GL_LIBRARY_FOUND "GStreamerPluginsBase" "GStreamer opengl library"
|
||||
"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
|
||||
|
||||
|
||||
#find_package(GStreamerPluginsBad 1.0.0 COMPONENTS player)
|
||||
#macro_log_feature(GSTREAMER_PLAYER_LIBRARY_FOUND "GStreamer player library"
|
||||
#"${GSTREAMER_PLAYER_LIBRARY}"
|
||||
#"http://gstreamer.freedesktop.org/" TRUE "1.0.0")
|
||||
|
||||
|
||||
# Various preprocessor definitions for GST
|
||||
add_definitions(-DGST_DISABLE_XML -DGST_DISABLE_LOADSAVE)
|
||||
|
||||
# Basics
|
||||
find_package(GLIB2)
|
||||
macro_log_feature(GLIB2_FOUND "GLib" "GTK general-purpose utility library" "http://www.gtk.org/" TRUE)
|
||||
|
||||
find_package(GObject)
|
||||
macro_log_feature(GOBJECT_FOUND "GObject" "GTK object-oriented framework" "http://www.gtk.org/" TRUE)
|
||||
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
set(THREAD_LIBRARY Threads::Threads)
|
||||
|
||||
find_package(PNG REQUIRED)
|
||||
set(PNG_LIBRARY PNG::PNG)
|
||||
#
|
||||
# ICU4C
|
||||
#
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(ICU REQUIRED icu-i18n icu-uc icu-io)
|
||||
else ()
|
||||
find_package(ICU REQUIRED COMPONENTS i18n io uc)
|
||||
endif ()
|
||||
macro_log_feature(ICU_FOUND "ICU" "International Components for Unicode" "http://site.icu-project.org" TRUE)
|
||||
|
||||
#
|
||||
# GLFW3
|
||||
# NB: set glfw3_PATH to /usr/local/Cellar/glfw/3.3.2/lib/cmake/glfw3
|
||||
#
|
||||
find_package(glfw3 3.2 REQUIRED)
|
||||
macro_log_feature(glfw3_FOUND "GLFW3" "Open Source multi-platform library for OpenGL" "http://www.glfw.org/" TRUE)
|
||||
set(GLFW_LIBRARY glfw)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(GLFW3 REQUIRED glfw3>=3.2)
|
||||
else ()
|
||||
find_package(glfw3 3.2 REQUIRED)
|
||||
endif()
|
||||
macro_log_feature(GLFW3_FOUND "glfw3" "Open Source multi-platform library for OpenGL" "http://www.glfw.org" TRUE)
|
||||
|
||||
#find_package(OpenGL REQUIRED)
|
||||
|
||||
macro_display_feature_log()
|
||||
|
||||
# static sub packages in ext
|
||||
set(BUILD_STATIC_LIBS ON)
|
||||
@@ -108,14 +135,21 @@ set(BUILD_STATIC_LIBS ON)
|
||||
# GLM
|
||||
#
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/glm)
|
||||
message(STATUS "Compiling 'GLM' OpenGL mathematics https://glm.g-truc.net.")
|
||||
message(STATUS "Compiling 'GLM' OpenGL mathematics https://glm.g-truc.net -- ${CMAKE_CURRENT_SOURCE_DIR}/ext/glm")
|
||||
|
||||
#
|
||||
# Ableton LINK
|
||||
#
|
||||
#add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/link)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/ext/link/AbletonLinkConfig.cmake)
|
||||
message(STATUS "Compiling Ableton 'Link' https://github.com/Ableton/link -- ${CMAKE_CURRENT_SOURCE_DIR}/ext/link")
|
||||
|
||||
#
|
||||
# GLAD
|
||||
#
|
||||
set(GLAD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/glad/include)
|
||||
add_library(GLAD "${CMAKE_CURRENT_SOURCE_DIR}/ext/glad/src/glad.c")
|
||||
message(STATUS "Compiling 'GLAD' Open source multi-language OpenGL loader https://glad.dav1d.de/ -- ${GLAD_INCLUDE_DIR}.")
|
||||
message(STATUS "Compiling 'GLAD' Open source multi-language OpenGL loader https://glad.dav1d.de -- ${GLAD_INCLUDE_DIR}")
|
||||
|
||||
#
|
||||
# DEAR IMGUI
|
||||
@@ -131,23 +165,14 @@ set(IMGUI_SRCS
|
||||
set(IMGUI_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/imgui)
|
||||
add_library(IMGUI "${IMGUI_SRCS}")
|
||||
target_compile_definitions(IMGUI PRIVATE "IMGUI_IMPL_OPENGL_LOADER_GLAD")
|
||||
message(STATUS "Compiling 'Dear ImGui' from https://github.com/ocornut/imgui.git -- ${IMGUI_INCLUDE_DIR}.")
|
||||
|
||||
#
|
||||
# ImGui Color Text Editor
|
||||
#
|
||||
set(IMGUITEXTEDIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit)
|
||||
set(IMGUITEXTEDIT_SRC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit/TextEditor.cpp
|
||||
)
|
||||
message(STATUS "Including 'ImGuiColorTextEdit' from https://github.com/BalazsJako/ImGuiColorTextEdit -- ${IMGUITEXTEDIT_INCLUDE_DIR}.")
|
||||
message(STATUS "Compiling 'Dear ImGui' from https://github.com/ocornut/imgui.git -- ${IMGUI_INCLUDE_DIR}")
|
||||
|
||||
#
|
||||
# TINY XML 2
|
||||
#
|
||||
set(TINYXML2_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyxml2)
|
||||
add_library(TINYXML2 "${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyxml2/tinyxml2.cpp")
|
||||
message(STATUS "Compiling 'TinyXML2' from https://github.com/leethomason/tinyxml2.git -- ${TINYXML2_INCLUDE_DIR}.")
|
||||
message(STATUS "Compiling 'TinyXML2' from https://github.com/leethomason/tinyxml2.git -- ${TINYXML2_INCLUDE_DIR}")
|
||||
|
||||
#
|
||||
# OSCPack
|
||||
@@ -168,40 +193,47 @@ set(OSCPACK_SRCS
|
||||
)
|
||||
set(OSCPACK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack)
|
||||
add_library(OSCPACK "${OSCPACK_SRCS}")
|
||||
message(STATUS "Compiling 'OSCPack' from http://www.rossbencina.com/code/oscpack -- ${OSCPACK_INCLUDE_DIR}.")
|
||||
message(STATUS "Compiling 'OSCPack' from http://www.rossbencina.com/code/oscpack -- ${OSCPACK_INCLUDE_DIR}")
|
||||
|
||||
#
|
||||
# FILE DIALOG: use tinyfiledialog for all except Linux
|
||||
#
|
||||
if(UNIX)
|
||||
if (APPLE)
|
||||
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
|
||||
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
|
||||
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
|
||||
set(TINYFD_LIBRARY TINYFD)
|
||||
endif()
|
||||
else()
|
||||
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
|
||||
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
|
||||
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
|
||||
set(TINYFD_LIBRARY TINYFD)
|
||||
endif()
|
||||
|
||||
#
|
||||
# ImGui Color Text Editor
|
||||
#
|
||||
set(IMGUITEXTEDIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit)
|
||||
set(IMGUITEXTEDIT_SRC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ext/ImGuiColorTextEdit/TextEditor.cpp
|
||||
)
|
||||
message(STATUS "Including 'ImGuiColorTextEdit' from https://github.com/BalazsJako/ImGuiColorTextEdit -- ${IMGUITEXTEDIT_INCLUDE_DIR}")
|
||||
|
||||
#
|
||||
# STB
|
||||
#
|
||||
set(STB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/stb)
|
||||
add_definitions(-DIMGUI_USE_STB_SPRINTF)
|
||||
message(STATUS "Including 'STB Nothings' from https://github.com/nothings/stb -- ${STB_INCLUDE_DIR}.")
|
||||
message(STATUS "Including 'STB Nothings' from https://github.com/nothings/stb -- ${STB_INCLUDE_DIR}")
|
||||
|
||||
#
|
||||
# DIRENT
|
||||
#
|
||||
# DIRENT (windows only)
|
||||
if(WIN32)
|
||||
set(DIRENT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/Dirent/include)
|
||||
message(STATUS "Including 'Dirent' from https://github.com/tronkko/dirent -- ${DIRENT_INCLUDE_DIR}.")
|
||||
endif( WIN32 )
|
||||
|
||||
#
|
||||
# TINY FILE DIALOG
|
||||
#
|
||||
set(TINYFD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd)
|
||||
add_library(TINYFD "${CMAKE_CURRENT_SOURCE_DIR}/ext/tfd/tinyfiledialogs.c")
|
||||
message(STATUS "Compiling 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git -- ${TINYFD_INCLUDE_DIR}.")
|
||||
|
||||
#
|
||||
# OBJ LOADER
|
||||
#
|
||||
#set(OBJLOADER_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/obj)
|
||||
#add_library(OBJLOADER "${CMAKE_CURRENT_SOURCE_DIR}/ext/obj/ObjLoader.cpp")
|
||||
#message(STATUS "Compiling 'ObjLoader' from https://github.com/mortennobel/OpenGL_3_2_Utils -- ${OBJLOADER_INCLUDE_DIR}.")
|
||||
|
||||
# find_package(PkgConfig REQUIRED)
|
||||
# pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
||||
endif()
|
||||
|
||||
#
|
||||
# Application
|
||||
@@ -216,9 +248,11 @@ include_directories(
|
||||
${GSTREAMER_APP_INCLUDE_DIR}
|
||||
${GSTREAMER_PBUTILS_INCLUDE_DIR}
|
||||
${GSTREAMER_GL_INCLUDE_DIR}
|
||||
${GLFW3_INCLUDE_DIRS}
|
||||
${ICU_INCLUDE_DIRS}
|
||||
${GLM_INCLUDE_DIRS}
|
||||
${GLIB2_INCLUDE_DIR}
|
||||
${GLAD_INCLUDE_DIR}
|
||||
${GLM_INCLUDE_DIRS}
|
||||
${IMGUI_INCLUDE_DIR}
|
||||
${IMGUI_INCLUDE_DIR}/examples
|
||||
${IMGUITEXTEDIT_INCLUDE_DIR}
|
||||
@@ -227,6 +261,12 @@ include_directories(
|
||||
${STB_INCLUDE_DIR}
|
||||
${DIRENT_INCLUDE_DIR}
|
||||
${OSCPACK_INCLUDE_DIR}
|
||||
${link_HEADERS}
|
||||
)
|
||||
|
||||
link_directories(
|
||||
${GLFW3_LIBRARY_DIRS}
|
||||
${ICU_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
|
||||
@@ -234,6 +274,7 @@ set(VMIX_BINARY "vimix")
|
||||
set(VMIX_SRCS
|
||||
main.cpp
|
||||
Log.cpp
|
||||
BaseToolkit.cpp
|
||||
Shader.cpp
|
||||
ImageShader.cpp
|
||||
ImageProcessingShader.cpp
|
||||
@@ -243,13 +284,23 @@ set(VMIX_SRCS
|
||||
Mesh.cpp
|
||||
Decorations.cpp
|
||||
View.cpp
|
||||
RenderView.cpp
|
||||
GeometryView.cpp
|
||||
MixingView.cpp
|
||||
MixingGroup.cpp
|
||||
LayerView.cpp
|
||||
TextureView.cpp
|
||||
TransitionView.cpp
|
||||
Source.cpp
|
||||
SourceCallback.cpp
|
||||
SourceList.cpp
|
||||
Session.cpp
|
||||
Selection.cpp
|
||||
SessionSource.cpp
|
||||
SessionVisitor.cpp
|
||||
GarbageVisitor.cpp
|
||||
Interpolator.cpp
|
||||
SessionCreator.cpp
|
||||
SessionParser.cpp
|
||||
Mixer.cpp
|
||||
FrameGrabber.cpp
|
||||
Recorder.cpp
|
||||
@@ -258,7 +309,6 @@ set(VMIX_SRCS
|
||||
Settings.cpp
|
||||
Screenshot.cpp
|
||||
Resource.cpp
|
||||
FileDialog.cpp
|
||||
Timeline.cpp
|
||||
Stream.cpp
|
||||
MediaPlayer.cpp
|
||||
@@ -267,6 +317,7 @@ set(VMIX_SRCS
|
||||
PatternSource.cpp
|
||||
DeviceSource.cpp
|
||||
NetworkSource.cpp
|
||||
MultiFileSource.cpp
|
||||
FrameBuffer.cpp
|
||||
RenderingManager.cpp
|
||||
UserInterfaceManager.cpp
|
||||
@@ -276,13 +327,18 @@ set(VMIX_SRCS
|
||||
SearchVisitor.cpp
|
||||
ImGuiToolkit.cpp
|
||||
ImGuiVisitor.cpp
|
||||
InfoVisitor.cpp
|
||||
GstToolkit.cpp
|
||||
GlmToolkit.cpp
|
||||
SystemToolkit.cpp
|
||||
DialogToolkit.cpp
|
||||
tinyxml2Toolkit.cpp
|
||||
NetworkToolkit.cpp
|
||||
Connection.cpp
|
||||
ActionManager.cpp
|
||||
Overlay.cpp
|
||||
Metronome.cpp
|
||||
ControlManager.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -290,8 +346,15 @@ set(VMIX_RSC_FILES
|
||||
./rsc/shaders/simple.fs
|
||||
./rsc/shaders/simple.vs
|
||||
./rsc/shaders/image.fs
|
||||
./rsc/shaders/mask_elipse.fs
|
||||
./rsc/shaders/mask_box.fs
|
||||
./rsc/shaders/mask_round.fs
|
||||
./rsc/shaders/mask_horizontal.fs
|
||||
./rsc/shaders/mask_vertical.fs
|
||||
./rsc/shaders/mask_draw.fs
|
||||
./rsc/shaders/image.vs
|
||||
./rsc/shaders/imageprocessing.fs
|
||||
./rsc/shaders/imageblending.fs
|
||||
./rsc/fonts/Hack-Regular.ttf
|
||||
./rsc/fonts/Roboto-Regular.ttf
|
||||
./rsc/fonts/Roboto-Bold.ttf
|
||||
@@ -321,11 +384,17 @@ set(VMIX_RSC_FILES
|
||||
./rsc/images/soft_shadow.dds
|
||||
./rsc/mesh/disk.ply
|
||||
./rsc/mesh/circle.ply
|
||||
./rsc/mesh/corner.ply
|
||||
./rsc/mesh/shadow.ply
|
||||
./rsc/mesh/glow.ply
|
||||
./rsc/mesh/border_round_left.ply
|
||||
./rsc/mesh/border_round.ply
|
||||
./rsc/mesh/border_top.ply
|
||||
./rsc/mesh/border_perspective_round_left.ply
|
||||
./rsc/mesh/border_perspective_round.ply
|
||||
./rsc/mesh/border_perspective_top.ply
|
||||
./rsc/mesh/border_sharp.ply
|
||||
./rsc/mesh/border_large_round_left.ply
|
||||
./rsc/mesh/border_large_round.ply
|
||||
./rsc/mesh/border_large_top.ply
|
||||
./rsc/mesh/border_handles_rotation.ply
|
||||
@@ -334,13 +403,19 @@ set(VMIX_RSC_FILES
|
||||
./rsc/mesh/border_handles_overlay_filled.ply
|
||||
./rsc/mesh/border_handles_sharp.ply
|
||||
./rsc/mesh/border_handles_menu.ply
|
||||
./rsc/mesh/border_handles_crop.ply
|
||||
./rsc/mesh/border_handles_lock.ply
|
||||
./rsc/mesh/border_handles_lock_open.ply
|
||||
./rsc/mesh/border_handles_shadow.ply
|
||||
./rsc/mesh/border_large_sharp.ply
|
||||
./rsc/mesh/border_vertical_overlay.ply
|
||||
./rsc/mesh/perspective_layer.ply
|
||||
./rsc/mesh/perspective_axis_left.ply
|
||||
./rsc/mesh/perspective_axis_right.ply
|
||||
./rsc/mesh/shadow_perspective.ply
|
||||
./rsc/mesh/point.ply
|
||||
./rsc/mesh/square_point.ply
|
||||
./rsc/mesh/triangle_point.ply
|
||||
./rsc/mesh/icon_video.ply
|
||||
./rsc/mesh/icon_image.ply
|
||||
./rsc/mesh/icon_render.ply
|
||||
@@ -349,6 +424,7 @@ set(VMIX_RSC_FILES
|
||||
./rsc/mesh/icon_share.ply
|
||||
./rsc/mesh/icon_clone.ply
|
||||
./rsc/mesh/icon_vimix.ply
|
||||
./rsc/mesh/icon_group_vimix.ply
|
||||
./rsc/mesh/icon_circles.ply
|
||||
./rsc/mesh/icon_dots.ply
|
||||
./rsc/mesh/icon_empty.ply
|
||||
@@ -361,10 +437,21 @@ set(VMIX_RSC_FILES
|
||||
./rsc/mesh/icon_clock_hand.ply
|
||||
./rsc/mesh/icon_grid.ply
|
||||
./rsc/mesh/icon_rightarrow.ply
|
||||
./rsc/mesh/icon_crop.ply
|
||||
./rsc/mesh/icon_eye.ply
|
||||
./rsc/mesh/icon_eye_slash.ply
|
||||
./rsc/mesh/icon_vector_square_slash.ply
|
||||
./rsc/mesh/icon_cube.ply
|
||||
./rsc/mesh/icon_sequence.ply
|
||||
./rsc/mesh/h_line.ply
|
||||
./rsc/mesh/h_mark.ply
|
||||
)
|
||||
|
||||
# Include the CMake RC module
|
||||
include(CMakeRC)
|
||||
cmrc_add_resource_library(vmix-resources ALIAS vmix::rc NAMESPACE vmix WHENCE rsc ${VMIX_RSC_FILES})
|
||||
message(STATUS "Using 'CMakeRC ' from https://github.com/vector-of-bool/cmrc.git -- ${CMAKE_MODULE_PATH}.")
|
||||
|
||||
### DEFINE THE TARGET (OS specific)
|
||||
|
||||
IF(APPLE)
|
||||
@@ -378,7 +465,6 @@ IF(APPLE)
|
||||
# create the application
|
||||
add_executable(${VMIX_BINARY} MACOSX_BUNDLE
|
||||
${VMIX_SRCS}
|
||||
./osx/CustomDelegate.m
|
||||
${IMGUITEXTEDIT_SRC}
|
||||
${MACOSX_BUNDLE_ICON_FILE}
|
||||
)
|
||||
@@ -394,17 +480,19 @@ IF(APPLE)
|
||||
|
||||
ELSE(APPLE)
|
||||
|
||||
link_directories (${GTK3_LIBRARY_DIRS})
|
||||
|
||||
add_executable(${VMIX_BINARY}
|
||||
${VMIX_SRCS}
|
||||
${IMGUITEXTEDIT_SRC}
|
||||
)
|
||||
|
||||
set(PLATFORM_LIBS ""
|
||||
set(PLATFORM_LIBS
|
||||
GTK::GTK
|
||||
)
|
||||
|
||||
ENDIF(APPLE)
|
||||
|
||||
|
||||
### COMPILE THE TARGET (all OS)
|
||||
|
||||
set_property(TARGET ${VMIX_BINARY} PROPERTY CXX_STANDARD 17)
|
||||
@@ -412,14 +500,16 @@ set_property(TARGET ${VMIX_BINARY} PROPERTY C_STANDARD 11)
|
||||
|
||||
target_compile_definitions(${VMIX_BINARY} PUBLIC "IMGUI_IMPL_OPENGL_LOADER_GLAD")
|
||||
|
||||
cmrc_add_resource_library(vmix-resources ALIAS vmix::rc NAMESPACE vmix WHENCE rsc ${VMIX_RSC_FILES})
|
||||
message(STATUS "Using 'CMakeRC ' from https://github.com/vector-of-bool/cmrc.git -- ${CMAKE_MODULE_PATH}.")
|
||||
|
||||
|
||||
target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
|
||||
${GLFW_LIBRARY}
|
||||
GLAD
|
||||
vmix::rc
|
||||
glm::glm
|
||||
GLAD
|
||||
TINYXML2
|
||||
IMGUI
|
||||
OSCPACK
|
||||
${TINYFD_LIBRARY}
|
||||
${GLFW3_LIBRARIES}
|
||||
${ICU_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
${GOBJECT_LIBRARIES}
|
||||
${GSTREAMER_LIBRARY}
|
||||
@@ -429,30 +519,24 @@ target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
|
||||
${GSTREAMER_VIDEO_LIBRARY}
|
||||
${GSTREAMER_PBUTILS_LIBRARY}
|
||||
${GSTREAMER_GL_LIBRARY}
|
||||
${GSTREAMER_PLAYER_LIBRARY}
|
||||
${NFD_LIBRARY}
|
||||
${PNG_LIBRARY}
|
||||
${THREAD_LIBRARY}
|
||||
TINYXML2
|
||||
TINYFD
|
||||
IMGUI
|
||||
OSCPACK
|
||||
vmix::rc
|
||||
Threads::Threads
|
||||
PNG::PNG
|
||||
Ableton::Link
|
||||
${PLATFORM_LIBS}
|
||||
)
|
||||
|
||||
macro_display_feature_log()
|
||||
|
||||
|
||||
### DEFINE THE PACKAGING (all OS)
|
||||
|
||||
SET(CPACK_PACKAGE_NAME "vimix")
|
||||
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "vimix\n Real-time video mixing for live performance.")
|
||||
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "vimix\nReal-time video mixing for live performance.")
|
||||
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md")
|
||||
SET(CPACK_PACKAGE_CONTACT "bruno.herbelin@gmail.com")
|
||||
SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING.txt")
|
||||
SET(CPACK_PACKAGE_VERSION_MAJOR "0")
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "4")
|
||||
SET(CPACK_PACKAGE_CONTACT "bruno.herbelin@gmail.com")
|
||||
SET(CPACK_PACKAGE_HOMEPAGE_URL "https://brunoherbelin.github.io/vimix")
|
||||
SET(CPACK_PACKAGE_VERSION_MAJOR "${VIMIX_VERSION_MAJOR}")
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "${VIMIX_VERSION_MINOR}")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "${VIMIX_VERSION_PATCH}")
|
||||
SET(CPACK_PACKAGE_VENDOR "Bruno Herbelin")
|
||||
SET(CPACK_SOURCE_IGNORE_FILES
|
||||
"/\\\\.git/"
|
||||
@@ -461,23 +545,19 @@ SET(CPACK_SOURCE_IGNORE_FILES
|
||||
)
|
||||
|
||||
|
||||
# optimize size ?
|
||||
SET(CPACK_STRIP_FILES TRUE)
|
||||
|
||||
### DEFINE THE PACKAGING (OS specific)
|
||||
|
||||
IF(APPLE)
|
||||
|
||||
# include( InstallRequiredSystemLibraries )
|
||||
|
||||
# Bundle target
|
||||
set(CPACK_GENERATOR "DragNDrop")
|
||||
set(CPACK_GENERATOR DragNDrop)
|
||||
set(CPACK_BINARY_DRAGNDROP ON)
|
||||
|
||||
# OSX cpack info
|
||||
set(CPACK_SYSTEM_NAME "OSX_${CMAKE_OSX_DEPLOYMENT_TARGET}_${CMAKE_OSX_ARCHITECTURES}")
|
||||
set(CPACK_BUNDLE_NAME ${CPACK_PACKAGE_NAME})
|
||||
set(CPACK_BUNDLE_ICON ${MACOSX_BUNDLE_ICON_FILE})
|
||||
set(CPACK_BUNDLE_PLIST ${MACOSX_BUNDLE_PLIST_FILE})
|
||||
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/vimix.app")
|
||||
# set( APPS "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/vimix${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
|
||||
install(TARGETS ${VMIX_BINARY}
|
||||
CONFIGURATIONS Release RelWithDebInfo
|
||||
@@ -490,34 +570,56 @@ IF(APPLE)
|
||||
|
||||
### TODO configure auto to find installation dir of gst
|
||||
|
||||
message(STATUS "install gst-plugins ${PKG_GSTREAMER_PLUGIN_DIR}")
|
||||
message(STATUS "install gst-plugins-base ${PKG_GSTREAMER_BASE_PLUGIN_DIR}")
|
||||
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKG_GSTREAMER_PLUGINS_BAD gstreamer-plugins-bad-${GSTREAMER_ABI_VERSION})
|
||||
set(PKG_GSTREAMER_BAD_PLUGIN_DIR ${PKG_GSTREAMER_PLUGINS_BAD_LIBDIR}/gstreamer-${GSTREAMER_ABI_VERSION})
|
||||
message(STATUS "install gst-plugins-bad ${PKG_GSTREAMER_BAD_PLUGIN_DIR}")
|
||||
endif()
|
||||
|
||||
|
||||
# intall the gst-plugin-scanner program (used by plugins at load time)
|
||||
install(FILES "/usr/local/Cellar/gstreamer/1.18.1/libexec/gstreamer-1.0/gst-plugin-scanner"
|
||||
set(PKG_GSTREAMER_SCANNER "${PKG_GSTREAMER_PREFIX}/libexec/gstreamer-1.0/gst-plugin-scanner")
|
||||
message(STATUS "install gst-plugin-scanner ${PKG_GSTREAMER_SCANNER}")
|
||||
install(FILES "${PKG_GSTREAMER_SCANNER}"
|
||||
DESTINATION "${plugin_dest_dir}"
|
||||
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
# ICU DATA LIB GST dependency : undocumented and hacked here : seems to work
|
||||
install(FILES "/usr/local/Cellar/icu4c/67.1/lib/libicudata.67.1.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" RENAME "libicudata.67.dylib" COMPONENT Runtime)
|
||||
|
||||
# Install the gst-plugins (all those installed with brew )
|
||||
install(DIRECTORY "${PKG_GSTREAMER_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-base/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-bad/1.18.1_1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.18.1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PKG_GSTREAMER_BASE_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PKG_GSTREAMER_BAD_PLUGIN_DIR}" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
|
||||
# install locally recompiled gst-plugins (because not included in brew package)
|
||||
set(LOCAL_BUILD_BAD "/Users/herbelin/Development/gst/gst-plugins-bad-1.18.0/build")
|
||||
install(FILES "${LOCAL_BUILD_BAD}/sys/applemedia/libgstapplemedia.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
|
||||
install(FILES "${LOCAL_BUILD_BAD}/ext/libde265/libgstde265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
|
||||
install(FILES "${LOCAL_BUILD_BAD}/ext/x265/libgstx265.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.18.4/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.18.4_1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.18.4/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
|
||||
# install locally recompiled & installed gst-plugins (because not included in brew package)
|
||||
install(FILES "/usr/local/lib/gstreamer-1.0/libgstapplemedia.dylib"
|
||||
"/usr/local/lib/gstreamer-1.0/libgstde265.dylib"
|
||||
"/usr/local/lib/gstreamer-1.0/libgstx265.dylib"
|
||||
DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
|
||||
|
||||
# install frei0r plugins (dependencies of gstreamer-1.0/libgstfrei0r.dylib plugin)
|
||||
install(FILES "/usr/local/Cellar/frei0r/1.7.0/lib/frei0r-1/lissajous0r.so"
|
||||
"/usr/local/Cellar/frei0r/1.7.0/lib/frei0r-1/rgbnoise.so"
|
||||
DESTINATION "${plugin_dest_dir}/frei0r-1" COMPONENT Runtime)
|
||||
|
||||
|
||||
# ICU DATA LIB GST dependency : undocumented and hacked here : seems to work
|
||||
# install(FILES "${ICU_LINK_LIBRARIES}" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" COMPONENT Runtime)
|
||||
install(FILES "/usr/local/Cellar/icu4c/69.1/lib/libicudata.69.1.dylib" DESTINATION "${plugin_dest_dir}/gstreamer-1.0" RENAME "libicudata.69.dylib" COMPONENT Runtime)
|
||||
message(STATUS "install ${ICU_LINK_LIBRARIES} from ${ICU_LIBRARY_DIRS}")
|
||||
|
||||
# package runtime fixup bundle
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/vimix.app")
|
||||
install(CODE "
|
||||
file(GLOB_RECURSE GSTPLUGINS \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/gstreamer-1.0/*.dylib\")
|
||||
list(APPEND LIBS_PATH \"/usr/local/Cellar/icu4c/67.1/lib\")
|
||||
list(APPEND LIBS_PATH \"\${ICU_LIBRARY_DIRS}\")
|
||||
include(BundleUtilities)
|
||||
set(BU_CHMOD_BUNDLE_ITEMS TRUE)
|
||||
fixup_bundle(\"${APPS}\" \"\${GSTPLUGINS}\" \"${LIBS_PATH}\")
|
||||
@@ -525,22 +627,43 @@ IF(APPLE)
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
set(CPACK_BINARY_DRAGNDROP ON)
|
||||
set(APPLE_CODESIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/osx/entitlements.plist")
|
||||
set(APPLE_CODESIGN_IDENTITY "" CACHE STRING "")
|
||||
string(LENGTH "${APPLE_CODESIGN_IDENTITY}" APPLE_CODESIGN_IDENTITY_LENGHT)
|
||||
if( ${APPLE_CODESIGN_IDENTITY_LENGHT} LESS 40 )
|
||||
message(STATUS "Not signing bundle. Specify APPLE_CODESIGN_IDENTITY to cmake before running cpack to sign")
|
||||
else()
|
||||
install(CODE "
|
||||
execute_process(COMMAND
|
||||
codesign --verbose=4 --deep --force
|
||||
--entitlements \"${APPLE_CODESIGN_ENTITLEMENTS}\"
|
||||
--sign \"${APPLE_CODESIGN_IDENTITY}\"
|
||||
\"${APPS}\" )
|
||||
"
|
||||
COMPONENT Runtime
|
||||
)
|
||||
endif()
|
||||
|
||||
# # package runtime fixup bundle and codesign
|
||||
# set(BUNDLE_NAME "vimix.app")
|
||||
# set(BUNDLE_LIBS_DIR "${plugin_dest_dir}/gstreamer-1.0")
|
||||
# set(BUNDLE_DIRS "${ICU_LIBRARY_DIRS}")
|
||||
# set(APPLE_CODESIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/osx/entitlements.plist")
|
||||
|
||||
# configure_file(cmake/modules/BundleInstall.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake" @ONLY)
|
||||
# install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake" COMPONENT Runtime)
|
||||
|
||||
ELSE(APPLE)
|
||||
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/vimix")
|
||||
|
||||
install(TARGETS ${VMIX_BINARY}
|
||||
CONFIGURATIONS Release RelWithDebInfo
|
||||
RUNTIME DESTINATION bin COMPONENT Runtime
|
||||
)
|
||||
|
||||
|
||||
ENDIF(APPLE)
|
||||
|
||||
# Package full name
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}_${CPACK_SYSTEM_NAME}")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CPACK_SYSTEM_NAME}")
|
||||
|
||||
# To Create a package, run "cpack"
|
||||
include(CPack)
|
||||
|
||||
128
CODE_OF_CONDUCT.md
Normal file
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.
|
||||
@@ -1,28 +1,44 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "osc/OscOutboundPacketStream.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "Connection.h"
|
||||
#include "Settings.h"
|
||||
#include "Streamer.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "Connection.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define CONNECTION_DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
Connection::Connection()
|
||||
Connection::Connection() : receiver_(nullptr)
|
||||
{
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
if (receiver_!=nullptr) {
|
||||
@@ -114,10 +130,10 @@ ConnectionInfo Connection::info(int index)
|
||||
|
||||
struct hasName: public std::unary_function<ConnectionInfo, bool>
|
||||
{
|
||||
inline bool operator()(const ConnectionInfo elem) const {
|
||||
inline bool operator()(const ConnectionInfo &elem) const {
|
||||
return (elem.name.compare(_a) == 0);
|
||||
}
|
||||
hasName(std::string a) : _a(a) { }
|
||||
explicit hasName(const std::string &a) : _a(a) { }
|
||||
private:
|
||||
std::string _a;
|
||||
};
|
||||
@@ -147,7 +163,7 @@ int Connection::index(ConnectionInfo i) const
|
||||
|
||||
void Connection::print()
|
||||
{
|
||||
for(int i = 0; i<connections_.size(); i++) {
|
||||
for(size_t i = 0; i<connections_.size(); i++) {
|
||||
Log::Info(" - %s %s:%d", connections_[i].name.c_str(), connections_[i].address.c_str(), connections_[i].port_handshake);
|
||||
}
|
||||
}
|
||||
@@ -157,7 +173,8 @@ void Connection::listen()
|
||||
#ifdef CONNECTION_DEBUG
|
||||
Log::Info("Accepting handshake on port %d", Connection::manager().connections_[0].port_handshake);
|
||||
#endif
|
||||
Connection::manager().receiver_->Run();
|
||||
if (Connection::manager().receiver_)
|
||||
Connection::manager().receiver_->Run();
|
||||
}
|
||||
|
||||
void Connection::ask()
|
||||
@@ -185,7 +202,7 @@ void Connection::ask()
|
||||
|
||||
// check the list of connections for non responding (disconnected)
|
||||
std::vector< ConnectionInfo >::iterator it = Connection::manager().connections_.begin();
|
||||
for(it++; it!=Connection::manager().connections_.end(); ) {
|
||||
for(++it; it!=Connection::manager().connections_.end(); ) {
|
||||
// decrease life score
|
||||
(*it).alive--;
|
||||
// erase connection if its life score is negative (not responding too many times)
|
||||
@@ -201,13 +218,13 @@ void Connection::ask()
|
||||
}
|
||||
// loop
|
||||
else
|
||||
it++;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ConnectionRequestListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
void Connection::RequestListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint )
|
||||
{
|
||||
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
|
||||
|
||||
31
Connection.h
31
Connection.h
@@ -1,22 +1,17 @@
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "osc/OscReceivedElements.h"
|
||||
#include "osc/OscPacketListener.h"
|
||||
#include "ip/UdpSocket.h"
|
||||
|
||||
#include "NetworkToolkit.h"
|
||||
|
||||
#define MAX_HANDSHAKE 20
|
||||
#define HANDSHAKE_PORT 71310
|
||||
#define STREAM_REQUEST_PORT 71510
|
||||
#define OSC_DIALOG_PORT 71010
|
||||
#define ALIVE 3
|
||||
|
||||
class ConnectionRequestListener : public osc::OscPacketListener {
|
||||
|
||||
protected:
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
|
||||
@@ -58,12 +53,10 @@ struct ConnectionInfo {
|
||||
|
||||
class Connection
|
||||
{
|
||||
friend class ConnectionRequestListener;
|
||||
|
||||
// Private Constructor
|
||||
Connection();
|
||||
Connection(Connection const& copy); // Not Implemented
|
||||
Connection& operator=(Connection const& copy); // Not Implemented
|
||||
Connection(Connection const& copy) = delete;
|
||||
Connection& operator=(Connection const& copy) = delete;
|
||||
|
||||
public:
|
||||
static Connection& manager()
|
||||
@@ -82,11 +75,19 @@ public:
|
||||
int index(const std::string &name) const;
|
||||
ConnectionInfo info(int index = 0); // index 0 for self
|
||||
|
||||
protected:
|
||||
class RequestListener : public osc::OscPacketListener {
|
||||
|
||||
protected:
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
static void ask();
|
||||
static void listen();
|
||||
ConnectionRequestListener listener_;
|
||||
RequestListener listener_;
|
||||
UdpListeningReceiveSocket *receiver_;
|
||||
|
||||
std::vector< ConnectionInfo > connections_;
|
||||
|
||||
756
ControlManager.cpp
Normal file
756
ControlManager.cpp
Normal file
@@ -0,0 +1,756 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "osc/OscOutboundPacketStream.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "Mixer.h"
|
||||
#include "Source.h"
|
||||
#include "ActionManager.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "tinyxml2Toolkit.h"
|
||||
|
||||
#include "ControlManager.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define CONTROL_DEBUG
|
||||
#endif
|
||||
|
||||
#define CONTROL_OSC_MSG "OSC: "
|
||||
|
||||
|
||||
void Control::RequestListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint )
|
||||
{
|
||||
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
|
||||
remoteEndpoint.AddressAndPortAsString(sender);
|
||||
|
||||
try{
|
||||
#ifdef CONTROL_DEBUG
|
||||
Log::Info(CONTROL_OSC_MSG "received '%s' from %s", FullMessage(m).c_str(), sender);
|
||||
#endif
|
||||
// Preprocessing with Translator
|
||||
std::string address_pattern = Control::manager().translate(m.AddressPattern());
|
||||
|
||||
// structured OSC address
|
||||
std::list<std::string> address = BaseToolkit::splitted(address_pattern, OSC_SEPARATOR);
|
||||
//
|
||||
// A wellformed OSC address is in the form '/vimix/target/attribute {arguments}'
|
||||
// First test: should have 3 elements and start with APP_NAME ('vimix')
|
||||
//
|
||||
if (address.size() > 2 && address.front().compare(OSC_PREFIX) == 0 ){
|
||||
// done with the first part of the OSC address
|
||||
address.pop_front();
|
||||
// next part of the OSC message is the target
|
||||
std::string target = address.front();
|
||||
// next part of the OSC message is the attribute
|
||||
address.pop_front();
|
||||
std::string attribute = address.front();
|
||||
// Log target: just print text in log window
|
||||
if ( target.compare(OSC_INFO) == 0 )
|
||||
{
|
||||
if ( attribute.compare(OSC_INFO_NOTIFY) == 0) {
|
||||
Log::Notify(CONTROL_OSC_MSG "Received '%s' from %s", FullMessage(m).c_str(), sender);
|
||||
}
|
||||
else if ( attribute.compare(OSC_INFO_LOG) == 0) {
|
||||
Log::Info(CONTROL_OSC_MSG "Received '%s' from %s", FullMessage(m).c_str(), sender);
|
||||
}
|
||||
}
|
||||
// Output target: concerns attributes of the rendering output
|
||||
else if ( target.compare(OSC_OUTPUT) == 0 )
|
||||
{
|
||||
if ( Control::manager().receiveOutputAttribute(attribute, m.ArgumentStream())) {
|
||||
// send the global status
|
||||
Control::manager().sendOutputStatus(remoteEndpoint);
|
||||
}
|
||||
}
|
||||
// Session target: concerns attributes of the session
|
||||
else if ( target.compare(OSC_SESSION) == 0 )
|
||||
{
|
||||
if ( Control::manager().receiveSessionAttribute(attribute, m.ArgumentStream()) ) {
|
||||
// send the global status
|
||||
Control::manager().sendOutputStatus(remoteEndpoint);
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
}
|
||||
// ALL sources target: apply attribute to all sources of the session
|
||||
else if ( target.compare(OSC_ALL) == 0 )
|
||||
{
|
||||
// Loop over selected sources
|
||||
for (SourceList::iterator it = Mixer::manager().session()->begin(); it != Mixer::manager().session()->end(); ++it) {
|
||||
// apply attributes
|
||||
if ( Control::manager().receiveSourceAttribute( *it, attribute, m.ArgumentStream()) && Mixer::manager().currentSource() == *it)
|
||||
// and send back feedback if needed
|
||||
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
|
||||
}
|
||||
}
|
||||
// Selected sources target: apply attribute to all sources of the selection
|
||||
else if ( target.compare(OSC_SELECTED) == 0 )
|
||||
{
|
||||
// Loop over selected sources
|
||||
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
||||
// apply attributes
|
||||
if ( Control::manager().receiveSourceAttribute( *it, attribute, m.ArgumentStream()) && Mixer::manager().currentSource() == *it)
|
||||
// and send back feedback if needed
|
||||
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
|
||||
}
|
||||
}
|
||||
// Current source target: apply attribute to the current sources
|
||||
else if ( target.compare(OSC_CURRENT) == 0 )
|
||||
{
|
||||
int sourceid = -1;
|
||||
if ( attribute.compare(OSC_SYNC) == 0) {
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
else if ( attribute.compare(OSC_NEXT) == 0) {
|
||||
// set current to NEXT
|
||||
Mixer::manager().setCurrentNext();
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
else if ( attribute.compare(OSC_PREVIOUS) == 0) {
|
||||
// set current to PREVIOUS
|
||||
Mixer::manager().setCurrentPrevious();
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
else if ( BaseToolkit::is_a_number( attribute.substr(1), &sourceid) ){
|
||||
// set current to given INDEX
|
||||
Mixer::manager().setCurrentIndex(sourceid);
|
||||
// send the status of all sources
|
||||
Control::manager().sendSourcesStatus(remoteEndpoint, m.ArgumentStream());
|
||||
}
|
||||
// all other attributes operate on current source
|
||||
else {
|
||||
// apply attributes to current source
|
||||
if ( Control::manager().receiveSourceAttribute( Mixer::manager().currentSource(), attribute, m.ArgumentStream()) )
|
||||
// and send back feedback if needed
|
||||
Control::manager().sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
|
||||
}
|
||||
}
|
||||
// General case: try to identify the target
|
||||
else {
|
||||
// try to find source by index
|
||||
Source *s = nullptr;
|
||||
int sourceid = -1;
|
||||
if ( BaseToolkit::is_a_number(target.substr(1), &sourceid) )
|
||||
s = Mixer::manager().sourceAtIndex(sourceid);
|
||||
|
||||
// if failed, try to find source by name
|
||||
if (s == nullptr)
|
||||
s = Mixer::manager().findSource(target.substr(1));
|
||||
|
||||
// if a source with the given target name or index was found
|
||||
if (s) {
|
||||
// apply attributes to source
|
||||
if ( Control::manager().receiveSourceAttribute(s, attribute, m.ArgumentStream()) )
|
||||
// and send back feedback if needed
|
||||
Control::manager().sendSourceAttibutes(remoteEndpoint, target, s);
|
||||
}
|
||||
else
|
||||
Log::Info(CONTROL_OSC_MSG "Unknown target '%s' requested by %s.", target.c_str(), sender);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log::Info(CONTROL_OSC_MSG "Unknown osc message '%s' sent by %s.", m.AddressPattern(), sender);
|
||||
}
|
||||
}
|
||||
catch( osc::Exception& e ){
|
||||
// any parsing errors such as unexpected argument types, or
|
||||
// missing arguments get thrown as exceptions.
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring error in message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string Control::RequestListener::FullMessage( const osc::ReceivedMessage& m )
|
||||
{
|
||||
// build a string with the address pattern of the message
|
||||
std::ostringstream message;
|
||||
message << m.AddressPattern() << " ";
|
||||
|
||||
// try to fill the string with the arguments
|
||||
std::ostringstream arguments;
|
||||
try{
|
||||
// loop over all arguments
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
while (arg != m.ArgumentsEnd()) {
|
||||
if( arg->IsBool() ){
|
||||
bool a = (arg++)->AsBoolUnchecked();
|
||||
message << (a ? "T" : "F");
|
||||
}
|
||||
else if( arg->IsInt32() ){
|
||||
int a = (arg++)->AsInt32Unchecked();
|
||||
message << "i";
|
||||
arguments << " " << a;
|
||||
}
|
||||
else if( arg->IsFloat() ){
|
||||
float a = (arg++)->AsFloatUnchecked();
|
||||
message << "f";
|
||||
arguments << " " << std::fixed << std::setprecision(2) << a;
|
||||
}
|
||||
else if( arg->IsString() ){
|
||||
const char *a = (arg++)->AsStringUnchecked();
|
||||
message << "s";
|
||||
arguments << " " << a;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( osc::Exception& e ){
|
||||
// any parsing errors such as unexpected argument types, or
|
||||
// missing arguments get thrown as exceptions.
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring error in message '%s': %s", m.AddressPattern(), e.what());
|
||||
}
|
||||
|
||||
// append list of arguments to the message string
|
||||
message << arguments.str();
|
||||
|
||||
// returns the full message
|
||||
return message.str();
|
||||
}
|
||||
|
||||
|
||||
Control::Control() : receiver_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Control::~Control()
|
||||
{
|
||||
terminate();
|
||||
}
|
||||
|
||||
|
||||
std::string Control::translate (std::string addresspattern)
|
||||
{
|
||||
auto it_translation = translation_.find(addresspattern);
|
||||
if ( it_translation != translation_.end() )
|
||||
return it_translation->second;
|
||||
else
|
||||
return addresspattern;
|
||||
}
|
||||
|
||||
void Control::loadOscConfig()
|
||||
{
|
||||
// reset translations
|
||||
translation_.clear();
|
||||
|
||||
// load osc config file
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLError eResult = xmlDoc.LoadFile(Settings::application.control.osc_filename.c_str());
|
||||
|
||||
// the only reason to return false is if the file does not exist or is empty
|
||||
if (eResult == tinyxml2::XML_ERROR_FILE_NOT_FOUND
|
||||
| eResult == tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED
|
||||
| eResult == tinyxml2::XML_ERROR_FILE_READ_ERROR
|
||||
| eResult == tinyxml2::XML_ERROR_EMPTY_DOCUMENT )
|
||||
resetOscConfig();
|
||||
|
||||
// found the file, could open and read it
|
||||
else if (eResult != tinyxml2::XML_SUCCESS)
|
||||
Log::Warning(CONTROL_OSC_MSG "Error while parsing Translator: %s", xmlDoc.ErrorIDToName(eResult));
|
||||
|
||||
// no XML parsing error
|
||||
else {
|
||||
// parse all entries 'osc'
|
||||
tinyxml2::XMLElement* osc = xmlDoc.FirstChildElement("osc");
|
||||
for( ; osc ; osc=osc->NextSiblingElement()) {
|
||||
// get the 'from' entry
|
||||
tinyxml2::XMLElement* from = osc->FirstChildElement("from");
|
||||
if (from) {
|
||||
const char *str_from = from->GetText();
|
||||
if (str_from) {
|
||||
// get the 'to' entry
|
||||
tinyxml2::XMLElement* to = osc->FirstChildElement("to");
|
||||
if (to) {
|
||||
const char *str_to = to->GetText();
|
||||
// if could get both; add to translator
|
||||
if (str_to)
|
||||
translation_[str_from] = str_to;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log::Info(CONTROL_OSC_MSG "Loaded %d translation%s.", translation_.size(), translation_.size()>1?"s":"");
|
||||
}
|
||||
|
||||
void Control::resetOscConfig()
|
||||
{
|
||||
// generate a template xml translation dictionnary
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLDeclaration *pDec = xmlDoc.NewDeclaration();
|
||||
xmlDoc.InsertFirstChild(pDec);
|
||||
tinyxml2::XMLComment *pComment = xmlDoc.NewComment("The OSC translator converts OSC address patterns into other ones.\n"
|
||||
"Complete the dictionnary by adding as many <osc> translations as you want.\n"
|
||||
"Each <osc> should contain a <from> pattern to translate into a <to> pattern.\n"
|
||||
"More at https://github.com/brunoherbelin/vimix/wiki/Open-Sound-Control-API.");
|
||||
xmlDoc.InsertEndChild(pComment);
|
||||
tinyxml2::XMLElement *from = xmlDoc.NewElement( "from" );
|
||||
from->InsertFirstChild( xmlDoc.NewText("/example/osc/message") );
|
||||
tinyxml2::XMLElement *to = xmlDoc.NewElement( "to" );
|
||||
to->InsertFirstChild( xmlDoc.NewText("/vimix/info/log") );
|
||||
tinyxml2::XMLElement *osc = xmlDoc.NewElement("osc");
|
||||
osc->InsertEndChild(from);
|
||||
osc->InsertEndChild(to);
|
||||
xmlDoc.InsertEndChild(osc);
|
||||
|
||||
// save xml in osc config file
|
||||
xmlDoc.SaveFile(Settings::application.control.osc_filename.c_str());
|
||||
|
||||
// reset and fill translation with default example
|
||||
translation_.clear();
|
||||
translation_["/example/osc/message"] = "/vimix/info/log";
|
||||
}
|
||||
|
||||
bool Control::init()
|
||||
{
|
||||
//
|
||||
// terminate before init (allows calling init() multiple times)
|
||||
//
|
||||
terminate();
|
||||
|
||||
//
|
||||
// load OSC Translator
|
||||
//
|
||||
loadOscConfig();
|
||||
|
||||
//
|
||||
// launch OSC listener
|
||||
//
|
||||
try {
|
||||
// try to create listenning socket
|
||||
// through exception runtime if fails
|
||||
receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS,
|
||||
Settings::application.control.osc_port_receive ), &listener_ );
|
||||
// listen for answers in a separate thread
|
||||
std::thread(listen).detach();
|
||||
|
||||
// inform user
|
||||
IpEndpointName ip = receiver_->LocalEndpointFor( IpEndpointName( NetworkToolkit::hostname().c_str(),
|
||||
Settings::application.control.osc_port_receive ));
|
||||
static char *addresseip = (char *)malloc(IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH);
|
||||
ip.AddressAndPortAsString(addresseip);
|
||||
Log::Info(CONTROL_OSC_MSG "Listening to UDP messages sent to %s", addresseip);
|
||||
}
|
||||
catch (const std::runtime_error &e) {
|
||||
// arg, the receiver could not be initialized
|
||||
// (often because the port was not available)
|
||||
receiver_ = nullptr;
|
||||
Log::Warning(CONTROL_OSC_MSG "Failed to init listener on port %d; %s", Settings::application.control.osc_port_receive, e.what());
|
||||
}
|
||||
|
||||
return receiver_ != nullptr;
|
||||
}
|
||||
|
||||
void Control::listen()
|
||||
{
|
||||
if (Control::manager().receiver_)
|
||||
Control::manager().receiver_->Run();
|
||||
|
||||
Control::manager().receiver_end_.notify_all();
|
||||
}
|
||||
|
||||
void Control::terminate()
|
||||
{
|
||||
if ( receiver_ != nullptr ) {
|
||||
|
||||
// request termination of receiver
|
||||
receiver_->AsynchronousBreak();
|
||||
|
||||
// wait for the receiver_end_ notification
|
||||
std::mutex mtx;
|
||||
std::unique_lock<std::mutex> lck(mtx);
|
||||
// if waited more than 2 seconds, its dead :(
|
||||
if ( receiver_end_.wait_for(lck,std::chrono::seconds(2)) == std::cv_status::timeout)
|
||||
Log::Warning(CONTROL_OSC_MSG "Failed to terminate; try again.");
|
||||
|
||||
// delete receiver and ready to initialize
|
||||
delete receiver_;
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Control::receiveOutputAttribute(const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
bool need_feedback = false;
|
||||
|
||||
try {
|
||||
if ( attribute.compare(OSC_SYNC) == 0) {
|
||||
need_feedback = true;
|
||||
}
|
||||
/// e.g. '/vimix/output/enable' or '/vimix/output/enable 1.0' or '/vimix/output/enable 0.0'
|
||||
else if ( attribute.compare(OSC_OUTPUT_ENABLE) == 0) {
|
||||
float on = 1.f;
|
||||
if ( !arguments.Eos()) {
|
||||
arguments >> on >> osc::EndMessage;
|
||||
}
|
||||
Settings::application.render.disabled = on < 0.5f;
|
||||
}
|
||||
/// e.g. '/vimix/output/disable' or '/vimix/output/disable 1.0' or '/vimix/output/disable 0.0'
|
||||
else if ( attribute.compare(OSC_OUTPUT_DISABLE) == 0) {
|
||||
float on = 1.f;
|
||||
if ( !arguments.Eos()) {
|
||||
arguments >> on >> osc::EndMessage;
|
||||
}
|
||||
Settings::application.render.disabled = on > 0.5f;
|
||||
}
|
||||
/// e.g. '/vimix/output/fading f 0.2' or '/vimix/output/fading ff 1.0 300.f'
|
||||
else if ( attribute.compare(OSC_OUTPUT_FADING) == 0) {
|
||||
float f = 0.f, d = 0.f;
|
||||
// first argument is fading value
|
||||
arguments >> f;
|
||||
if (arguments.Eos())
|
||||
arguments >> osc::EndMessage;
|
||||
// if a second argument is given, it is a duration
|
||||
else
|
||||
arguments >> d >> osc::EndMessage;
|
||||
Mixer::manager().session()->setFadingTarget(f, d);
|
||||
}
|
||||
/// e.g. '/vimix/output/fadein' or '/vimix/output/fadein f 300.f'
|
||||
else if ( attribute.compare(OSC_OUTPUT_FADE_IN) == 0) {
|
||||
float f = 0.f;
|
||||
// if argument is given, it is a duration
|
||||
if (!arguments.Eos())
|
||||
arguments >> f >> osc::EndMessage;
|
||||
Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() - f * 0.01);
|
||||
need_feedback = true;
|
||||
}
|
||||
else if ( attribute.compare(OSC_OUTPUT_FADE_OUT) == 0) {
|
||||
float f = 0.f;
|
||||
// if argument is given, it is a duration
|
||||
if (!arguments.Eos())
|
||||
arguments >> f >> osc::EndMessage;
|
||||
Mixer::manager().session()->setFadingTarget( Mixer::manager().session()->fading() + f * 0.01);
|
||||
need_feedback = true;
|
||||
}
|
||||
#ifdef CONTROL_DEBUG
|
||||
else {
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target 'output'", attribute.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
catch (osc::MissingArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target 'output'", attribute.c_str());
|
||||
}
|
||||
catch (osc::ExcessArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target 'output'", attribute.c_str());
|
||||
}
|
||||
catch (osc::WrongArgumentTypeException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target 'output'", attribute.c_str());
|
||||
}
|
||||
|
||||
return need_feedback;
|
||||
}
|
||||
|
||||
bool Control::receiveSourceAttribute(Source *target, const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
bool send_feedback = false;
|
||||
|
||||
if (target == nullptr)
|
||||
return send_feedback;
|
||||
|
||||
try {
|
||||
/// e.g. '/vimix/current/play' or '/vimix/current/play T' or '/vimix/current/play F'
|
||||
if ( attribute.compare(OSC_SOURCE_PLAY) == 0) {
|
||||
float on = 1.f;
|
||||
if ( !arguments.Eos()) {
|
||||
arguments >> on >> osc::EndMessage;
|
||||
}
|
||||
target->call( new SetPlay(on > 0.5f) );
|
||||
}
|
||||
/// e.g. '/vimix/current/pause' or '/vimix/current/pause T' or '/vimix/current/pause F'
|
||||
else if ( attribute.compare(OSC_SOURCE_PAUSE) == 0) {
|
||||
float on = 1.f;
|
||||
if ( !arguments.Eos()) {
|
||||
arguments >> on >> osc::EndMessage;
|
||||
}
|
||||
target->call( new SetPlay(on < 0.5f) );
|
||||
}
|
||||
/// e.g. '/vimix/current/replay'
|
||||
else if ( attribute.compare(OSC_SOURCE_REPLAY) == 0) {
|
||||
target->call( new RePlay() );
|
||||
}
|
||||
/// e.g. '/vimix/current/alpha f 0.3'
|
||||
else if ( attribute.compare(OSC_SOURCE_LOCK) == 0) {
|
||||
float x = 1.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new SetLock(x > 0.5f ? true : false) );
|
||||
}
|
||||
/// e.g. '/vimix/current/alpha f 0.3'
|
||||
else if ( attribute.compare(OSC_SOURCE_ALPHA) == 0) {
|
||||
float x = 1.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new SetAlpha(x), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/alpha f 0.3'
|
||||
else if ( attribute.compare(OSC_SOURCE_LOOM) == 0) {
|
||||
float x = 1.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new Loom(x), true );
|
||||
// this will require to send feedback status about source
|
||||
send_feedback = true;
|
||||
}
|
||||
/// e.g. '/vimix/current/transparency f 0.7'
|
||||
else if ( attribute.compare(OSC_SOURCE_TRANSPARENCY) == 0) {
|
||||
float x = 0.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new SetAlpha(1.f - x), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/depth f 5.0'
|
||||
else if ( attribute.compare(OSC_SOURCE_DEPTH) == 0) {
|
||||
float x = 0.f;
|
||||
arguments >> x >> osc::EndMessage;
|
||||
target->call( new SetDepth(x), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/translation ff 10.0 2.2'
|
||||
else if ( attribute.compare(OSC_SOURCE_GRAB) == 0) {
|
||||
float x = 0.f, y = 0.f;
|
||||
arguments >> x >> y >> osc::EndMessage;
|
||||
target->call( new Grab( x, y), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/scale ff 10.0 2.2'
|
||||
else if ( attribute.compare(OSC_SOURCE_RESIZE) == 0) {
|
||||
float x = 0.f, y = 0.f;
|
||||
arguments >> x >> y >> osc::EndMessage;
|
||||
target->call( new Resize( x, y), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/turn f 1.0'
|
||||
else if ( attribute.compare(OSC_SOURCE_TURN) == 0) {
|
||||
float x = 0.f, y = 0.f;
|
||||
arguments >> x;
|
||||
if (arguments.Eos())
|
||||
arguments >> osc::EndMessage;
|
||||
else // ignore second argument
|
||||
arguments >> y >> osc::EndMessage;
|
||||
target->call( new Turn( x ), true );
|
||||
}
|
||||
/// e.g. '/vimix/current/reset'
|
||||
else if ( attribute.compare(OSC_SOURCE_RESET) == 0) {
|
||||
target->call( new ResetGeometry(), true );
|
||||
}
|
||||
#ifdef CONTROL_DEBUG
|
||||
else {
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// overwrite value if source locked
|
||||
if (target->locked())
|
||||
send_feedback = true;
|
||||
|
||||
}
|
||||
catch (osc::MissingArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
|
||||
}
|
||||
catch (osc::ExcessArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
|
||||
}
|
||||
catch (osc::WrongArgumentTypeException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target %s.", attribute.c_str(), target->name().c_str());
|
||||
}
|
||||
|
||||
return send_feedback;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Control::receiveSessionAttribute(const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
bool send_feedback = false;
|
||||
|
||||
try {
|
||||
if ( attribute.compare(OSC_SYNC) == 0) {
|
||||
send_feedback = true;
|
||||
}
|
||||
else if ( attribute.compare(OSC_SESSION_VERSION) == 0) {
|
||||
float v = 0.f;
|
||||
arguments >> v >> osc::EndMessage;
|
||||
size_t id = (int) ceil(v);
|
||||
std::list<uint64_t> snapshots = Action::manager().snapshots();
|
||||
if ( id < snapshots.size() ) {
|
||||
for (size_t i = 0; i < id; ++i)
|
||||
snapshots.pop_back();
|
||||
uint64_t snap = snapshots.back();
|
||||
Action::manager().restore(snap);
|
||||
}
|
||||
send_feedback = true;
|
||||
}
|
||||
#ifdef CONTROL_DEBUG
|
||||
else {
|
||||
Log::Info(CONTROL_OSC_MSG "Ignoring attribute '%s' for target 'session'", attribute.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
catch (osc::MissingArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Missing argument for attribute '%s' for target 'session'", attribute.c_str());
|
||||
}
|
||||
catch (osc::ExcessArgumentException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Too many arguments for attribute '%s' for target 'session'", attribute.c_str());
|
||||
}
|
||||
catch (osc::WrongArgumentTypeException &e) {
|
||||
Log::Info(CONTROL_OSC_MSG "Invalid argument for attribute '%s' for target 'session'", attribute.c_str());
|
||||
}
|
||||
|
||||
return send_feedback;
|
||||
}
|
||||
|
||||
void Control::sendSourceAttibutes(const IpEndpointName &remoteEndpoint, std::string target, Source *s)
|
||||
{
|
||||
// default values
|
||||
char name[21] = {"\0"};
|
||||
float lock = 0.f;
|
||||
float play = 0.f;
|
||||
float depth = 0.f;
|
||||
float alpha = 0.f;
|
||||
|
||||
// get source or current source
|
||||
Source *_s = s;
|
||||
if ( target.compare(OSC_CURRENT) == 0 )
|
||||
_s = Mixer::manager().currentSource();
|
||||
|
||||
// fill values if the source is valid
|
||||
if (_s!=nullptr) {
|
||||
strncpy(name, _s->name().c_str(), 20);
|
||||
lock = _s->locked() ? 1.f : 0.f;
|
||||
play = _s->playing() ? 1.f : 0.f;
|
||||
depth = _s->depth();
|
||||
alpha = _s->alpha();
|
||||
}
|
||||
|
||||
// build socket to send message to indicated endpoint
|
||||
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
|
||||
|
||||
// build messages packet
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
|
||||
// create bundle
|
||||
p.Clear();
|
||||
p << osc::BeginBundle();
|
||||
|
||||
/// name
|
||||
std::string address = std::string(OSC_PREFIX) + target + OSC_SOURCE_NAME;
|
||||
p << osc::BeginMessage( address.c_str() ) << name << osc::EndMessage;
|
||||
/// Play status
|
||||
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_LOCK;
|
||||
p << osc::BeginMessage( address.c_str() ) << lock << osc::EndMessage;
|
||||
/// Play status
|
||||
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_PLAY;
|
||||
p << osc::BeginMessage( address.c_str() ) << play << osc::EndMessage;
|
||||
/// Depth
|
||||
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_DEPTH;
|
||||
p << osc::BeginMessage( address.c_str() ) << depth << osc::EndMessage;
|
||||
/// Alpha
|
||||
address = std::string(OSC_PREFIX) + target + OSC_SOURCE_ALPHA;
|
||||
p << osc::BeginMessage( address.c_str() ) << alpha << osc::EndMessage;
|
||||
|
||||
// send bundle
|
||||
p << osc::EndBundle;
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
}
|
||||
|
||||
|
||||
void Control::sendSourcesStatus(const IpEndpointName &remoteEndpoint, osc::ReceivedMessageArgumentStream arguments)
|
||||
{
|
||||
// (if an argument is given, it indicates the number of sources to update)
|
||||
float N = 0.f;
|
||||
if ( !arguments.Eos())
|
||||
arguments >> N >> osc::EndMessage;
|
||||
|
||||
// build socket to send message to indicated endpoint
|
||||
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
|
||||
|
||||
// build messages packet
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
|
||||
p.Clear();
|
||||
p << osc::BeginBundle();
|
||||
|
||||
int i = 0;
|
||||
char oscaddr[128];
|
||||
int index_current = Mixer::manager().indexCurrentSource();
|
||||
for (; i < Mixer::manager().count(); ++i) {
|
||||
// send status of currently selected
|
||||
sprintf(oscaddr, OSC_PREFIX OSC_CURRENT "/%d", i);
|
||||
p << osc::BeginMessage( oscaddr ) << (index_current == i ? 1.f : 0.f) << osc::EndMessage;
|
||||
|
||||
// send status of alpha
|
||||
sprintf(oscaddr, OSC_PREFIX "/%d" OSC_SOURCE_ALPHA, i);
|
||||
p << osc::BeginMessage( oscaddr ) << Mixer::manager().sourceAtIndex(i)->alpha() << osc::EndMessage;
|
||||
}
|
||||
|
||||
for (; i < (int) N ; ++i) {
|
||||
// reset status of currently selected
|
||||
sprintf(oscaddr, OSC_PREFIX OSC_CURRENT "/%d", i);
|
||||
p << osc::BeginMessage( oscaddr ) << 0.f << osc::EndMessage;
|
||||
|
||||
// reset status of alpha
|
||||
sprintf(oscaddr, OSC_PREFIX "/%d" OSC_SOURCE_ALPHA, i);
|
||||
p << osc::BeginMessage( oscaddr ) << 0.f << osc::EndMessage;
|
||||
}
|
||||
|
||||
p << osc::EndBundle;
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
|
||||
// send status of current source
|
||||
sendSourceAttibutes(remoteEndpoint, OSC_CURRENT);
|
||||
}
|
||||
|
||||
|
||||
void Control::sendOutputStatus(const IpEndpointName &remoteEndpoint)
|
||||
{
|
||||
// build socket to send message to indicated endpoint
|
||||
UdpTransmitSocket socket( IpEndpointName( remoteEndpoint.address, Settings::application.control.osc_port_send ) );
|
||||
|
||||
// build messages packet
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
|
||||
p.Clear();
|
||||
p << osc::BeginBundle();
|
||||
|
||||
/// output attributes
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_OUTPUT OSC_OUTPUT_ENABLE );
|
||||
p << (Settings::application.render.disabled ? 0.f : 1.f);
|
||||
p << osc::EndMessage;
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_OUTPUT OSC_OUTPUT_FADING );
|
||||
p << Mixer::manager().session()->fading();
|
||||
p << osc::EndMessage;
|
||||
|
||||
p << osc::EndBundle;
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
}
|
||||
105
ControlManager.h
Normal file
105
ControlManager.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#ifndef CONTROL_H
|
||||
#define CONTROL_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <condition_variable>
|
||||
#include "NetworkToolkit.h"
|
||||
|
||||
#define OSC_SYNC "/sync"
|
||||
|
||||
#define OSC_INFO "/info"
|
||||
#define OSC_INFO_LOG "/log"
|
||||
#define OSC_INFO_NOTIFY "/notify"
|
||||
|
||||
#define OSC_OUTPUT "/output"
|
||||
#define OSC_OUTPUT_ENABLE "/enable"
|
||||
#define OSC_OUTPUT_DISABLE "/disable"
|
||||
#define OSC_OUTPUT_FADING "/fading"
|
||||
#define OSC_OUTPUT_FADE_IN "/fade-in"
|
||||
#define OSC_OUTPUT_FADE_OUT "/fade-out"
|
||||
|
||||
#define OSC_ALL "/all"
|
||||
#define OSC_SELECTED "/selected"
|
||||
#define OSC_CURRENT "/current"
|
||||
#define OSC_NEXT "/next"
|
||||
#define OSC_PREVIOUS "/previous"
|
||||
|
||||
#define OSC_SOURCE_NAME "/name"
|
||||
#define OSC_SOURCE_LOCK "/lock"
|
||||
#define OSC_SOURCE_PLAY "/play"
|
||||
#define OSC_SOURCE_PAUSE "/pause"
|
||||
#define OSC_SOURCE_REPLAY "/replay"
|
||||
#define OSC_SOURCE_ALPHA "/alpha"
|
||||
#define OSC_SOURCE_LOOM "/loom"
|
||||
#define OSC_SOURCE_TRANSPARENCY "/transparency"
|
||||
#define OSC_SOURCE_DEPTH "/depth"
|
||||
#define OSC_SOURCE_GRAB "/grab"
|
||||
#define OSC_SOURCE_RESIZE "/resize"
|
||||
#define OSC_SOURCE_TURN "/turn"
|
||||
#define OSC_SOURCE_RESET "/reset"
|
||||
|
||||
#define OSC_SESSION "/session"
|
||||
#define OSC_SESSION_VERSION "/version"
|
||||
|
||||
class Session;
|
||||
class Source;
|
||||
|
||||
class Control
|
||||
{
|
||||
// Private Constructor
|
||||
Control();
|
||||
Control(Control const& copy) = delete;
|
||||
Control& operator=(Control const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
static Control& manager ()
|
||||
{
|
||||
// The only instance
|
||||
static Control _instance;
|
||||
return _instance;
|
||||
}
|
||||
~Control();
|
||||
|
||||
bool init();
|
||||
void terminate();
|
||||
|
||||
std::string translate (std::string addresspattern);
|
||||
|
||||
protected:
|
||||
|
||||
class RequestListener : public osc::OscPacketListener {
|
||||
protected:
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
std::string FullMessage( const osc::ReceivedMessage& m );
|
||||
};
|
||||
|
||||
bool receiveOutputAttribute(const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments);
|
||||
|
||||
bool receiveSourceAttribute(Source *target, const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments);
|
||||
|
||||
bool receiveSessionAttribute(const std::string &attribute,
|
||||
osc::ReceivedMessageArgumentStream arguments);
|
||||
|
||||
void sendSourceAttibutes(const IpEndpointName& remoteEndpoint, std::string target, Source *s = nullptr);
|
||||
void sendSourcesStatus(const IpEndpointName& remoteEndpoint, osc::ReceivedMessageArgumentStream arguments);
|
||||
void sendOutputStatus(const IpEndpointName& remoteEndpoint);
|
||||
|
||||
private:
|
||||
|
||||
static void listen();
|
||||
RequestListener listener_;
|
||||
std::condition_variable receiver_end_;
|
||||
UdpListeningReceiveSocket *receiver_;
|
||||
|
||||
std::map<std::string, std::string> translation_;
|
||||
void loadOscConfig();
|
||||
void resetOscConfig();
|
||||
|
||||
};
|
||||
|
||||
#endif // CONTROL_H
|
||||
104
CopyVisitor.cpp
Normal file
104
CopyVisitor.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* This file is part of vimix - Live video mixer
|
||||
*
|
||||
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
#include "Scene.h"
|
||||
#include "Source.h"
|
||||
|
||||
#include "CopyVisitor.h"
|
||||
|
||||
Node *CopyVisitor::deepCopy(Node *node)
|
||||
{
|
||||
CopyVisitor cv;
|
||||
node->accept(cv);
|
||||
|
||||
return cv.current_;
|
||||
}
|
||||
|
||||
CopyVisitor::CopyVisitor() : current_(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void CopyVisitor::visit(Node &n)
|
||||
{
|
||||
}
|
||||
|
||||
void CopyVisitor::visit(Group &n)
|
||||
{
|
||||
Group *here = new Group;
|
||||
|
||||
// node
|
||||
current_ = here;
|
||||
current_->copyTransform(&n);
|
||||
current_->visible_ = n.visible_;
|
||||
|
||||
// loop
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
|
||||
(*node)->accept(*this);
|
||||
|
||||
here->attach( current_ );
|
||||
|
||||
current_ = here;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CopyVisitor::visit(Switch &n)
|
||||
{
|
||||
Switch *here = new Switch;
|
||||
|
||||
// node
|
||||
current_ = here;
|
||||
current_->copyTransform(&n);
|
||||
current_->visible_ = n.visible_;
|
||||
|
||||
// switch properties
|
||||
here->setActive( n.active() );
|
||||
|
||||
// loop
|
||||
for (uint i = 0; i < n.numChildren(); ++i) {
|
||||
n.child(i)->accept(*this);
|
||||
|
||||
here->attach( current_ );
|
||||
|
||||
current_ = here;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CopyVisitor::visit(Scene &n)
|
||||
{
|
||||
Scene *here = new Scene;
|
||||
|
||||
current_ = here->root();
|
||||
n.root()->accept(*this);
|
||||
}
|
||||
|
||||
|
||||
void CopyVisitor::visit(Primitive &n)
|
||||
{
|
||||
Primitive *here = new Primitive;
|
||||
|
||||
// node
|
||||
current_ = here;
|
||||
current_->copyTransform(&n);
|
||||
current_->visible_ = n.visible_;
|
||||
|
||||
}
|
||||
24
CopyVisitor.h
Normal file
24
CopyVisitor.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef COPYVISITOR_H
|
||||
#define COPYVISITOR_H
|
||||
|
||||
#include "Visitor.h"
|
||||
|
||||
class SourceCore;
|
||||
|
||||
class CopyVisitor : public Visitor
|
||||
{
|
||||
Node *current_;
|
||||
CopyVisitor();
|
||||
|
||||
public:
|
||||
|
||||
static Node *deepCopy(Node *node);
|
||||
|
||||
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 // COPYVISITOR_H
|
||||
400
Decorations.cpp
400
Decorations.cpp
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <glm/gtx/component_wise.hpp>
|
||||
@@ -11,8 +30,11 @@
|
||||
#include "Resource.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include <glad/glad.h>
|
||||
|
||||
Frame::Frame(CornerType corner, BorderType border, ShadowType shadow) : Node(), side_(nullptr), top_(nullptr), shadow_(nullptr), square_(nullptr)
|
||||
Frame::Frame(CornerType corner, BorderType border, ShadowType shadow) : Node(),
|
||||
right_(nullptr), left_(nullptr), top_(nullptr), shadow_(nullptr), square_(nullptr)
|
||||
{
|
||||
static Mesh *shadows[3] = {nullptr};
|
||||
if (shadows[0] == nullptr) {
|
||||
@@ -20,33 +42,54 @@ Frame::Frame(CornerType corner, BorderType border, ShadowType shadow) : Node(),
|
||||
shadows[1] = new Mesh("mesh/shadow.ply", "images/shadow.dds");
|
||||
shadows[2] = new Mesh("mesh/shadow_perspective.ply", "images/shadow_perspective.dds");
|
||||
}
|
||||
static Mesh *frames[4] = {nullptr};
|
||||
static Mesh *frames[9] = {nullptr};
|
||||
if (frames[0] == nullptr) {
|
||||
frames[0] = new Mesh("mesh/border_round.ply");
|
||||
frames[1] = new Mesh("mesh/border_top.ply");
|
||||
frames[2] = new Mesh("mesh/border_large_round.ply");
|
||||
frames[3] = new Mesh("mesh/border_large_top.ply");
|
||||
frames[1] = new Mesh("mesh/border_round_left.ply");
|
||||
frames[2] = new Mesh("mesh/border_top.ply");
|
||||
frames[3] = new Mesh("mesh/border_large_round.ply");
|
||||
frames[4] = new Mesh("mesh/border_large_round_left.ply");
|
||||
frames[5] = new Mesh("mesh/border_large_top.ply");
|
||||
frames[6] = new Mesh("mesh/border_perspective_round.ply");
|
||||
frames[7] = new Mesh("mesh/border_perspective_round_left.ply");
|
||||
frames[8] = new Mesh("mesh/border_perspective_top.ply");
|
||||
}
|
||||
static LineSquare *sharpframethin = new LineSquare( 3 );
|
||||
static LineSquare *sharpframelarge = new LineSquare( 5 );
|
||||
static LineSquare *sharpframethin = new LineSquare( 4.f );
|
||||
static LineSquare *sharpframelarge = new LineSquare( 6.f );
|
||||
|
||||
if (corner == SHARP) {
|
||||
// Round corners
|
||||
if (corner == ROUND){
|
||||
if (border == THIN) {
|
||||
right_ = frames[0];
|
||||
left_ = frames[1];
|
||||
top_ = frames[2];
|
||||
}
|
||||
else{
|
||||
right_ = frames[3];
|
||||
left_ = frames[4];
|
||||
top_ = frames[5];
|
||||
}
|
||||
}
|
||||
// Group corners
|
||||
else if (corner == GROUP){
|
||||
if (border == THIN) {
|
||||
right_ = frames[6];
|
||||
left_ = frames[7];
|
||||
top_ = frames[8];
|
||||
}
|
||||
else{
|
||||
right_ = frames[6];
|
||||
left_ = frames[7];
|
||||
top_ = frames[8];
|
||||
}
|
||||
}
|
||||
// Sharp corner
|
||||
else {
|
||||
if (border == LARGE)
|
||||
square_ = sharpframelarge;
|
||||
else
|
||||
square_ = sharpframethin;
|
||||
}
|
||||
else {
|
||||
// Round corners
|
||||
if (border == THIN) {
|
||||
side_ = frames[0];
|
||||
top_ = frames[1];
|
||||
}
|
||||
else{
|
||||
side_ = frames[2];
|
||||
top_ = frames[3];
|
||||
}
|
||||
}
|
||||
|
||||
switch (shadow) {
|
||||
default:
|
||||
@@ -76,8 +119,10 @@ void Frame::update( float dt )
|
||||
Node::update(dt);
|
||||
if(top_)
|
||||
top_->update(dt);
|
||||
if(side_)
|
||||
side_->update(dt);
|
||||
if(right_)
|
||||
right_->update(dt);
|
||||
if(left_)
|
||||
left_->update(dt);
|
||||
if(shadow_)
|
||||
shadow_->update(dt);
|
||||
if(square_)
|
||||
@@ -87,9 +132,11 @@ void Frame::update( float dt )
|
||||
void Frame::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
if ( !initialized() ) {
|
||||
if(side_ && !side_->initialized())
|
||||
side_->init();
|
||||
if(top_ && !top_->initialized())
|
||||
if(right_ && !right_->initialized())
|
||||
right_->init();
|
||||
if(left_ && !left_->initialized())
|
||||
left_->init();
|
||||
if(top_ && !top_->initialized())
|
||||
top_->init();
|
||||
if(shadow_ && !shadow_->initialized())
|
||||
shadow_->init();
|
||||
@@ -102,27 +149,28 @@ void Frame::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
|
||||
glm::mat4 ctm = modelview * transform_;
|
||||
|
||||
// sharp border (scaled)
|
||||
if(square_) {
|
||||
square_->setColor(color);
|
||||
square_->draw( ctm, projection);
|
||||
}
|
||||
|
||||
// shadow (scaled)
|
||||
if(shadow_){
|
||||
shadow_->shader()->color.a = 0.98f;
|
||||
shadow_->draw( ctm, projection);
|
||||
}
|
||||
|
||||
// top (scaled)
|
||||
// round top (scaled)
|
||||
if(top_) {
|
||||
top_->shader()->color = color;
|
||||
top_->draw( ctm, projection);
|
||||
}
|
||||
|
||||
// top (scaled)
|
||||
if(square_) {
|
||||
square_->shader()->color = color;
|
||||
square_->draw( ctm, projection);
|
||||
}
|
||||
// round sides
|
||||
if(right_) {
|
||||
|
||||
if(side_) {
|
||||
|
||||
side_->shader()->color = color;
|
||||
right_->shader()->color = color;
|
||||
|
||||
// get scale
|
||||
glm::vec4 scale = ctm * glm::vec4(1.f, 1.0f, 0.f, 0.f);
|
||||
@@ -132,17 +180,26 @@ void Frame::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
glm::vec4 vec = ctm * glm::vec4(1.f, 0.f, 0.f, 0.f);
|
||||
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
|
||||
|
||||
if(side_) {
|
||||
// right side
|
||||
vec = ctm * glm::vec4(1.f, 0.f, 0.f, 1.f);
|
||||
right_->draw( GlmToolkit::transform(vec, rot, glm::vec3(scale.y, scale.y, 1.f)), projection );
|
||||
|
||||
// left side
|
||||
vec = ctm * glm::vec4(1.f, 0.f, 0.f, 1.f);
|
||||
side_->draw( GlmToolkit::transform(vec, rot, glm::vec3(scale.y, scale.y, 1.f)), projection );
|
||||
}
|
||||
if(left_) {
|
||||
|
||||
// right side
|
||||
vec = ctm * glm::vec4(-1.f, 0.f, 0.f, 1.f);
|
||||
side_->draw( GlmToolkit::transform(vec, rot, glm::vec3(-scale.y, scale.y, 1.f)), projection );
|
||||
left_->shader()->color = color;
|
||||
|
||||
}
|
||||
// get scale
|
||||
glm::vec4 scale = ctm * glm::vec4(1.f, 1.0f, 0.f, 0.f);
|
||||
|
||||
// get rotation
|
||||
glm::vec3 rot(0.f);
|
||||
glm::vec4 vec = ctm * glm::vec4(1.f, 0.f, 0.f, 0.f);
|
||||
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
|
||||
|
||||
// right side
|
||||
vec = ctm * glm::vec4(-1.f, 0.f, 0.f, 1.f);
|
||||
left_->draw( GlmToolkit::transform(vec, rot, glm::vec3(scale.y, scale.y, 1.f)), projection );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,10 +216,12 @@ Handles::Handles(Type type) : Node(), type_(type)
|
||||
static Mesh *handle_rotation = new Mesh("mesh/border_handles_rotation.ply");
|
||||
static Mesh *handle_corner = new Mesh("mesh/border_handles_overlay.ply");
|
||||
static Mesh *handle_scale = new Mesh("mesh/border_handles_scale.ply");
|
||||
static Mesh *handle_restore = new Mesh("mesh/border_handles_menu.ply");
|
||||
static Mesh *handle_crop = new Mesh("mesh/border_handles_crop.ply");
|
||||
static Mesh *handle_menu = new Mesh("mesh/border_handles_menu.ply");
|
||||
static Mesh *handle_lock = new Mesh("mesh/border_handles_lock.ply");
|
||||
static Mesh *handle_unlock = new Mesh("mesh/border_handles_lock_open.ply");
|
||||
static Mesh *handle_shadow = new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
|
||||
|
||||
color = glm::vec4( 1.f, 1.f, 0.f, 1.f);
|
||||
if ( type_ == Handles::ROTATE ) {
|
||||
handle_ = handle_rotation;
|
||||
}
|
||||
@@ -170,14 +229,23 @@ Handles::Handles(Type type) : Node(), type_(type)
|
||||
handle_ = handle_scale;
|
||||
}
|
||||
else if ( type_ == Handles::MENU ) {
|
||||
handle_ = handle_restore;
|
||||
handle_ = handle_menu;
|
||||
}
|
||||
else if ( type_ == Handles::CROP ) {
|
||||
handle_ = handle_crop;
|
||||
}
|
||||
else if ( type_ == Handles::LOCKED ) {
|
||||
handle_ = handle_lock;
|
||||
}
|
||||
else if ( type_ == Handles::UNLOCKED ) {
|
||||
handle_ = handle_unlock;
|
||||
}
|
||||
else {
|
||||
handle_ = handle_corner;
|
||||
}
|
||||
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
corner_ = glm::vec2(0.f, 0.f);
|
||||
|
||||
shadow_ = handle_shadow;
|
||||
}
|
||||
|
||||
@@ -193,7 +261,6 @@ void Handles::update( float dt )
|
||||
|
||||
void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
static Mesh *handle_active = new Mesh("mesh/border_handles_overlay_filled.ply");
|
||||
|
||||
if ( !initialized() ) {
|
||||
if(handle_ && !handle_->initialized())
|
||||
@@ -203,21 +270,20 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
init();
|
||||
}
|
||||
|
||||
if ( visible_ ) {
|
||||
if ( visible_ && handle_) {
|
||||
static Mesh *handle_active = new Mesh("mesh/border_handles_overlay_filled.ply");
|
||||
|
||||
// set color
|
||||
handle_->shader()->color = color;
|
||||
handle_active->shader()->color = color;
|
||||
|
||||
// extract rotation from modelview
|
||||
glm::mat4 ctm;
|
||||
glm::vec3 rot(0.f);
|
||||
glm::vec4 vec = modelview * glm::vec4(1.f, 0.f, 0.f, 0.f);
|
||||
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
|
||||
glm::vec4 vec;
|
||||
glm::vec3 tra, rot, sca;
|
||||
|
||||
// extract scaling and mirroring
|
||||
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview ;
|
||||
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
|
||||
glm::vec4 mirror = glm::sign(vec);
|
||||
// get rotation and mirroring from the modelview
|
||||
GlmToolkit::inverse_transform(modelview, tra, rot, sca);
|
||||
glm::vec3 mirror = glm::sign(sca);
|
||||
|
||||
if ( type_ == Handles::RESIZE ) {
|
||||
|
||||
@@ -295,7 +361,19 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
glm::vec4 pos = ctm * glm::vec4(mirror.x * 0.12f, mirror.x * -0.12f, 0.f, 1.f);
|
||||
// 2. ..from the bottom right corner (1,1)
|
||||
vec = ( modelview * glm::vec4(1.f, -1.f, 0.f, 1.f) ) + pos;
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(mirror.x, mirror.y, 1.f));
|
||||
ctm = GlmToolkit::transform(vec, rot, mirror);
|
||||
// 3. draw
|
||||
shadow_->draw( ctm, projection );
|
||||
handle_->draw( ctm, projection );
|
||||
}
|
||||
else if ( type_ == Handles::CROP ){
|
||||
// one icon in bottom right corner
|
||||
// 1. Fixed displacement by (0.12,0.12) along the rotation..
|
||||
ctm = GlmToolkit::transform(glm::vec4(0.f), rot, mirror);
|
||||
glm::vec4 pos = ctm * glm::vec4(mirror.x * 0.12f, mirror.x * 0.12f, 0.f, 1.f);
|
||||
// 2. ..from the bottom right corner (1,-1)
|
||||
vec = ( modelview * glm::vec4(-1.f, -1.f, 0.f, 1.f) ) + pos;
|
||||
ctm = GlmToolkit::transform(vec, rot, mirror);
|
||||
// 3. draw
|
||||
shadow_->draw( ctm, projection );
|
||||
handle_->draw( ctm, projection );
|
||||
@@ -307,7 +385,19 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
glm::vec4 pos = ctm * glm::vec4( -0.12f, 0.12f, 0.f, 1.f);
|
||||
// 2. ..from the top right corner (1,1)
|
||||
vec = ( modelview * glm::vec4(-1.f, 1.f, 0.f, 1.f) ) + pos;
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
ctm = GlmToolkit::transform(vec, rot, mirror);
|
||||
// 3. draw
|
||||
shadow_->draw( ctm, projection );
|
||||
handle_->draw( ctm, projection );
|
||||
}
|
||||
else if ( type_ == Handles::LOCKED || type_ == Handles::UNLOCKED ){
|
||||
// one icon in top left corner
|
||||
// 1. Fixed displacement by (-0.12,0.12) along the rotation..
|
||||
ctm = GlmToolkit::transform(glm::vec4(0.f), rot, mirror);
|
||||
glm::vec4 pos = ctm * glm::vec4( -0.12f, 0.12f, 0.f, 1.f);
|
||||
// 2. ..from the bottom right corner (1,-1)
|
||||
vec = ( modelview * glm::vec4(1.f, -1.f, 0.f, 1.f) ) + pos;
|
||||
ctm = GlmToolkit::transform(vec, rot, mirror);
|
||||
// 3. draw
|
||||
shadow_->draw( ctm, projection );
|
||||
handle_->draw( ctm, projection );
|
||||
@@ -325,36 +415,73 @@ void Handles::accept(Visitor& v)
|
||||
|
||||
Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
|
||||
{
|
||||
static Mesh *shadow= new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
|
||||
static Mesh *shadows[(int)EMPTY+1] = {nullptr};
|
||||
static Mesh *icons[(int)EMPTY+1] = {nullptr};
|
||||
if (icons[0] == nullptr) {
|
||||
icons[CIRCLE_POINT] = new Mesh("mesh/point.ply");
|
||||
shadows[CIRCLE_POINT] = nullptr;
|
||||
icons[SQUARE_POINT] = new Mesh("mesh/square_point.ply");
|
||||
icons[IMAGE] = new Mesh("mesh/icon_image.ply");
|
||||
icons[VIDEO] = new Mesh("mesh/icon_video.ply");
|
||||
icons[SESSION] = new Mesh("mesh/icon_vimix.ply");
|
||||
icons[CLONE] = new Mesh("mesh/icon_clone.ply");
|
||||
icons[RENDER] = new Mesh("mesh/icon_render.ply");
|
||||
icons[PATTERN] = new Mesh("mesh/icon_gear.ply");
|
||||
icons[CAMERA] = new Mesh("mesh/icon_camera.ply");
|
||||
icons[SHARE] = new Mesh("mesh/icon_share.ply");
|
||||
icons[DOTS] = new Mesh("mesh/icon_dots.ply");
|
||||
icons[BUSY] = new Mesh("mesh/icon_circles.ply");
|
||||
icons[LOCK] = new Mesh("mesh/icon_lock.ply");
|
||||
icons[UNLOCK] = new Mesh("mesh/icon_unlock.ply");
|
||||
icons[CROP] = new Mesh("mesh/icon_rightarrow.ply");
|
||||
icons[CIRCLE] = new Mesh("mesh/icon_circle.ply");
|
||||
icons[CLOCK] = new Mesh("mesh/icon_clock.ply");
|
||||
icons[CLOCK_H] = new Mesh("mesh/icon_clock_hand.ply");
|
||||
icons[SQUARE] = new Mesh("mesh/icon_square.ply");
|
||||
icons[CROSS] = new Mesh("mesh/icon_cross.ply");
|
||||
icons[GRID] = new Mesh("mesh/icon_grid.ply");
|
||||
icons[EMPTY] = new Mesh("mesh/icon_empty.ply");
|
||||
shadows[SQUARE_POINT] = nullptr;
|
||||
icons[IMAGE] = new Mesh("mesh/icon_image.ply");
|
||||
shadows[IMAGE] = shadow;
|
||||
icons[SEQUENCE] = new Mesh("mesh/icon_sequence.ply");
|
||||
shadows[SEQUENCE]= shadow;
|
||||
icons[VIDEO] = new Mesh("mesh/icon_video.ply");
|
||||
shadows[VIDEO] = shadow;
|
||||
icons[SESSION] = new Mesh("mesh/icon_vimix.ply");
|
||||
shadows[SESSION]= shadow;
|
||||
icons[CLONE] = new Mesh("mesh/icon_clone.ply");
|
||||
shadows[CLONE] = shadow;
|
||||
icons[RENDER] = new Mesh("mesh/icon_render.ply");
|
||||
shadows[RENDER] = shadow;
|
||||
icons[GROUP] = new Mesh("mesh/icon_group_vimix.ply");
|
||||
shadows[GROUP] = shadow;
|
||||
icons[PATTERN] = new Mesh("mesh/icon_gear.ply");
|
||||
shadows[PATTERN]= shadow;
|
||||
icons[CAMERA] = new Mesh("mesh/icon_camera.ply");
|
||||
shadows[CAMERA] = shadow;
|
||||
icons[CUBE] = new Mesh("mesh/icon_cube.ply");
|
||||
shadows[CUBE] = shadow;
|
||||
icons[SHARE] = new Mesh("mesh/icon_share.ply");
|
||||
shadows[SHARE] = shadow;
|
||||
icons[DOTS] = new Mesh("mesh/icon_dots.ply");
|
||||
shadows[DOTS] = nullptr;
|
||||
icons[BUSY] = new Mesh("mesh/icon_circles.ply");
|
||||
shadows[BUSY] = nullptr;
|
||||
icons[LOCK] = new Mesh("mesh/icon_lock.ply");
|
||||
shadows[LOCK] = shadow;
|
||||
icons[UNLOCK] = new Mesh("mesh/icon_unlock.ply");
|
||||
shadows[UNLOCK] = shadow;
|
||||
icons[EYE] = new Mesh("mesh/icon_eye.ply");
|
||||
shadows[EYE] = shadow;
|
||||
icons[EYESLASH] = new Mesh("mesh/icon_eye_slash.ply");
|
||||
shadows[EYESLASH] = shadow;
|
||||
icons[VECTORSLASH] = new Mesh("mesh/icon_vector_square_slash.ply");
|
||||
shadows[VECTORSLASH] = shadow;
|
||||
icons[ARROWS] = new Mesh("mesh/icon_rightarrow.ply");
|
||||
shadows[ARROWS] = shadow;
|
||||
icons[ROTATION] = new Mesh("mesh/border_handles_rotation.ply");
|
||||
shadows[ROTATION] = shadow;
|
||||
icons[CIRCLE] = new Mesh("mesh/icon_circle.ply");
|
||||
shadows[CIRCLE] = nullptr;
|
||||
icons[CLOCK] = new Mesh("mesh/icon_clock.ply");
|
||||
shadows[CLOCK] = nullptr;
|
||||
icons[CLOCK_H] = new Mesh("mesh/icon_clock_hand.ply");
|
||||
shadows[CLOCK_H]= nullptr;
|
||||
icons[SQUARE] = new Mesh("mesh/icon_square.ply");
|
||||
shadows[SQUARE] = nullptr;
|
||||
icons[CROSS] = new Mesh("mesh/icon_cross.ply");
|
||||
shadows[CROSS] = nullptr;
|
||||
icons[GRID] = new Mesh("mesh/icon_grid.ply");
|
||||
shadows[GRID] = nullptr;
|
||||
icons[EMPTY] = new Mesh("mesh/icon_empty.ply");
|
||||
shadows[EMPTY] = shadow;
|
||||
}
|
||||
|
||||
static Mesh *shadow= new Mesh("mesh/border_handles_shadow.ply", "images/soft_shadow.dds");
|
||||
|
||||
symbol_ = icons[type_];
|
||||
shadow_ = shadow;
|
||||
shadow_ = shadows[type_];
|
||||
translation_ = pos;
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
@@ -397,7 +524,8 @@ void Symbol::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
// generate matrix
|
||||
ctm = GlmToolkit::transform(tran, rot, sca);
|
||||
|
||||
shadow_->draw( ctm, projection );
|
||||
if (shadow_)
|
||||
shadow_->draw( ctm, projection );
|
||||
symbol_->draw( ctm, projection);
|
||||
}
|
||||
}
|
||||
@@ -419,11 +547,6 @@ Disk::Disk() : Node()
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
Disk::~Disk()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Disk::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
if ( !initialized() ) {
|
||||
@@ -449,55 +572,88 @@ void Disk::accept(Visitor& v)
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
Surface *Glyph::font_ = nullptr;
|
||||
|
||||
//SelectionBox::SelectionBox()
|
||||
//{
|
||||
//// color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
// color = glm::vec4( 1.f, 0.f, 0.f, 1.f);
|
||||
// square_ = new LineSquare( 3 );
|
||||
Glyph::Glyph(int imgui_font_index) : Node(), character_(' '), font_index_(imgui_font_index), baseline_(0.f), shape_(1.f, 1.f)
|
||||
{
|
||||
if (Glyph::font_ == nullptr)
|
||||
Glyph::font_ = new Surface;
|
||||
|
||||
//}
|
||||
uvTransform_ = glm::identity<glm::mat4>();
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
//void SelectionBox::draw (glm::mat4 modelview, glm::mat4 projection)
|
||||
//{
|
||||
// if ( !initialized() ) {
|
||||
// square_->init();
|
||||
// init();
|
||||
// }
|
||||
void Glyph::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
if ( !initialized() ) {
|
||||
|
||||
// if (visible_) {
|
||||
if (!Glyph::font_->initialized()) {
|
||||
|
||||
// // use a visitor bounding box to calculate extend of all selected nodes
|
||||
// BoundingBoxVisitor vbox;
|
||||
uint tex = (uint)(intptr_t)ImGui::GetIO().Fonts->TexID;
|
||||
if ( tex > 0) {
|
||||
Glyph::font_->init();
|
||||
font_->setTextureIndex(tex);
|
||||
}
|
||||
}
|
||||
|
||||
// // visit every child of the selection
|
||||
// for (NodeSet::iterator node = children_.begin();
|
||||
// node != children_.end(); node++) {
|
||||
// // reset the transform before
|
||||
// vbox.setModelview(glm::identity<glm::mat4>());
|
||||
// (*node)->accept(vbox);
|
||||
// }
|
||||
if (Glyph::font_->initialized()) {
|
||||
init();
|
||||
setChar(character_);
|
||||
}
|
||||
}
|
||||
|
||||
// // get the bounding box
|
||||
// bbox_ = vbox.bbox();
|
||||
if ( visible_ ) {
|
||||
|
||||
//// Log::Info(" -------- visitor box (%f, %f)-(%f, %f)", bbox_.min().x, bbox_.min().y, bbox_.max().x, bbox_.max().y);
|
||||
// set color
|
||||
Glyph::font_->shader()->color = color;
|
||||
|
||||
// // set color
|
||||
// square_->shader()->color = color;
|
||||
// modify the shader iTransform to select UVs of the desired glyph
|
||||
Glyph::font_->shader()->iTransform = uvTransform_;
|
||||
|
||||
// // compute transformation from bounding box
|
||||
//// glm::mat4 ctm = modelview * GlmToolkit::transform(glm::vec3(0.f), glm::vec3(0.f), glm::vec3(1.f));
|
||||
// glm::mat4 ctm = modelview * GlmToolkit::transform(bbox_.center(), glm::vec3(0.f), bbox_.scale());
|
||||
// rebuild a matrix with rotation (see handles) and translation from modelview + translation_
|
||||
// and define scale to be 1, 1
|
||||
glm::mat4 ctm;
|
||||
glm::vec3 rot(0.f);
|
||||
glm::vec4 vec = modelview * glm::vec4(1.f, 0.f, 0.f, 0.f);
|
||||
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
|
||||
// extract scaling
|
||||
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview ;
|
||||
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
|
||||
glm::vec2 sca = glm::vec2(vec.y) * glm::vec2(scale_.y) * shape_;
|
||||
// extract translation
|
||||
glm::vec3 tran = glm::vec3(modelview[3][0], modelview[3][1], modelview[3][2]) ;
|
||||
tran += (translation_ + glm::vec3(0.f, baseline_ * scale_.y, 0.f) ) * glm::vec3(vec);
|
||||
// apply local rotation
|
||||
rot.z += rotation_.z;
|
||||
// generate matrix
|
||||
ctm = GlmToolkit::transform(tran, rot, glm::vec3(sca, 1.f));
|
||||
|
||||
// // draw bbox
|
||||
//// square_->draw( modelview, projection);
|
||||
// square_->draw( ctm, projection);
|
||||
Glyph::font_->draw( ctm, projection);
|
||||
}
|
||||
}
|
||||
|
||||
// // DEBUG
|
||||
//// visible_=false;
|
||||
// }
|
||||
void Glyph::setChar(char c)
|
||||
{
|
||||
character_ = c;
|
||||
|
||||
//}
|
||||
if (initialized()) {
|
||||
// get from imgui the UVs of the given char
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImFont* myfont = io.Fonts->Fonts[0];
|
||||
if (font_index_ > 0 && font_index_ < io.Fonts->Fonts.size() )
|
||||
myfont = io.Fonts->Fonts[font_index_];
|
||||
const ImFontGlyph* glyph = myfont->FindGlyph(character_);
|
||||
|
||||
if (glyph) {
|
||||
// create a texture UV transform to get the UV coordinates of the glyph
|
||||
const glm::vec3 uv_t = glm::vec3( glyph->U0, glyph->V0, 0.f);
|
||||
const glm::vec3 uv_s = glm::vec3( glyph->U1 - glyph->U0, glyph->V1 - glyph->V0, 1.f);
|
||||
const glm::vec3 uv_r = glm::vec3(0.f, 0.f, 0.f);
|
||||
uvTransform_ = GlmToolkit::transform(uv_t, uv_r, uv_s);
|
||||
|
||||
// remember glyph shape
|
||||
shape_ = glm::vec2(glyph->X1 - glyph->X0, glyph->Y1 - glyph->Y0) / myfont->FontSize;
|
||||
baseline_ = -glyph->Y0 / myfont->FontSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class Frame : public Node
|
||||
{
|
||||
public:
|
||||
|
||||
typedef enum { ROUND = 0, SHARP } CornerType;
|
||||
typedef enum { ROUND = 0, SHARP, GROUP } CornerType;
|
||||
typedef enum { THIN = 0, LARGE } BorderType;
|
||||
typedef enum { NONE = 0, GLOW, DROP, PERSPECTIVE } ShadowType;
|
||||
|
||||
@@ -23,11 +23,11 @@ public:
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
Mesh *border() const { return side_; }
|
||||
glm::vec4 color;
|
||||
|
||||
protected:
|
||||
Mesh *side_;
|
||||
Mesh *right_;
|
||||
Mesh *left_;
|
||||
Mesh *top_;
|
||||
Mesh *shadow_;
|
||||
LineSquare *square_;
|
||||
@@ -36,7 +36,7 @@ protected:
|
||||
class Handles : public Node
|
||||
{
|
||||
public:
|
||||
typedef enum { RESIZE = 0, RESIZE_H, RESIZE_V, ROTATE, SCALE, MENU } Type;
|
||||
typedef enum { RESIZE = 0, RESIZE_H, RESIZE_V, ROTATE, SCALE, CROP, MENU, LOCKED, UNLOCKED } Type;
|
||||
Handles(Type type);
|
||||
~Handles();
|
||||
|
||||
@@ -60,9 +60,9 @@ protected:
|
||||
class Symbol : public Node
|
||||
{
|
||||
public:
|
||||
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, PATTERN, CAMERA, SHARE,
|
||||
DOTS, BUSY, LOCK, UNLOCK, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
|
||||
Symbol(Type t = CIRCLE_POINT, glm::vec3 pos = glm::vec3(0.f));
|
||||
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, SEQUENCE, 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, glm::vec3 pos = glm::vec3(0.f));
|
||||
~Symbol();
|
||||
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
@@ -83,7 +83,6 @@ class Disk : public Node
|
||||
{
|
||||
public:
|
||||
Disk();
|
||||
~Disk();
|
||||
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
@@ -94,20 +93,26 @@ protected:
|
||||
static Mesh *disk_;
|
||||
};
|
||||
|
||||
//class SelectionBox : public Group
|
||||
//{
|
||||
//public:
|
||||
// SelectionBox();
|
||||
class Glyph : public Node
|
||||
{
|
||||
public:
|
||||
Glyph(int imgui_font_index = 0);
|
||||
void setChar(char c);
|
||||
|
||||
// void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
|
||||
// glm::vec4 color;
|
||||
glm::vec4 color;
|
||||
|
||||
//protected:
|
||||
// LineSquare *square_;
|
||||
// GlmToolkit::AxisAlignedBoundingBox bbox_;
|
||||
protected:
|
||||
|
||||
//};
|
||||
char character_;
|
||||
int font_index_;
|
||||
float baseline_;
|
||||
glm::vec2 shape_;
|
||||
glm::mat4 uvTransform_;
|
||||
static Surface *font_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // DECORATIONS_H
|
||||
|
||||
214
DeviceSource.cpp
214
DeviceSource.cpp
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
@@ -19,7 +38,7 @@
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define DEVICE_DEBUG
|
||||
//#define GST_DEVICE_DEBUG
|
||||
#define GST_DEVICE_DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
@@ -101,80 +120,99 @@ gboolean
|
||||
Device::callback_device_monitor (GstBus *, GstMessage * message, gpointer )
|
||||
{
|
||||
GstDevice *device;
|
||||
gchar *name;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_DEVICE_ADDED: {
|
||||
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;
|
||||
|
||||
manager().add(device);
|
||||
gst_object_unref (device);
|
||||
|
||||
}
|
||||
break;
|
||||
case GST_MESSAGE_DEVICE_REMOVED: {
|
||||
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;
|
||||
|
||||
manager().remove(device);
|
||||
gst_object_unref (device);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
void Device::remove(const char *device)
|
||||
|
||||
void Device::add(GstDevice *device)
|
||||
{
|
||||
if (device==nullptr)
|
||||
return;
|
||||
|
||||
gchar *device_name = gst_device_get_display_name (device);
|
||||
|
||||
if ( std::find(manager().src_name_.begin(), manager().src_name_.end(), device_name) == manager().src_name_.end()) {
|
||||
|
||||
std::string p = pipelineForDevice(device, manager().src_description_.size());
|
||||
DeviceConfigSet confs = getDeviceConfigs(p);
|
||||
|
||||
// add if not in the list and valid
|
||||
if (!p.empty() && !confs.empty()) {
|
||||
src_name_.push_back(device_name);
|
||||
src_description_.push_back(p);
|
||||
src_config_.push_back(confs);
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
|
||||
g_print("\nDevice %s plugged : %s\n", device_name, stru);
|
||||
g_free (stru);
|
||||
#endif
|
||||
}
|
||||
list_uptodate_ = false;
|
||||
}
|
||||
g_free (device_name);
|
||||
}
|
||||
|
||||
void Device::remove(GstDevice *device)
|
||||
{
|
||||
if (device==nullptr)
|
||||
return;
|
||||
|
||||
gchar *device_name = gst_device_get_display_name (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 )
|
||||
if ( (*nameit).compare(device_name) == 0 )
|
||||
{
|
||||
src_name_.erase(nameit);
|
||||
src_description_.erase(descit);
|
||||
src_config_.erase(coit);
|
||||
|
||||
list_uptodate_ = false;
|
||||
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
g_print("\nDevice %s unplugged\n", device_name);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
nameit++;
|
||||
descit++;
|
||||
coit++;
|
||||
++nameit;
|
||||
++descit;
|
||||
++coit;
|
||||
}
|
||||
g_free (device_name);
|
||||
}
|
||||
|
||||
|
||||
Device::Device()
|
||||
Device::Device(): list_uptodate_(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Device::init()
|
||||
{
|
||||
GstBus *bus;
|
||||
GstCaps *caps;
|
||||
@@ -198,24 +236,9 @@ Device::Device()
|
||||
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);
|
||||
add(device);
|
||||
gst_object_unref (device);
|
||||
}
|
||||
g_list_free(devices);
|
||||
|
||||
@@ -257,7 +280,7 @@ 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) { }
|
||||
explicit hasDevice(const std::string &d) : _d(d) { }
|
||||
private:
|
||||
std::string _d;
|
||||
};
|
||||
@@ -325,13 +348,14 @@ int Device::index(const std::string &device) const
|
||||
return i;
|
||||
}
|
||||
|
||||
DeviceSource::DeviceSource() : StreamSource()
|
||||
DeviceSource::DeviceSource(uint64_t id) : StreamSource(id)
|
||||
{
|
||||
// create stream
|
||||
stream_ = new Stream;
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::CAMERA, glm::vec3(0.8f, 0.8f, 0.01f));
|
||||
symbol_ = new Symbol(Symbol::CAMERA, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
DeviceSource::~DeviceSource()
|
||||
@@ -341,9 +365,8 @@ DeviceSource::~DeviceSource()
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -359,32 +382,42 @@ void DeviceSource::setDevice(const std::string &devicename)
|
||||
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++ ){
|
||||
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);
|
||||
if (!confs.empty()) {
|
||||
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;
|
||||
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 ( 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";
|
||||
if ( device_.find("Screen") != std::string::npos )
|
||||
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue max-size-buffers=3";
|
||||
|
||||
pipeline << " ! videoconvert";
|
||||
pipeline << " ! videoconvert";
|
||||
|
||||
stream_->open( pipeline.str(), best.width, best.height);
|
||||
stream_->play(true);
|
||||
// resize render buffer
|
||||
if (renderbuffer_)
|
||||
renderbuffer_->resize(best.width, best.height);
|
||||
|
||||
// open gstreamer
|
||||
stream_->open( pipeline.str(), best.width, best.height);
|
||||
stream_->play(true);
|
||||
}
|
||||
|
||||
// will be ready after init and one frame rendered
|
||||
ready_ = false;
|
||||
}
|
||||
else
|
||||
Log::Warning("No such device '%s'", device_.c_str());
|
||||
@@ -430,11 +463,11 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
|
||||
// get the first pad and its content
|
||||
GstIterator *iter = gst_element_iterate_src_pads(elem);
|
||||
GValue vPad = G_VALUE_INIT;
|
||||
GstPad* ret = NULL;
|
||||
GstPad* pad_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);
|
||||
pad_ret = GST_PAD(g_value_get_object(&vPad));
|
||||
GstCaps *device_caps = gst_pad_query_caps (pad_ret, NULL);
|
||||
|
||||
// loop over all caps offered by the pad
|
||||
int C = gst_caps_get_size(device_caps);
|
||||
@@ -507,8 +540,8 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
|
||||
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);
|
||||
for (int i = 0; i < N; ++i ){
|
||||
const GValue *frac = gst_value_list_get_value(val, i);
|
||||
// read one fraction in the list
|
||||
if ( GST_VALUE_HOLDS_FRACTION(frac)) {
|
||||
int n = gst_value_get_fraction_numerator(frac);
|
||||
@@ -556,9 +589,14 @@ DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
|
||||
glm::ivec2 DeviceSource::icon() const
|
||||
{
|
||||
if ( device_.find("Screen") != std::string::npos )
|
||||
return glm::ivec2(19, 1);
|
||||
return glm::ivec2(ICON_SOURCE_DEVICE_SCREEN);
|
||||
else
|
||||
return glm::ivec2(2, 14);
|
||||
return glm::ivec2(ICON_SOURCE_DEVICE);
|
||||
}
|
||||
|
||||
std::string DeviceSource::info() const
|
||||
{
|
||||
return std::string("device '") + device_ + "'";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
class DeviceSource : public StreamSource
|
||||
{
|
||||
public:
|
||||
DeviceSource();
|
||||
DeviceSource(uint64_t id = 0);
|
||||
~DeviceSource();
|
||||
|
||||
// Source interface
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
inline std::string device() const { return device_; }
|
||||
|
||||
glm::ivec2 icon() const override;
|
||||
std::string info() const override;
|
||||
|
||||
private:
|
||||
std::string device_;
|
||||
@@ -87,8 +88,8 @@ class Device
|
||||
friend class DeviceSource;
|
||||
|
||||
Device();
|
||||
Device(Device const& copy); // Not Implemented
|
||||
Device& operator=(Device const& copy); // Not Implemented
|
||||
Device(Device const& copy) = delete;
|
||||
Device& operator=(Device const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
@@ -99,6 +100,7 @@ public:
|
||||
return _instance;
|
||||
}
|
||||
|
||||
void init();
|
||||
int numDevices () const;
|
||||
std::string name (int index) const;
|
||||
std::string description (int index) const;
|
||||
@@ -115,7 +117,8 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
void remove(const char *device);
|
||||
void remove(GstDevice *device);
|
||||
void add(GstDevice *device);
|
||||
|
||||
std::vector< std::string > src_name_;
|
||||
std::vector< std::string > src_description_;
|
||||
|
||||
615
DialogToolkit.cpp
Normal file
615
DialogToolkit.cpp
Normal file
@@ -0,0 +1,615 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
|
||||
// multiplatform implementation of system dialogs
|
||||
//
|
||||
// 'TinyFileDialog' from https://github.com/native-toolkit/tinyfiledialogs.git
|
||||
// is the prefered solution, but it is not compatible with linux snap packaging
|
||||
// because of 'zenity' access rights nightmare :(
|
||||
// Thus this re-implementation of native GTK+ dialogs for linux
|
||||
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "DialogToolkit.h"
|
||||
|
||||
#if defined(LINUX)
|
||||
#define USE_TINYFILEDIALOG 0
|
||||
#else
|
||||
#define USE_TINYFILEDIALOG 1
|
||||
#endif
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
#include "tinyfiledialogs.h"
|
||||
#else
|
||||
#include <stdio.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static bool gtk_init_ok = false;
|
||||
static int window_x = -1, window_y = -1;
|
||||
|
||||
void add_filter_any_file_dialog( GtkWidget *dialog)
|
||||
{
|
||||
/* append a wildcard option */
|
||||
GtkFileFilter *filter;
|
||||
filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name( filter, "Any file" );
|
||||
gtk_file_filter_add_pattern( filter, "*" );
|
||||
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
|
||||
}
|
||||
|
||||
void add_filter_file_dialog( GtkWidget *dialog, int const countfilterPatterns, char const * const * const filterPatterns, char const * const filterDescription)
|
||||
{
|
||||
GtkFileFilter *filter;
|
||||
filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name( filter, filterDescription );
|
||||
for (int i = 0; i < countfilterPatterns; i++ ) {
|
||||
gtk_file_filter_add_pattern( filter, g_ascii_strdown(filterPatterns[i], -1) );
|
||||
gtk_file_filter_add_pattern( filter, g_ascii_strup(filterPatterns[i], -1) );
|
||||
}
|
||||
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
|
||||
}
|
||||
|
||||
void wait_for_event(void)
|
||||
{
|
||||
while ( gtk_events_pending() )
|
||||
gtk_main_iteration();
|
||||
}
|
||||
|
||||
bool gtk_init()
|
||||
{
|
||||
if (!gtk_init_ok) {
|
||||
if ( gtk_init_check( NULL, NULL ) )
|
||||
gtk_init_ok = true;
|
||||
}
|
||||
return gtk_init_ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// globals
|
||||
const std::chrono::milliseconds timeout = std::chrono::milliseconds(4);
|
||||
bool DialogToolkit::FileDialog::busy_ = false;
|
||||
|
||||
//
|
||||
// FileDialog common functions
|
||||
//
|
||||
DialogToolkit::FileDialog::FileDialog(const std::string &name) : id_(name)
|
||||
{
|
||||
if ( Settings::application.dialogRecentFolder.count(id_) == 0 )
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::home_path();
|
||||
}
|
||||
|
||||
bool DialogToolkit::FileDialog::closed()
|
||||
{
|
||||
if ( !promises_.empty() ) {
|
||||
// check that file dialog thread finished
|
||||
if (promises_.back().wait_for(timeout) == std::future_status::ready ) {
|
||||
// get the filename from this file dialog
|
||||
path_ = promises_.back().get();
|
||||
if (!path_.empty()) {
|
||||
// save path location
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_);
|
||||
}
|
||||
// done with this file dialog
|
||||
promises_.pop_back();
|
||||
busy_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// type specific implementations
|
||||
//
|
||||
std::string openImageFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenImageDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openImageFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string openSessionFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenSessionDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openSessionFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string openMediaFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenMediaDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openMediaFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string saveSessionFileDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::SaveSessionDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, saveSessionFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string openFolderDialog(const std::string &label, const std::string &path);
|
||||
void DialogToolkit::OpenFolderDialog::open()
|
||||
{
|
||||
if ( !busy_ && promises_.empty() ) {
|
||||
promises_.emplace_back( std::async(std::launch::async, openFolderDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::list<std::string> selectImagesFileDialog(const std::string &label,const std::string &path);
|
||||
void DialogToolkit::MultipleImagesDialog::open()
|
||||
{
|
||||
if ( !busy_ && promisedlist_.empty() ) {
|
||||
promisedlist_.emplace_back( std::async(std::launch::async, selectImagesFileDialog, id_,
|
||||
Settings::application.dialogRecentFolder[id_]) );
|
||||
busy_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DialogToolkit::MultipleImagesDialog::closed()
|
||||
{
|
||||
if ( !promisedlist_.empty() ) {
|
||||
// check that file dialog thread finished
|
||||
if (promisedlist_.back().wait_for(timeout) == std::future_status::ready ) {
|
||||
// get the filename from this file dialog
|
||||
std::list<std::string> list = promisedlist_.back().get();
|
||||
if (!list.empty()) {
|
||||
// selected a filenames
|
||||
pathlist_ = list;
|
||||
path_ = list.front();
|
||||
// save path location
|
||||
Settings::application.dialogRecentFolder[id_] = SystemToolkit::path_filename(path_);
|
||||
}
|
||||
else {
|
||||
pathlist_.clear();
|
||||
path_.clear();
|
||||
}
|
||||
// done with this file dialog
|
||||
promisedlist_.pop_back();
|
||||
busy_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// CALLBACKS
|
||||
//
|
||||
//
|
||||
|
||||
std::string saveSessionFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
char const * save_pattern[1] = { VIMIX_FILES_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * save_file_name;
|
||||
|
||||
save_file_name = tinyfd_saveFileDialog( label.c_str(), path.c_str(), 1, save_pattern, "vimix session");
|
||||
|
||||
if (save_file_name)
|
||||
filename = std::string(save_file_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Save", GTK_RESPONSE_ACCEPT, NULL );
|
||||
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, save_pattern, "vimix session");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), path.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *save_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (save_file_name)
|
||||
filename = std::string(save_file_name);
|
||||
g_free( save_file_name );
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
std::string extension = filename.substr(filename.find_last_of(".") + 1);
|
||||
if (!filename.empty() && extension != "mix")
|
||||
filename += ".mix";
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string openSessionFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[1] = { VIMIX_FILES_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 1, open_pattern, "vimix session", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 1, open_pattern, "vimix session");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
g_free( open_file_name );
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string openMediaFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[26] = { MEDIA_FILES_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 26, open_pattern, "All supported formats", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 26, open_pattern, "Supported formats (videos, images, sessions)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
g_free( open_file_name );
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string openImageFileDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[5] = { IMAGES_FILES_PATTERN };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( label.c_str(), startpath.c_str(), 5, open_pattern, "Image (JPG, PNG, BMP, PPM, GIF)", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 5, open_pattern, "Image (JPG, PNG, BMP, PPM, GIF)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_file_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
g_free( open_file_name );
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string openFolderDialog(const std::string &label, const std::string &path)
|
||||
{
|
||||
std::string foldername = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_folder_name;
|
||||
open_folder_name = tinyfd_selectFolderDialog(label.c_str(), startpath.c_str());
|
||||
|
||||
if (open_folder_name)
|
||||
foldername = std::string(open_folder_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return foldername;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Select", GTK_RESPONSE_ACCEPT, NULL );
|
||||
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
char *open_folder_name = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
|
||||
if (open_folder_name)
|
||||
foldername = std::string(open_folder_name);
|
||||
g_free( open_folder_name );
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
return foldername;
|
||||
}
|
||||
|
||||
|
||||
std::list<std::string> selectImagesFileDialog(const std::string &label,const std::string &path)
|
||||
{
|
||||
std::list<std::string> files;
|
||||
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[6] = { "*.jpg", "*.png", "*.tif" };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_names;
|
||||
open_file_names = tinyfd_openFileDialog(label.c_str(), startpath.c_str(), 3, open_pattern, "Images (JPG, PNG, TIF)", 1);
|
||||
|
||||
if (open_file_names) {
|
||||
|
||||
const std::string& str (open_file_names);
|
||||
const std::string& delimiters = "|";
|
||||
// Skip delimiters at beginning.
|
||||
std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
|
||||
|
||||
// Find first non-delimiter.
|
||||
std::string::size_type pos = str.find_first_of(delimiters, lastPos);
|
||||
|
||||
while (std::string::npos != pos || std::string::npos != lastPos) {
|
||||
// Found a token, add it to the vector.
|
||||
files.push_back(str.substr(lastPos, pos - lastPos));
|
||||
|
||||
// Skip delimiters.
|
||||
lastPos = str.find_first_not_of(delimiters, pos);
|
||||
|
||||
// Find next non-delimiter.
|
||||
pos = str.find_first_of(delimiters, lastPos);
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
if (!gtk_init()) {
|
||||
DialogToolkit::ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return files;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( label.c_str(), NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 3, open_pattern, "Images (JPG, PNG, TIF)");
|
||||
add_filter_any_file_dialog(dialog);
|
||||
|
||||
// multiple files
|
||||
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), true );
|
||||
|
||||
// Set the default path
|
||||
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() );
|
||||
|
||||
// ensure front and centered
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
if (window_x > 0 && window_y > 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), window_x, window_y);
|
||||
|
||||
// display and get filename
|
||||
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
|
||||
|
||||
GSList *open_file_names = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
|
||||
|
||||
while (open_file_names) {
|
||||
files.push_back( (char *) open_file_names->data );
|
||||
open_file_names = open_file_names->next;
|
||||
// g_free( open_file_names->data );
|
||||
}
|
||||
|
||||
g_slist_free( open_file_names );
|
||||
}
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy(dialog);
|
||||
wait_for_event();
|
||||
#endif
|
||||
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void DialogToolkit::ErrorDialog(const char* message)
|
||||
{
|
||||
#if USE_TINYFILEDIALOG
|
||||
tinyfd_messageBox( APP_TITLE, message, "ok", "error", 0);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_message_dialog_new( NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
||||
"Error: %s", message);
|
||||
|
||||
gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE );
|
||||
static int x = 0, y = 0;
|
||||
if (x != 0)
|
||||
gtk_window_move( GTK_WINDOW(dialog), x, y);
|
||||
|
||||
// show
|
||||
gtk_dialog_run( GTK_DIALOG(dialog) );
|
||||
|
||||
// remember position
|
||||
gtk_window_get_position( GTK_WINDOW(dialog), &x, &y);
|
||||
|
||||
// done
|
||||
gtk_widget_destroy( dialog );
|
||||
wait_for_event();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
90
DialogToolkit.h
Normal file
90
DialogToolkit.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef DIALOGTOOLKIT_H
|
||||
#define DIALOGTOOLKIT_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <future>
|
||||
|
||||
#define VIMIX_FILES_PATTERN "*.mix"
|
||||
#define MEDIA_FILES_PATTERN "*.mix", "*.mp4", "*.mpg", "*.mpeg", "*.m2v", "*.m4v", "*.avi", "*.mov",\
|
||||
"*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg",\
|
||||
"*.flv", "*.hevc", "*.asf", "*.jpg", "*.png", "*.gif",\
|
||||
"*.tif", "*.tiff", "*.webp", "*.bmp", "*.ppm", "*.svg,"
|
||||
#define IMAGES_FILES_PATTERN "*.jpg", "*.png", "*.bmp", "*.ppm", "*.gif"
|
||||
|
||||
namespace DialogToolkit
|
||||
{
|
||||
|
||||
void ErrorDialog(const char* message);
|
||||
|
||||
class FileDialog
|
||||
{
|
||||
protected:
|
||||
std::string id_;
|
||||
std::string directory_;
|
||||
std::string path_;
|
||||
std::vector< std::future<std::string> >promises_;
|
||||
static bool busy_;
|
||||
|
||||
public:
|
||||
FileDialog(const std::string &name);
|
||||
|
||||
virtual void open() = 0;
|
||||
virtual bool closed();
|
||||
inline std::string path() const { return path_; }
|
||||
|
||||
static bool busy() { return busy_; }
|
||||
};
|
||||
|
||||
class OpenImageDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenImageDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenSessionDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenSessionDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenMediaDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenMediaDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class SaveSessionDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
SaveSessionDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class OpenFolderDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenFolderDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open();
|
||||
};
|
||||
|
||||
class MultipleImagesDialog : public FileDialog
|
||||
{
|
||||
std::list<std::string> pathlist_;
|
||||
std::vector< std::future< std::list<std::string> > > promisedlist_;
|
||||
public:
|
||||
MultipleImagesDialog(const std::string &name) : FileDialog(name) {}
|
||||
void open() override;
|
||||
bool closed() override;
|
||||
inline std::list<std::string> images() const { return pathlist_; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // DIALOGTOOLKIT_H
|
||||
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
@@ -8,14 +28,19 @@
|
||||
|
||||
DrawVisitor::DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force): force_(force)
|
||||
{
|
||||
target_ = nodetodraw;
|
||||
targets_.push_back(nodetodraw);
|
||||
modelview_ = glm::identity<glm::mat4>();
|
||||
projection_ = projection;
|
||||
done_ = false;
|
||||
num_duplicat_ = 1;
|
||||
transform_duplicat_ = glm::identity<glm::mat4>();
|
||||
}
|
||||
|
||||
DrawVisitor::DrawVisitor(const std::vector<Node *> &nodestodraw, glm::mat4 projection, bool force):
|
||||
modelview_(glm::identity<glm::mat4>()), projection_(projection), targets_(nodestodraw), force_(force),
|
||||
num_duplicat_(1), transform_duplicat_(glm::identity<glm::mat4>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void DrawVisitor::loop(int num, glm::mat4 transform)
|
||||
{
|
||||
@@ -25,19 +50,30 @@ void DrawVisitor::loop(int num, glm::mat4 transform)
|
||||
|
||||
void DrawVisitor::visit(Node &n)
|
||||
{
|
||||
// draw the target
|
||||
if ( target_ && n.id() == target_->id()) {
|
||||
// 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_;
|
||||
}
|
||||
|
||||
done_ = true;
|
||||
}
|
||||
|
||||
if (done_) return;
|
||||
// restore visibility
|
||||
n.visible_ = v;
|
||||
|
||||
if (targets_.empty()) return;
|
||||
|
||||
// update transform
|
||||
modelview_ *= n.transform_;
|
||||
@@ -47,11 +83,11 @@ void DrawVisitor::visit(Node &n)
|
||||
void DrawVisitor::visit(Group &n)
|
||||
{
|
||||
// no need to traverse deeper if this node was drawn already
|
||||
if (done_) return;
|
||||
if (targets_.empty()) return;
|
||||
|
||||
// traverse children
|
||||
glm::mat4 mv = modelview_;
|
||||
for (NodeSet::iterator node = n.begin(); !done_ && node != n.end(); node++) {
|
||||
for (NodeSet::iterator node = n.begin(); !targets_.empty() && node != n.end(); ++node) {
|
||||
if ( (*node)->visible_ || force_)
|
||||
(*node)->accept(*this);
|
||||
modelview_ = mv;
|
||||
@@ -60,17 +96,22 @@ void DrawVisitor::visit(Group &n)
|
||||
|
||||
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_;
|
||||
n.activeChild()->accept(*this);
|
||||
if ( n.activeChild()->visible_ || force_)
|
||||
n.activeChild()->accept(*this);
|
||||
modelview_ = mv;
|
||||
}
|
||||
|
||||
void DrawVisitor::visit(Primitive &n)
|
||||
void DrawVisitor::visit(Primitive &)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
#ifndef DRAWVISITOR_H
|
||||
#define DRAWVISITOR_H
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include "GlmToolkit.h"
|
||||
#include "Visitor.h"
|
||||
|
||||
class DrawVisitor : public Visitor
|
||||
{
|
||||
glm::mat4 modelview_;
|
||||
glm::mat4 projection_;
|
||||
Node *target_;
|
||||
bool done_;
|
||||
std::vector<Node *> targets_;
|
||||
bool force_;
|
||||
int num_duplicat_;
|
||||
glm::mat4 transform_duplicat_;
|
||||
|
||||
public:
|
||||
DrawVisitor(Node *nodetodraw, glm::mat4 projection, bool force = false);
|
||||
DrawVisitor(const 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(Primitive& ) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
};
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
#include "FileDialog.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
/*
|
||||
* This file is part of vimix - Live video mixer
|
||||
*
|
||||
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include <stb_image.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -29,8 +42,8 @@
|
||||
#endif
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "ImGuiToolkit.h"
|
||||
#include "FileDialog.h"
|
||||
|
||||
static std::string s_fs_root(1u, PATH_SEP);
|
||||
static std::string currentFileDialog;
|
||||
@@ -184,7 +197,7 @@ inline PathStruct ParsePathFileName(const std::string& vPathFileName)
|
||||
return res;
|
||||
}
|
||||
|
||||
inline void AppendToBuffer(char* vBuffer, size_t vBufferLen, std::string vStr)
|
||||
inline void AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string &vStr)
|
||||
{
|
||||
std::string st = vStr;
|
||||
size_t len = vBufferLen - 1u;
|
||||
@@ -259,8 +272,6 @@ static bool stringComparator(const FileInfoStruct& a, const FileInfoStruct& b)
|
||||
void FileDialog::ScanDir(const std::string& vPath)
|
||||
{
|
||||
struct dirent **files = NULL;
|
||||
int i = 0;
|
||||
int n = 0;
|
||||
std::string path = vPath;
|
||||
|
||||
#if defined(LINUX) or defined(APPLE)
|
||||
@@ -286,12 +297,12 @@ void FileDialog::ScanDir(const std::string& vPath)
|
||||
path += PATH_SEP;
|
||||
}
|
||||
#endif
|
||||
n = scandir(path.c_str(), &files, NULL, alphaSort);
|
||||
int n = scandir(path.c_str(), &files, NULL, alphaSort);
|
||||
if (n > 0)
|
||||
{
|
||||
m_FileList.clear();
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
struct dirent *ent = files[i];
|
||||
|
||||
@@ -334,7 +345,7 @@ void FileDialog::ScanDir(const std::string& vPath)
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
free(files[i]);
|
||||
}
|
||||
@@ -440,7 +451,7 @@ void FileDialog::ComposeNewPath(std::vector<std::string>::iterator vIter)
|
||||
break;
|
||||
}
|
||||
|
||||
vIter--;
|
||||
--vIter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,9 +720,9 @@ bool FileDialog::Render(const std::string& vKey, ImVec2 geometry)
|
||||
SetPath("."); // Go Home
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
bool drivesClick = false;
|
||||
|
||||
#ifdef WIN32
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Drives"))
|
||||
@@ -871,10 +882,12 @@ bool FileDialog::Render(const std::string& vKey, ImVec2 geometry)
|
||||
SetPath(m_CurrentPath);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
if (drivesClick == true)
|
||||
{
|
||||
GetDrives();
|
||||
}
|
||||
#endif
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::PopFont();
|
||||
@@ -1019,12 +1032,12 @@ std::string FileDialog::GetUserString()
|
||||
return dlg_userString;
|
||||
}
|
||||
|
||||
void FileDialog::SetFilterColor(std::string vFilter, ImVec4 vColor)
|
||||
void FileDialog::SetFilterColor(const std::string &vFilter, ImVec4 vColor)
|
||||
{
|
||||
m_FilterColor[vFilter] = vColor;
|
||||
}
|
||||
|
||||
bool FileDialog::GetFilterColor(std::string vFilter, ImVec4 *vColor)
|
||||
bool FileDialog::GetFilterColor(const std::string &vFilter, ImVec4 *vColor)
|
||||
{
|
||||
if (vColor)
|
||||
{
|
||||
@@ -1060,7 +1073,7 @@ inline void InfosPane(std::string vFilter, bool *vCantContinue)
|
||||
*vCantContinue = canValidateDialog;
|
||||
}
|
||||
|
||||
inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
|
||||
inline void TextInfosPane(const std::string &vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
|
||||
{
|
||||
ImGui::TextColored(ImVec4(0, 1, 1, 1), "Text");
|
||||
|
||||
@@ -1097,7 +1110,7 @@ inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantC
|
||||
*vCantContinue = text.size() > 0;
|
||||
}
|
||||
|
||||
inline void ImageInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
|
||||
inline void ImageInfosPane(const std::string &vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
|
||||
{
|
||||
// opengl texture
|
||||
static GLuint tex = 0;
|
||||
|
||||
15
FileDialog.h
15
FileDialog.h
@@ -6,15 +6,8 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include <future>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
#define MAX_FILE_DIALOG_NAME_BUFFER 1024
|
||||
|
||||
@@ -78,8 +71,8 @@ public:
|
||||
protected:
|
||||
|
||||
FileDialog(); // Prevent construction
|
||||
FileDialog(const FileDialog&) {}; // Prevent construction by copying
|
||||
FileDialog& operator =(const FileDialog&) { return *this; }; // Prevent assignment
|
||||
FileDialog(const FileDialog&) = delete; // Prevent construction by copying
|
||||
FileDialog& operator =(const FileDialog&) = delete; // Prevent assignment
|
||||
~FileDialog(); // Prevent unwanted destruction
|
||||
|
||||
public:
|
||||
@@ -102,8 +95,8 @@ public:
|
||||
std::string GetCurrentFilter();
|
||||
std::string GetUserString();
|
||||
|
||||
void SetFilterColor(std::string vFilter, ImVec4 vColor);
|
||||
bool GetFilterColor(std::string vFilter, ImVec4 *vColor);
|
||||
void SetFilterColor(const std::string &vFilter, ImVec4 vColor);
|
||||
bool GetFilterColor(const std::string &vFilter, ImVec4 *vColor);
|
||||
void ClearFilterColor();
|
||||
|
||||
private:
|
||||
|
||||
272
FrameBuffer.cpp
272
FrameBuffer.cpp
@@ -1,3 +1,24 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "FrameBuffer.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
@@ -7,28 +28,56 @@
|
||||
#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", "4K" };
|
||||
float FrameBuffer::resolution_height[4] = { 720.f, 1080.f, 1440.f, 2160.f };
|
||||
const char* FrameBuffer::resolution_name[5] = { "720", "1080", "1200", "1440", "2160" };
|
||||
float FrameBuffer::resolution_height[5] = { 720.f, 1080.f, 1200.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;
|
||||
width -= (int)width % 2;
|
||||
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);
|
||||
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, use_alpha_ ? 0.f : 1.f);
|
||||
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):
|
||||
@@ -36,8 +85,8 @@ FrameBuffer::FrameBuffer(uint width, uint height, bool useAlpha, bool multiSampl
|
||||
use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
|
||||
{
|
||||
attrib_.viewport = glm::ivec2(width, height);
|
||||
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, use_alpha_ ? 0.f : 1.f);
|
||||
setProjectionArea(glm::vec2(1.f, 1.f));
|
||||
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, 0.f);
|
||||
}
|
||||
|
||||
void FrameBuffer::init()
|
||||
@@ -102,6 +151,12 @@ 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_);
|
||||
}
|
||||
|
||||
|
||||
@@ -121,20 +176,14 @@ float FrameBuffer::aspectRatio() const
|
||||
|
||||
std::string FrameBuffer::info() const
|
||||
{
|
||||
std::string s = "";
|
||||
glm::ivec2 p = FrameBuffer::getParametersFromResolution(resolution());
|
||||
std::ostringstream info;
|
||||
|
||||
static int num_ar = ((int)(sizeof(FrameBuffer::aspect_ratio_size) / sizeof(*FrameBuffer::aspect_ratio_size)));
|
||||
float myratio = aspectRatio();
|
||||
for(int i= 0; i < num_ar; i++) {
|
||||
if ( myratio - (FrameBuffer::aspect_ratio_size[i].x / FrameBuffer::aspect_ratio_size[i].y ) < EPSILON)
|
||||
{
|
||||
s += std::string( FrameBuffer::aspect_ratio_name[i]) + ", ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
info << attrib_.viewport.x << "x" << attrib_.viewport.y;
|
||||
if (p.x > -1)
|
||||
info << "px, " << FrameBuffer::aspect_ratio_name[p.x];
|
||||
|
||||
s += std::to_string(width()) + "x" + std::to_string(height()) + " px";
|
||||
return s;
|
||||
return info.str();
|
||||
}
|
||||
|
||||
glm::vec3 FrameBuffer::resolution() const
|
||||
@@ -142,7 +191,32 @@ glm::vec3 FrameBuffer::resolution() const
|
||||
return glm::vec3(attrib_.viewport.x, attrib_.viewport.y, 0.f);
|
||||
}
|
||||
|
||||
void FrameBuffer::begin()
|
||||
void FrameBuffer::resize(int width, int height)
|
||||
{
|
||||
if (framebufferid_) {
|
||||
if (attrib_.viewport.x != width || attrib_.viewport.y != height)
|
||||
{
|
||||
// de-init
|
||||
glDeleteFramebuffers(1, &framebufferid_);
|
||||
framebufferid_ = 0;
|
||||
|
||||
if (intermediate_framebufferid_)
|
||||
glDeleteFramebuffers(1, &intermediate_framebufferid_);
|
||||
intermediate_framebufferid_ = 0;
|
||||
if (textureid_)
|
||||
glDeleteTextures(1, &textureid_);
|
||||
textureid_ = 0;
|
||||
if (intermediate_textureid_)
|
||||
glDeleteTextures(1, &intermediate_textureid_);
|
||||
intermediate_textureid_ = 0;
|
||||
|
||||
// change resolution
|
||||
attrib_.viewport = glm::ivec2(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameBuffer::begin(bool clear)
|
||||
{
|
||||
if (!framebufferid_)
|
||||
init();
|
||||
@@ -151,7 +225,8 @@ void FrameBuffer::begin()
|
||||
|
||||
Rendering::manager().pushAttrib(attrib_);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
if (clear)
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void FrameBuffer::end()
|
||||
@@ -176,7 +251,7 @@ void FrameBuffer::release()
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void FrameBuffer::readPixels()
|
||||
void FrameBuffer::readPixels(uint8_t *target_data)
|
||||
{
|
||||
if (!framebufferid_)
|
||||
return;
|
||||
@@ -191,21 +266,28 @@ void FrameBuffer::readPixels()
|
||||
else
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
|
||||
glReadPixels(0, 0, attrib_.viewport.x, attrib_.viewport.y, (use_alpha_? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, 0);
|
||||
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 *other)
|
||||
bool FrameBuffer::blit(FrameBuffer *destination)
|
||||
{
|
||||
if (!framebufferid_ || !other || !other->framebufferid_)
|
||||
if (!framebufferid_ || !destination || (use_alpha_ != destination->use_alpha_) )
|
||||
return false;
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, other->framebufferid_);
|
||||
if (!destination->framebufferid_)
|
||||
destination->init();
|
||||
|
||||
if (use_multi_sampling_)
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, intermediate_framebufferid_);
|
||||
else
|
||||
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, other->width(), other->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
0, 0, destination->width(), destination->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -240,7 +322,7 @@ void FrameBuffer::checkFramebufferStatus()
|
||||
break;
|
||||
case GL_FRAMEBUFFER_COMPLETE:
|
||||
#ifndef NDEBUG
|
||||
Log::Info("Framebuffer created %d x %d.", width(), height());
|
||||
g_print("Framebuffer created %d x %d\n", width(), height());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -252,16 +334,142 @@ glm::mat4 FrameBuffer::projection() const
|
||||
return projection_;
|
||||
}
|
||||
|
||||
|
||||
glm::vec2 FrameBuffer::projectionArea() const
|
||||
{
|
||||
return projection_crop_;
|
||||
return projection_area_;
|
||||
}
|
||||
|
||||
void FrameBuffer::setProjectionArea(glm::vec2 c)
|
||||
{
|
||||
projection_crop_.x = CLAMP(c.x, 0.1f, 1.f);
|
||||
projection_crop_.y = CLAMP(c.y, 0.1f, 1.f);
|
||||
projection_ = glm::ortho(-projection_crop_.x, projection_crop_.x, projection_crop_.y, -projection_crop_.y, -1.f, 1.f);
|
||||
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(const std::string &filename) :
|
||||
rgb(nullptr), width(0), height(0)
|
||||
{
|
||||
int c = 0;
|
||||
if (!filename.empty())
|
||||
rgb = stbi_load(filename.c_str(), &width, &height, &c, 3);
|
||||
}
|
||||
|
||||
FrameBufferImage::~FrameBufferImage()
|
||||
{
|
||||
if (rgb!=nullptr)
|
||||
delete rgb;
|
||||
}
|
||||
|
||||
FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg() const
|
||||
{
|
||||
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;
|
||||
|
||||
// only compatible for RGB FrameBuffers
|
||||
if (use_alpha_ || use_multi_sampling_)
|
||||
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();
|
||||
|
||||
// only compatible for RGB FrameBuffers
|
||||
if (use_alpha_ || use_multi_sampling_)
|
||||
return false;
|
||||
|
||||
// invalid image
|
||||
if ( image == nullptr ||
|
||||
image->rgb==nullptr ||
|
||||
image->width < 1 ||
|
||||
image->height < 1 )
|
||||
return false;
|
||||
|
||||
// is it same size ?
|
||||
if (image->width == attrib_.viewport.x && image->height == attrib_.viewport.y ) {
|
||||
// directly 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);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
else {
|
||||
uint textureimage, framebufferimage;
|
||||
// generate texture
|
||||
glGenTextures(1, &textureimage);
|
||||
glBindTexture(GL_TEXTURE_2D, textureimage);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, image->width, image->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
// create a framebuffer object
|
||||
glGenFramebuffers(1, &framebufferimage);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebufferimage);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureimage, 0);
|
||||
|
||||
// blit to the frame buffer object with interpolation
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferimage);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebufferid_);
|
||||
glBlitFramebuffer(0, 0, image->width, image->height,
|
||||
0, 0, attrib_.viewport.x, attrib_.viewport.y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
// cleanup
|
||||
glDeleteFramebuffers(1, &framebufferimage);
|
||||
glDeleteTextures(1, &textureimage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,35 +1,66 @@
|
||||
#ifndef FRAMEBUFFER_H
|
||||
#define FRAMEBUFFER_H
|
||||
|
||||
#include "Scene.h"
|
||||
#include "RenderingManager.h"
|
||||
|
||||
#define FBI_JPEG_QUALITY 90
|
||||
|
||||
/**
|
||||
* @brief The FrameBufferImage class stores an RGB image in RAM
|
||||
* Direct access to rgb array, and exchange format to JPEG in RAM
|
||||
*/
|
||||
class FrameBufferImage
|
||||
{
|
||||
public:
|
||||
uint8_t *rgb;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
struct jpegBuffer {
|
||||
unsigned char *buffer = nullptr;
|
||||
uint len = 0;
|
||||
};
|
||||
jpegBuffer getJpeg() const;
|
||||
|
||||
FrameBufferImage(int w, int h);
|
||||
FrameBufferImage(jpegBuffer jpgimg);
|
||||
FrameBufferImage(const std::string &filename);
|
||||
// non assignable class
|
||||
FrameBufferImage(FrameBufferImage const&) = delete;
|
||||
FrameBufferImage& operator=(FrameBufferImage const&) = delete;
|
||||
~FrameBufferImage();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The FrameBuffer class holds an OpenGL Frame Buffer Object.
|
||||
*/
|
||||
class FrameBuffer {
|
||||
|
||||
public:
|
||||
// size descriptions
|
||||
static const char* aspect_ratio_name[5];
|
||||
static glm::vec2 aspect_ratio_size[5];
|
||||
static const char* resolution_name[4];
|
||||
static float resolution_height[4];
|
||||
static const char* resolution_name[5];
|
||||
static float resolution_height[5];
|
||||
static glm::vec3 getResolutionFromParameters(int ar, int h);
|
||||
static glm::ivec2 getParametersFromResolution(glm::vec3 res);
|
||||
// unbind any framebuffer object
|
||||
static void release();
|
||||
|
||||
FrameBuffer(glm::vec3 resolution, bool useAlpha = false, bool multiSampling = false);
|
||||
FrameBuffer(uint width, uint height, bool useAlpha = false, bool multiSampling = false);
|
||||
FrameBuffer(FrameBuffer const&) = delete;
|
||||
~FrameBuffer();
|
||||
|
||||
// Bind & push attribs to prepare draw
|
||||
void begin();
|
||||
void begin(bool clear = true);
|
||||
// pop attrib and unbind to end draw
|
||||
void end();
|
||||
|
||||
// blit copy to another, returns true on success
|
||||
bool blit(FrameBuffer *other);
|
||||
bool blit(FrameBuffer *destination);
|
||||
// bind the FrameBuffer in READ and perform glReadPixels
|
||||
// return the size of the buffer
|
||||
void readPixels();
|
||||
// (to be used after preparing a target PBO)
|
||||
void readPixels(uint8_t* target_data = 0);
|
||||
|
||||
// clear color
|
||||
inline void setClearColor(glm::vec4 color) { attrib_.clear_color = color; }
|
||||
@@ -39,31 +70,38 @@ public:
|
||||
inline uint width() const { return attrib_.viewport.x; }
|
||||
inline uint height() const { return attrib_.viewport.y; }
|
||||
glm::vec3 resolution() const;
|
||||
void resize(int width, int height);
|
||||
float aspectRatio() const;
|
||||
std::string info() const;
|
||||
|
||||
// projection and crop
|
||||
// projection area (crop)
|
||||
glm::mat4 projection() const;
|
||||
glm::vec2 projectionArea() const;
|
||||
void setProjectionArea(glm::vec2 c);
|
||||
|
||||
// internal pixel format
|
||||
inline uint opengl_id() const { return framebufferid_; }
|
||||
inline bool use_alpha() const { return use_alpha_; }
|
||||
inline bool use_multisampling() const { return use_multi_sampling_; }
|
||||
|
||||
// index for texturing
|
||||
uint texture() const;
|
||||
|
||||
// get and fill image
|
||||
FrameBufferImage *image();
|
||||
bool fill(FrameBufferImage *image);
|
||||
|
||||
private:
|
||||
void init();
|
||||
void checkFramebufferStatus();
|
||||
|
||||
RenderingAttrib attrib_;
|
||||
glm::mat4 projection_;
|
||||
glm::vec2 projection_crop_;
|
||||
glm::vec2 projection_area_;
|
||||
uint textureid_, intermediate_textureid_;
|
||||
uint framebufferid_, intermediate_framebufferid_;
|
||||
bool use_alpha_, use_multi_sampling_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
330
FrameGrabber.cpp
330
FrameGrabber.cpp
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
@@ -10,13 +29,14 @@
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "BaseToolkit.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)
|
||||
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(NULL)
|
||||
{
|
||||
pbo_[0] = 0;
|
||||
pbo_[1] = 0;
|
||||
@@ -28,10 +48,10 @@ FrameGrabbing::~FrameGrabbing()
|
||||
clearAll();
|
||||
|
||||
// cleanup
|
||||
if (caps_!=nullptr)
|
||||
if (caps_)
|
||||
gst_caps_unref (caps_);
|
||||
if (pbo_[0])
|
||||
glDeleteBuffers(2, pbo_);
|
||||
// if (pbo_[0] > 0) // automatically deleted at shutdown
|
||||
// glDeleteBuffers(2, pbo_);
|
||||
}
|
||||
|
||||
void FrameGrabbing::add(FrameGrabber *rec)
|
||||
@@ -40,12 +60,31 @@ void FrameGrabbing::add(FrameGrabber *rec)
|
||||
grabbers_.push_back(rec);
|
||||
}
|
||||
|
||||
void FrameGrabbing::chain(FrameGrabber *rec, FrameGrabber *next_rec)
|
||||
{
|
||||
if (rec != nullptr && next_rec != nullptr)
|
||||
{
|
||||
// add grabber if not yet
|
||||
if ( std::find(grabbers_.begin(), grabbers_.end(), rec) == grabbers_.end() )
|
||||
grabbers_.push_back(rec);
|
||||
|
||||
grabbers_chain_[next_rec] = rec;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameGrabbing::verify(FrameGrabber **rec)
|
||||
{
|
||||
if ( std::find(grabbers_.begin(), grabbers_.end(), *rec) == grabbers_.end() &&
|
||||
grabbers_chain_.find(*rec) == grabbers_chain_.end() )
|
||||
*rec = nullptr;
|
||||
}
|
||||
|
||||
FrameGrabber *FrameGrabbing::front()
|
||||
{
|
||||
if (grabbers_.empty())
|
||||
return nullptr;
|
||||
else
|
||||
return grabbers_.front();
|
||||
|
||||
return grabbers_.front();
|
||||
}
|
||||
|
||||
struct fgId: public std::unary_function<FrameGrabber*, bool>
|
||||
@@ -53,7 +92,7 @@ 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) { }
|
||||
explicit fgId(uint64_t id) : _id(id) { }
|
||||
private:
|
||||
uint64_t _id;
|
||||
};
|
||||
@@ -73,7 +112,7 @@ FrameGrabber *FrameGrabbing::get(uint64_t id)
|
||||
void FrameGrabbing::stopAll()
|
||||
{
|
||||
std::list<FrameGrabber *>::iterator iter;
|
||||
for (iter=grabbers_.begin(); iter != grabbers_.end(); iter++ )
|
||||
for (iter=grabbers_.begin(); iter != grabbers_.end(); ++iter )
|
||||
(*iter)->stop();
|
||||
}
|
||||
|
||||
@@ -84,13 +123,17 @@ void FrameGrabbing::clearAll()
|
||||
{
|
||||
FrameGrabber *rec = *iter;
|
||||
rec->stop();
|
||||
iter = grabbers_.erase(iter);
|
||||
delete rec;
|
||||
if (rec->finished()) {
|
||||
iter = grabbers_.erase(iter);
|
||||
delete rec;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
|
||||
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer)
|
||||
{
|
||||
if (frame_buffer == nullptr)
|
||||
return;
|
||||
@@ -121,13 +164,12 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
|
||||
pbo_next_index_ = 0;
|
||||
|
||||
// new caps
|
||||
if (caps_!=nullptr)
|
||||
if (caps_)
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -145,6 +187,7 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
|
||||
#else
|
||||
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
#endif
|
||||
|
||||
// update case ; alternating indices
|
||||
@@ -179,21 +222,43 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
|
||||
pbo_index_ = (pbo_index_ + 1) % 2;
|
||||
|
||||
// a frame was successfully grabbed
|
||||
if (buffer != nullptr) {
|
||||
if ( buffer != nullptr && gst_buffer_get_size(buffer) > 0) {
|
||||
|
||||
// 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);
|
||||
rec->addFrame(buffer, caps_);
|
||||
|
||||
// remove finished recorders
|
||||
if (rec->finished()) {
|
||||
iter = grabbers_.erase(iter);
|
||||
delete rec;
|
||||
}
|
||||
else
|
||||
iter++;
|
||||
++iter;
|
||||
}
|
||||
|
||||
// manage the list of chainned recorder
|
||||
std::map<FrameGrabber *, FrameGrabber *>::iterator chain = grabbers_chain_.begin();
|
||||
while (chain != grabbers_chain_.end())
|
||||
{
|
||||
// update frame grabber of chain list
|
||||
chain->first->addFrame(buffer, caps_);
|
||||
|
||||
// if the chained recorder is now active
|
||||
if (chain->first->active_ && chain->first->accept_buffer_){
|
||||
// add it to main grabbers,
|
||||
grabbers_.push_back(chain->first);
|
||||
// stop the replaced grabber
|
||||
chain->second->stop();
|
||||
// loop in chain list: done with this chain
|
||||
chain = grabbers_chain_.erase(chain);
|
||||
}
|
||||
else
|
||||
// loop in chain list
|
||||
++chain;
|
||||
}
|
||||
|
||||
// unref / free the frame
|
||||
@@ -206,14 +271,14 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
|
||||
|
||||
|
||||
|
||||
FrameGrabber::FrameGrabber(): finished_(false), active_(false), accept_buffer_(false),
|
||||
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timestamp_(0)
|
||||
FrameGrabber::FrameGrabber(): finished_(false), initialized_(false), active_(false), endofstream_(false), accept_buffer_(false), buffering_full_(false),
|
||||
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timer_(nullptr), timer_firstframe_(0),
|
||||
timestamp_(0), duration_(0), frame_count_(0), buffering_size_(MIN_BUFFER_SIZE), timestamp_on_clock_(false)
|
||||
{
|
||||
// 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_;
|
||||
id_ = BaseToolkit::uniqueId();
|
||||
// configure default parameter
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, DEFAULT_GRABBER_FPS); // 25 FPS by default
|
||||
}
|
||||
|
||||
FrameGrabber::~FrameGrabber()
|
||||
@@ -222,8 +287,13 @@ FrameGrabber::~FrameGrabber()
|
||||
gst_object_unref (src_);
|
||||
if (caps_ != nullptr)
|
||||
gst_caps_unref (caps_);
|
||||
if (timer_)
|
||||
gst_object_unref (timer_);
|
||||
|
||||
if (pipeline_ != nullptr) {
|
||||
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
GstState state = GST_STATE_NULL;
|
||||
gst_element_set_state (pipeline_, state);
|
||||
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
|
||||
gst_object_unref (pipeline_);
|
||||
}
|
||||
}
|
||||
@@ -241,24 +311,28 @@ bool FrameGrabber::busy() const
|
||||
return false;
|
||||
}
|
||||
|
||||
double FrameGrabber::duration() const
|
||||
uint64_t FrameGrabber::duration() const
|
||||
{
|
||||
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
|
||||
return GST_TIME_AS_MSECONDS(duration_);
|
||||
}
|
||||
|
||||
void FrameGrabber::stop ()
|
||||
{
|
||||
// send end of stream
|
||||
gst_app_src_end_of_stream (src_);
|
||||
// TODO if not initialized wait for initializer
|
||||
|
||||
// stop recording
|
||||
active_ = false;
|
||||
|
||||
// send end of stream
|
||||
gst_app_src_end_of_stream (src_);
|
||||
}
|
||||
|
||||
std::string FrameGrabber::info() const
|
||||
{
|
||||
if (!initialized_)
|
||||
return "Initializing";
|
||||
if (active_)
|
||||
return GstToolkit::time_to_string(timestamp_);
|
||||
return GstToolkit::time_to_string(duration_);
|
||||
else
|
||||
return "Inactive";
|
||||
}
|
||||
@@ -275,83 +349,163 @@ void FrameGrabber::callback_need_data (GstAppSrc *, guint , gpointer p)
|
||||
void FrameGrabber::callback_enough_data (GstAppSrc *, gpointer p)
|
||||
{
|
||||
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
|
||||
if (grabber)
|
||||
if (grabber) {
|
||||
grabber->accept_buffer_ = false;
|
||||
#ifndef NDEBUG
|
||||
Log::Info("Frame capture : Buffer full");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
|
||||
GstPadProbeReturn FrameGrabber::callback_event_probe(GstPad *, GstPadProbeInfo * info, gpointer p)
|
||||
{
|
||||
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
|
||||
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS)
|
||||
{
|
||||
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
|
||||
if (grabber)
|
||||
grabber->endofstream_ = true;
|
||||
}
|
||||
|
||||
return GST_PAD_PROBE_OK;
|
||||
}
|
||||
|
||||
|
||||
std::string FrameGrabber::initialize(FrameGrabber *rec, GstCaps *caps)
|
||||
{
|
||||
return rec->init(caps);
|
||||
}
|
||||
|
||||
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps)
|
||||
{
|
||||
// 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.");
|
||||
if (pipeline_ == nullptr) {
|
||||
initializer_ = std::async( FrameGrabber::initialize, this, caps);
|
||||
}
|
||||
|
||||
// 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_)
|
||||
// initializer ongoing in separate thread
|
||||
if (initializer_.valid()) {
|
||||
// try to get info from initializer
|
||||
if (initializer_.wait_for( std::chrono::milliseconds(4) ) == std::future_status::ready )
|
||||
{
|
||||
// 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.");
|
||||
// done initialization
|
||||
std::string msg = initializer_.get();
|
||||
|
||||
// if initialization succeeded
|
||||
if (initialized_) {
|
||||
// attach EOS detector
|
||||
GstPad *pad = gst_element_get_static_pad (gst_bin_get_by_name (GST_BIN (pipeline_), "sink"), "sink");
|
||||
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, FrameGrabber::callback_event_probe, this, NULL);
|
||||
gst_object_unref (pad);
|
||||
// start recording
|
||||
active_ = true;
|
||||
// inform
|
||||
Log::Info("%s", msg.c_str());
|
||||
}
|
||||
// else show warning
|
||||
else {
|
||||
// end frame grabber
|
||||
finished_ = true;
|
||||
// inform
|
||||
Log::Warning("%s", msg.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (finished_)
|
||||
terminate();
|
||||
// stop if an incompatilble frame buffer given after initialization
|
||||
if (initialized_ && !gst_caps_is_subset( caps_, caps ))
|
||||
{
|
||||
stop();
|
||||
Log::Warning("Frame capture interrupted because the resolution changed.");
|
||||
}
|
||||
|
||||
// store a frame if recording is active and if the encoder accepts data
|
||||
if (active_)
|
||||
{
|
||||
if (accept_buffer_) {
|
||||
GstClockTime t = 0;
|
||||
|
||||
// initialize timer on first occurence
|
||||
if (timer_ == nullptr) {
|
||||
timer_ = gst_pipeline_get_clock ( GST_PIPELINE(pipeline_) );
|
||||
timer_firstframe_ = gst_clock_get_time(timer_);
|
||||
}
|
||||
else
|
||||
// time since timer starts (first frame registered)
|
||||
t = gst_clock_get_time(timer_) - timer_firstframe_;
|
||||
|
||||
// if time is zero (first frame) or if delta time is passed one frame duration (with a margin)
|
||||
if ( t == 0 || (t - duration_) > (frame_duration_ - 3000) ) {
|
||||
|
||||
// count frames
|
||||
frame_count_++;
|
||||
|
||||
// set duration to an exact multiples of frame duration
|
||||
duration_ = ( t / frame_duration_) * frame_duration_;
|
||||
|
||||
if (timestamp_on_clock_)
|
||||
// automatic frame presentation time stamp
|
||||
// set time to actual time
|
||||
// & round t to a multiples of frame duration
|
||||
timestamp_ = duration_;
|
||||
else {
|
||||
// monotonic time increment to keep fixed FPS
|
||||
timestamp_ += frame_duration_;
|
||||
// force frame presentation time stamp
|
||||
buffer->pts = timestamp_;
|
||||
// set frame duration
|
||||
buffer->duration = frame_duration_;
|
||||
}
|
||||
|
||||
// when buffering is (almost) full, refuse buffer 1 frame over 2
|
||||
if (buffering_full_)
|
||||
accept_buffer_ = frame_count_%2;
|
||||
else
|
||||
{
|
||||
// enter buffering_full_ mode if the space left in buffering is for only few frames
|
||||
// (this prevents filling the buffer entirely)
|
||||
if ( buffering_size_ - gst_app_src_get_current_level_bytes(src_) < MIN_BUFFER_SIZE ) {
|
||||
#ifndef NDEBUG
|
||||
Log::Info("Frame capture : Using %s of %s Buffer.",
|
||||
BaseToolkit::byte_to_string(gst_app_src_get_current_level_bytes(src_)).c_str(),
|
||||
BaseToolkit::byte_to_string(buffering_size_).c_str());
|
||||
#endif
|
||||
buffering_full_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// increment ref counter to make sure the frame remains available
|
||||
gst_buffer_ref(buffer);
|
||||
|
||||
// push frame
|
||||
gst_app_src_push_buffer (src_, buffer);
|
||||
// NB: buffer will be unrefed by the appsrc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we received and end of stream (from callback_event_probe)
|
||||
if (endofstream_)
|
||||
{
|
||||
// try to stop properly when interrupted
|
||||
if (active_) {
|
||||
// de-activate and re-send EOS
|
||||
stop();
|
||||
// inform
|
||||
Log::Info("Frame capture : Unnexpected EOF signal (no space left on drive? File deleted?)");
|
||||
Log::Warning("Frame capture : Failed after %s.", GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str());
|
||||
}
|
||||
// terminate properly if finished
|
||||
else
|
||||
{
|
||||
terminate();
|
||||
finished_ = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,18 +2,21 @@
|
||||
#define FRAMEGRABBER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#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
|
||||
#define DEFAULT_GRABBER_FPS 30
|
||||
#define MIN_BUFFER_SIZE 33177600 // 33177600 bytes = 1 frames 4K, 9 frames 720p
|
||||
|
||||
class FrameBuffer;
|
||||
|
||||
@@ -40,36 +43,49 @@ public:
|
||||
|
||||
virtual void stop();
|
||||
virtual std::string info() const;
|
||||
virtual double duration() const;
|
||||
virtual uint64_t 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);
|
||||
virtual void addFrame(GstBuffer *buffer, GstCaps *caps);
|
||||
|
||||
// only addFrame method shall call those
|
||||
virtual void init(GstCaps *caps) = 0;
|
||||
virtual std::string init(GstCaps *caps) = 0;
|
||||
virtual void terminate() = 0;
|
||||
|
||||
// thread-safe testing termination
|
||||
std::atomic<bool> finished_;
|
||||
std::atomic<bool> initialized_;
|
||||
std::atomic<bool> active_;
|
||||
std::atomic<bool> endofstream_;
|
||||
std::atomic<bool> accept_buffer_;
|
||||
std::atomic<bool> buffering_full_;
|
||||
|
||||
// gstreamer pipeline
|
||||
GstElement *pipeline_;
|
||||
GstAppSrc *src_;
|
||||
GstCaps *caps_;
|
||||
GstClockTime timeframe_;
|
||||
|
||||
GstClock *timer_;
|
||||
GstClockTime timer_firstframe_;
|
||||
GstClockTime timestamp_;
|
||||
GstClockTime duration_;
|
||||
GstClockTime frame_duration_;
|
||||
guint64 frame_count_;
|
||||
guint64 buffering_size_;
|
||||
bool timestamp_on_clock_;
|
||||
|
||||
// async threaded initializer
|
||||
std::future<std::string> initializer_;
|
||||
static std::string initialize(FrameGrabber *rec, GstCaps *caps);
|
||||
|
||||
// gstreamer callbacks
|
||||
static void callback_need_data (GstAppSrc *, guint, gpointer user_data);
|
||||
static void callback_enough_data (GstAppSrc *, gpointer user_data);
|
||||
|
||||
static void callback_enough_data (GstAppSrc *, gpointer user_data);
|
||||
static GstPadProbeReturn callback_event_probe(GstPad *, GstPadProbeInfo *info, gpointer user_data);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -80,12 +96,12 @@ protected:
|
||||
*/
|
||||
class FrameGrabbing
|
||||
{
|
||||
friend class Session;
|
||||
friend class Mixer;
|
||||
|
||||
// Private Constructor
|
||||
FrameGrabbing();
|
||||
FrameGrabbing(FrameGrabbing const& copy); // Not Implemented
|
||||
FrameGrabbing& operator=(FrameGrabbing const& copy); // Not Implemented
|
||||
FrameGrabbing(FrameGrabbing const& copy) = delete;
|
||||
FrameGrabbing& operator=(FrameGrabbing const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
@@ -101,6 +117,8 @@ public:
|
||||
inline uint height() const { return height_; }
|
||||
|
||||
void add(FrameGrabber *rec);
|
||||
void chain(FrameGrabber *rec, FrameGrabber *new_rec);
|
||||
void verify(FrameGrabber **rec);
|
||||
FrameGrabber *front();
|
||||
FrameGrabber *get(uint64_t id);
|
||||
void stopAll();
|
||||
@@ -109,10 +127,11 @@ public:
|
||||
protected:
|
||||
|
||||
// only for friend Session
|
||||
void grabFrame(FrameBuffer *frame_buffer, float dt);
|
||||
void grabFrame(FrameBuffer *frame_buffer);
|
||||
|
||||
private:
|
||||
std::list<FrameGrabber *> grabbers_;
|
||||
std::map<FrameGrabber *, FrameGrabber *> grabbers_chain_;
|
||||
guint pbo_[2];
|
||||
guint pbo_index_;
|
||||
guint pbo_next_index_;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of vimix - Live video mixer
|
||||
*
|
||||
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Log.h"
|
||||
@@ -58,7 +77,7 @@ void GarbageVisitor::visit(Group &n)
|
||||
|
||||
// loop over members of a group
|
||||
// and stop when found
|
||||
for (NodeSet::iterator node = n.begin(); !found_ && node != n.end(); node++) {
|
||||
for (NodeSet::iterator node = n.begin(); !found_ && node != n.end(); ++node) {
|
||||
// visit the child node
|
||||
(*node)->accept(*this);
|
||||
// un-stack recursive browsing
|
||||
@@ -82,78 +101,3 @@ 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(LineCircle &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Mesh &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Frame &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void GarbageVisitor::visit (Source& s)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit (MediaSource& s)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -24,24 +24,6 @@ public:
|
||||
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(LineCircle& n) 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
|
||||
|
||||
1148
GeometryView.cpp
Normal file
1148
GeometryView.cpp
Normal file
File diff suppressed because it is too large
Load Diff
49
GeometryView.h
Normal file
49
GeometryView.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef GEOMETRYVIEW_H
|
||||
#define GEOMETRYVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class GeometryView : public View
|
||||
{
|
||||
public:
|
||||
GeometryView();
|
||||
// non assignable class
|
||||
GeometryView(GeometryView const&) = delete;
|
||||
GeometryView& operator=(GeometryView const&) = delete;
|
||||
|
||||
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
|
||||
@@ -1,21 +1,32 @@
|
||||
// Freely inspired from https://github.com/alter-rokuz/glm-aabb.git
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/random.hpp>
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <glm/gtc/random.hpp>
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
uint64_t GlmToolkit::uniqueId()
|
||||
{
|
||||
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
|
||||
// 64-bit int 18446744073709551615UL
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000000000000UL;
|
||||
}
|
||||
|
||||
glm::mat4 GlmToolkit::transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale)
|
||||
{
|
||||
@@ -27,10 +38,52 @@ glm::mat4 GlmToolkit::transform(glm::vec3 translation, glm::vec3 rotation, glm::
|
||||
return View * Model;
|
||||
}
|
||||
|
||||
void GlmToolkit::inverse_transform(glm::mat4 M, glm::vec3 &translation, glm::vec3 &rotation, glm::vec3 &scale)
|
||||
{
|
||||
// extract rotation from modelview
|
||||
glm::mat4 ctm;
|
||||
glm::vec3 rot(0.f);
|
||||
glm::vec4 vec = M * glm::vec4(1.f, 0.f, 0.f, 0.f);
|
||||
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
|
||||
rotation = rot;
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() {
|
||||
mMin = glm::vec3(1.f);
|
||||
mMax = glm::vec3(-1.f);
|
||||
// extract scaling
|
||||
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * M ;
|
||||
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
|
||||
scale = glm::vec3(vec.x, vec.y, 1.f);
|
||||
|
||||
// extract translation
|
||||
vec = M * glm::vec4(0.f, 0.f, 0.f, 1.f);
|
||||
translation = glm::vec3(vec);
|
||||
}
|
||||
|
||||
//float rewrapAngleRestricted(float angle)
|
||||
//// This function takes an angle in the range [-3*pi, 3*pi] and
|
||||
//// wraps it to the range [-pi, pi].
|
||||
//{
|
||||
// if (angle > glm::pi<float>() )
|
||||
// return angle - glm::two_pi<float>();
|
||||
// else if (angle < - glm::pi<float>())
|
||||
// return angle + glm::two_pi<float>();
|
||||
// else
|
||||
// return angle;
|
||||
//}
|
||||
|
||||
// Freely inspired from https://github.com/alter-rokuz/glm-aabb.git
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() :
|
||||
mMin(glm::vec3(1.f)), mMax(glm::vec3(-1.f))
|
||||
{
|
||||
}
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox(const GlmToolkit::AxisAlignedBoundingBox &D) :
|
||||
mMin(D.mMin), mMax(D.mMax)
|
||||
{
|
||||
}
|
||||
|
||||
void GlmToolkit::AxisAlignedBoundingBox::operator = (const GlmToolkit::AxisAlignedBoundingBox &D ) {
|
||||
mMin = D.mMin;
|
||||
mMax = D.mMax;
|
||||
}
|
||||
|
||||
void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point)
|
||||
@@ -48,7 +101,7 @@ void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point)
|
||||
|
||||
void GlmToolkit::AxisAlignedBoundingBox::extend(std::vector<glm::vec3> points)
|
||||
{
|
||||
for (auto p = points.begin(); p != points.end(); p++)
|
||||
for (auto p = points.begin(); p != points.end(); ++p)
|
||||
extend(*p);
|
||||
}
|
||||
|
||||
@@ -175,10 +228,10 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transform
|
||||
glm::vec4 vec;
|
||||
|
||||
// Apply transform to all four corners (can be rotated) and update bbox accordingly
|
||||
vec = m * glm::vec4(mMin, 1.f);
|
||||
vec = m * glm::vec4(mMin.x, mMin.y, 0.f, 1.f);
|
||||
bb.extend(glm::vec3(vec));
|
||||
|
||||
vec = m * glm::vec4(mMax, 1.f);
|
||||
vec = m * glm::vec4(mMax.x, mMax.y, 0.f, 1.f);
|
||||
bb.extend(glm::vec3(vec));
|
||||
|
||||
vec = m * glm::vec4(mMin.x, mMax.y, 0.f, 1.f);
|
||||
@@ -190,6 +243,14 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transform
|
||||
return bb;
|
||||
}
|
||||
|
||||
bool GlmToolkit::operator< (const GlmToolkit::AxisAlignedBoundingBox& A, const GlmToolkit::AxisAlignedBoundingBox& B )
|
||||
{
|
||||
if (A.isNull())
|
||||
return true;
|
||||
if (B.isNull())
|
||||
return false;
|
||||
return ( glm::length2(A.mMax-A.mMin) < glm::length2(B.mMax-B.mMin) );
|
||||
}
|
||||
|
||||
glm::ivec2 GlmToolkit::resolutionFromDescription(int aspectratio, int height)
|
||||
{
|
||||
|
||||
35
GlmToolkit.h
35
GlmToolkit.h
@@ -1,31 +1,22 @@
|
||||
#ifndef GLMTOOLKIT_H
|
||||
#define GLMTOOLKIT_H
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace GlmToolkit
|
||||
{
|
||||
|
||||
// get integer with unique id
|
||||
uint64_t uniqueId();
|
||||
|
||||
// get Matrix for these transformation components
|
||||
glm::mat4 transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale);
|
||||
void inverse_transform(glm::mat4 M, glm::vec3 &translation, glm::vec3 &rotation, glm::vec3 &scale);
|
||||
|
||||
class AxisAlignedBoundingBox
|
||||
{
|
||||
glm::vec3 mMin;
|
||||
glm::vec3 mMax;
|
||||
|
||||
public:
|
||||
AxisAlignedBoundingBox();
|
||||
|
||||
void operator = (const AxisAlignedBoundingBox &D ) {
|
||||
mMin = D.mMin;
|
||||
mMax = D.mMax;
|
||||
}
|
||||
AxisAlignedBoundingBox(const AxisAlignedBoundingBox &D);
|
||||
void operator = (const AxisAlignedBoundingBox &D );
|
||||
|
||||
// test
|
||||
inline bool isNull() const { return mMin.x > mMax.x || mMin.y > mMax.y || mMin.z > mMax.z;}
|
||||
@@ -45,8 +36,28 @@ public:
|
||||
AxisAlignedBoundingBox translated(glm::vec3 t) const;
|
||||
AxisAlignedBoundingBox scaled(glm::vec3 s) const;
|
||||
AxisAlignedBoundingBox transformed(glm::mat4 m) const;
|
||||
|
||||
friend bool operator<(const AxisAlignedBoundingBox& A, const AxisAlignedBoundingBox& B );
|
||||
|
||||
protected:
|
||||
glm::vec3 mMin;
|
||||
glm::vec3 mMax;
|
||||
};
|
||||
|
||||
// a bbox A is < from bbox B if its diagonal is shorter
|
||||
bool operator< (const AxisAlignedBoundingBox& A, const AxisAlignedBoundingBox& B );
|
||||
|
||||
|
||||
class OrientedBoundingBox
|
||||
{
|
||||
public:
|
||||
OrientedBoundingBox() : orientation(glm::vec3(0.f,0.f,0.f)) {}
|
||||
|
||||
AxisAlignedBoundingBox aabb;
|
||||
glm::vec3 orientation;
|
||||
};
|
||||
|
||||
|
||||
|
||||
static const char* aspect_ratio_names[6] = { "1:1", "4:3", "3:2", "16:10", "16:9", "21:9" };
|
||||
static const char* height_names[10] = { "16", "64", "200", "320", "480", "576", "720p", "1080p", "1440", "4K" };
|
||||
|
||||
197
GstToolkit.cpp
197
GstToolkit.cpp
@@ -1,7 +1,28 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#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)
|
||||
@@ -12,6 +33,8 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
|
||||
return "00:00:00.00";
|
||||
case TIME_STRING_MINIMAL:
|
||||
return "0.0";
|
||||
case TIME_STRING_READABLE:
|
||||
return "0 second";
|
||||
default:
|
||||
return "00.00";
|
||||
}
|
||||
@@ -21,23 +44,52 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
|
||||
guint s = ms / 1000;
|
||||
ostringstream oss;
|
||||
|
||||
// MINIMAL: keep only the 2 higher values (most significant)
|
||||
if (m == TIME_STRING_MINIMAL) {
|
||||
// READABLE : long format
|
||||
if (m == TIME_STRING_READABLE) {
|
||||
int count = 0;
|
||||
if (s / 3600) {
|
||||
oss << s / 3600 << ':';
|
||||
oss << s / 3600 << " h ";
|
||||
count++;
|
||||
}
|
||||
if ((s % 3600) / 60) {
|
||||
oss << (s % 3600) / 60 << ':';
|
||||
oss << (s % 3600) / 60 << " min ";
|
||||
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) / 100 << " sec";
|
||||
else
|
||||
oss << " s";
|
||||
}
|
||||
if (count < 2 )
|
||||
oss << '.'<< setw(1) << setfill('0') << (ms % 1000) / 10;
|
||||
}
|
||||
// MINIMAL: keep only the 2 higher values (most significant)
|
||||
else if (m == TIME_STRING_MINIMAL) {
|
||||
int count = 0;
|
||||
// hours
|
||||
if (s / 3600) {
|
||||
oss << s / 3600 << ':';
|
||||
count++;
|
||||
}
|
||||
// minutes
|
||||
if (count > 0) {
|
||||
oss << setw(2) << setfill('0') << (s % 3600) / 60 << ':';
|
||||
count++;
|
||||
}
|
||||
else if ((s % 3600) / 60)
|
||||
{
|
||||
oss << (s % 3600) / 60 << ':';
|
||||
count++;
|
||||
}
|
||||
// seconds
|
||||
{
|
||||
oss << setw(count > 0 ? 2 : 1) << setfill('0') << (s % 3600) % 60;
|
||||
count++;
|
||||
}
|
||||
if (count < 2)
|
||||
oss << '.'<< setw((ms % 1000) / 100 ? 2 : 1) << setfill('0') << (ms % 1000) / 10;
|
||||
}
|
||||
else {
|
||||
// TIME_STRING_FIXED : fixed length string (11 chars) HH:mm:ss.ii"
|
||||
@@ -54,6 +106,18 @@ string GstToolkit::time_to_string(guint64 t, time_string_mode m)
|
||||
}
|
||||
|
||||
|
||||
std::string GstToolkit::filename_to_uri(std::string path)
|
||||
{
|
||||
if (path.empty())
|
||||
return 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;
|
||||
@@ -107,6 +171,31 @@ bool GstToolkit::enable_feature (string name, bool enable) {
|
||||
}
|
||||
|
||||
gst_registry_add_feature (registry, GST_PLUGIN_FEATURE (factory));
|
||||
gst_object_unref (factory);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GstToolkit::has_feature (string name)
|
||||
{
|
||||
if (name.empty())
|
||||
return false;
|
||||
|
||||
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;
|
||||
|
||||
GstElement *elem = gst_element_factory_create (factory, NULL);
|
||||
gst_object_unref (factory);
|
||||
|
||||
if (!elem) return false;
|
||||
|
||||
gst_object_unref (elem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -121,3 +210,99 @@ string GstToolkit::gst_version()
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
// https://gstreamer.freedesktop.org/documentation/nvcodec/index.html?gi-language=c#plugin-nvcodec
|
||||
// list ordered with higher priority first (e.g. nvidia proprietary before vaapi)
|
||||
const char *plugins[11] = { "nvh264dec", "nvh265dec", "nvmpeg2videodec", "nvmpeg4videodec", "nvvp8dec", "nvvp9dec",
|
||||
"vaapidecodebin", "omxmpeg4videodec", "omxmpeg2dec", "omxh264dec", "vdpaumpegdec",
|
||||
};
|
||||
const int N = 11;
|
||||
#elif GST_GL_HAVE_PLATFORM_CGL
|
||||
const char *plugins[2] = { "vtdec_hw", "vtdechw" };
|
||||
const int N = 2;
|
||||
#else
|
||||
const char *plugins[0] = { };
|
||||
const int N = 0;
|
||||
#endif
|
||||
|
||||
|
||||
// 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)
|
||||
{
|
||||
list<string> plugins_list_;
|
||||
|
||||
static GstRegistry* plugins_register = nullptr;
|
||||
if ( plugins_register == nullptr )
|
||||
plugins_register = gst_registry_get();
|
||||
|
||||
static bool enabled_ = false;
|
||||
if (enabled_ != enable) {
|
||||
enabled_ = enable;
|
||||
for (int i = 0; i < N; i++) {
|
||||
GstPluginFeature* feature = gst_registry_lookup_feature(plugins_register, plugins[i]);
|
||||
if(feature != NULL) {
|
||||
plugins_list_.push_front( string( plugins[i] ) );
|
||||
gst_plugin_feature_set_rank(feature, enable ? GST_RANK_PRIMARY + (N-i) : GST_RANK_MARGINAL);
|
||||
gst_object_unref(feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plugins_list_;
|
||||
}
|
||||
|
||||
|
||||
std::string GstToolkit::used_gpu_decoding_plugins(GstElement *gstbin)
|
||||
{
|
||||
std::string found = "";
|
||||
|
||||
GstIterator* it = gst_bin_iterate_recurse(GST_BIN(gstbin));
|
||||
GValue value = G_VALUE_INIT;
|
||||
for(GstIteratorResult r = gst_iterator_next(it, &value); r != GST_ITERATOR_DONE; r = gst_iterator_next(it, &value))
|
||||
{
|
||||
if ( r == GST_ITERATOR_OK )
|
||||
{
|
||||
GstElement *e = static_cast<GstElement*>(g_value_peek_pointer(&value));
|
||||
if (e) {
|
||||
const gchar *name = gst_element_get_name(e);
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (std::string(name).find(plugins[i]) != std::string::npos) {
|
||||
found = plugins[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
g_value_unset(&value);
|
||||
}
|
||||
gst_iterator_free(it);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string GstToolkit::used_decoding_plugins(GstElement *gstbin)
|
||||
{
|
||||
std::string found = "";
|
||||
|
||||
GstIterator* it = gst_bin_iterate_recurse(GST_BIN(gstbin));
|
||||
GValue value = G_VALUE_INIT;
|
||||
for(GstIteratorResult r = gst_iterator_next(it, &value); r != GST_ITERATOR_DONE; r = gst_iterator_next(it, &value))
|
||||
{
|
||||
if ( r == GST_ITERATOR_OK )
|
||||
{
|
||||
GstElement *e = static_cast<GstElement*>(g_value_peek_pointer(&value));
|
||||
if (e) {
|
||||
const gchar *name = gst_element_get_name(e);
|
||||
found += std::string(name) + ", ";
|
||||
}
|
||||
}
|
||||
g_value_unset(&value);
|
||||
}
|
||||
gst_iterator_free(it);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
13
GstToolkit.h
13
GstToolkit.h
@@ -12,15 +12,22 @@ namespace GstToolkit
|
||||
typedef enum {
|
||||
TIME_STRING_FIXED = 0,
|
||||
TIME_STRING_ADJUSTED,
|
||||
TIME_STRING_MINIMAL
|
||||
TIME_STRING_MINIMAL,
|
||||
TIME_STRING_READABLE
|
||||
} 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> all_plugin_features(std::string pluginname);
|
||||
|
||||
std::list<std::string> all_plugins();
|
||||
std::list<std::string> enable_gpu_decoding_plugins(bool enable = true);
|
||||
std::string used_gpu_decoding_plugins(GstElement *gstbin);
|
||||
std::string used_decoding_plugins(GstElement *gstbin);
|
||||
|
||||
std::list<std::string> all_plugin_features(std::string pluginname);
|
||||
bool has_feature (std::string name);
|
||||
bool enable_feature (std::string name, bool enable);
|
||||
|
||||
}
|
||||
|
||||
1272
ImGuiToolkit.cpp
1272
ImGuiToolkit.cpp
File diff suppressed because it is too large
Load Diff
@@ -3,33 +3,56 @@
|
||||
|
||||
#include <glib.h>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include "rsc/fonts/IconsFontAwesome5.h"
|
||||
|
||||
namespace ImGuiToolkit
|
||||
{
|
||||
// Icons from resource icon.dds
|
||||
void Icon(int i, int j);
|
||||
void Icon (int i, int j, bool enabled = true);
|
||||
bool IconButton (int i, int j, const char *tooltips = nullptr);
|
||||
bool IconButton (const char* icon, 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);
|
||||
|
||||
// utility buttons
|
||||
// buttons and gui items with icon
|
||||
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 ButtonToggle(const char* label, bool* toggle);
|
||||
void ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr);
|
||||
bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr);
|
||||
void ButtonOpenUrl (const char* url, const ImVec2& size_arg = ImVec2(0,0));
|
||||
bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltip = nullptr);
|
||||
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state, const char* tooltip = nullptr);
|
||||
bool MenuItemIcon (int i, int j, const char* label, bool selected = false, bool enabled = true);
|
||||
bool SelectableIcon(const char* label, int i, int j, bool selected = false);
|
||||
bool ComboIcon (std::vector<std::pair<int, int> > icons, std::vector<std::string> labels, int* state);
|
||||
bool ComboIcon (const char* label, std::vector<std::pair<int, int> > icons, std::vector<std::string> items, int* i);
|
||||
|
||||
void HelpMarker (const char* desc);
|
||||
// buttons
|
||||
bool ButtonToggle (const char* label, bool* toggle, const char *tooltip = nullptr);
|
||||
bool ButtonSwitch (const char* label, bool* toggle, const char *shortcut = nullptr);
|
||||
void ButtonOpenUrl (const char* label, const char* url, const ImVec2& size_arg = ImVec2(0,0));
|
||||
|
||||
// utility sliders
|
||||
bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width);
|
||||
// tooltip and mouse over help
|
||||
void setToolTipsEnabled (bool on);
|
||||
bool toolTipsEnabled ();
|
||||
void ToolTip (const char* desc, const char* shortcut = nullptr);
|
||||
void HelpToolTip(const char* desc, const char* shortcut = nullptr);
|
||||
void Indication (const char* desc, const char* icon, const char* shortcut = nullptr);
|
||||
void Indication (const char* desc, int i, int j, const char* shortcut = nullptr);
|
||||
|
||||
// sliders
|
||||
bool SliderTiming (const char* label, uint *ms, uint v_min, uint v_max, uint v_step, const char* text_max = nullptr);
|
||||
bool TimelineSlider (const char* label, guint64 *time, guint64 begin, guint64 first, guint64 end, guint64 step, const float width, double tempo = 0, double quantum = 0);
|
||||
void RenderTimeline (struct ImGuiWindow* window, struct ImRect timeline_bbox, guint64 begin, guint64 end, guint64 step, bool verticalflip = false);
|
||||
void RenderTimelineBPM (struct ImGuiWindow* window, struct ImRect timeline_bbox, double tempo, double quantum, guint64 begin, guint64 end, guint64 step, bool verticalflip = false);
|
||||
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);
|
||||
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, guint64 begin, guint64 end, bool cut, bool *released, const ImVec2 size);
|
||||
void ShowPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, const ImVec2 size);
|
||||
|
||||
// fonts from ressources 'fonts/'
|
||||
typedef enum {
|
||||
@@ -41,22 +64,30 @@ namespace ImGuiToolkit
|
||||
} font_style;
|
||||
void SetFont (font_style type, const std::string &ttf_font_name, int pointsize, int oversample = 2);
|
||||
void PushFont (font_style type);
|
||||
void ImageGlyph(font_style type, char c, float h = 60);
|
||||
void Spacing();
|
||||
|
||||
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);
|
||||
// text input
|
||||
bool InputText(const char* label, std::string* str);
|
||||
bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0));
|
||||
void TextMultiline(const char* label, const std::string &str, float width);
|
||||
|
||||
bool InputCodeMultiline(const char* label, std::string *str, const ImVec2& size = ImVec2(0, 0), int *numline = NULL);
|
||||
void CodeMultiline(const char* label, const std::string &str, float width);
|
||||
|
||||
// color of gui items
|
||||
// accent color of UI
|
||||
typedef enum {
|
||||
ACCENT_BLUE =0,
|
||||
ACCENT_ORANGE,
|
||||
ACCENT_GREY
|
||||
} accent_color;
|
||||
void SetAccentColor (accent_color color);
|
||||
struct ImVec4 GetHighlightColor ();
|
||||
struct ImVec4 HighlightColor (bool active = true);
|
||||
|
||||
void ShowStats (bool* p_open, int* p_corner, bool* p_timer);
|
||||
// varia
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
702
ImGuiVisitor.cpp
702
ImGuiVisitor.cpp
File diff suppressed because it is too large
Load Diff
@@ -2,34 +2,38 @@
|
||||
#define IMGUIVISITOR_H
|
||||
|
||||
#include "Visitor.h"
|
||||
#include "InfoVisitor.h"
|
||||
|
||||
class ImGuiVisitor: public Visitor
|
||||
{
|
||||
InfoVisitor info;
|
||||
|
||||
public:
|
||||
ImGuiVisitor();
|
||||
|
||||
// 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(MediaSurface& n) override;
|
||||
void visit(FrameBufferSurface& n) override;
|
||||
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 (FrameBufferSurface& n) override;
|
||||
|
||||
// Elements with attributes
|
||||
void visit(MediaPlayer& n) override;
|
||||
void visit(Shader& n) override;
|
||||
void visit(ImageShader& n) override;
|
||||
void visit(ImageProcessingShader& n) override;
|
||||
void visit (MediaPlayer& n) override;
|
||||
void visit (Shader& n) override;
|
||||
void visit (ImageProcessingShader& n) override;
|
||||
void visit (Source& s) override;
|
||||
void visit (MediaSource& s) override;
|
||||
void visit (SessionSource& s) override;
|
||||
void visit (SessionFileSource& s) override;
|
||||
void visit (SessionGroupSource& s) override;
|
||||
void visit (RenderSource& s) override;
|
||||
void visit (CloneSource& s) override;
|
||||
void visit (PatternSource& s) override;
|
||||
void visit (DeviceSource& s) override;
|
||||
void visit (NetworkSource& s) override;
|
||||
void visit (MultiFileSource& s) override;
|
||||
void visit (GenericStreamSource& s) override;
|
||||
};
|
||||
|
||||
#endif // IMGUIVISITOR_H
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include "defines.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "ImageProcessingShader.h"
|
||||
|
||||
ShadingProgram imageProcessingShadingProgram("shaders/image.vs", "shaders/imageprocessing.fs");
|
||||
@@ -12,26 +32,7 @@ const char* ImageProcessingShader::filter_names[12] = { "None", "Blur", "Sharpen
|
||||
ImageProcessingShader::ImageProcessingShader(): Shader()
|
||||
{
|
||||
program_ = &imageProcessingShadingProgram;
|
||||
reset();
|
||||
}
|
||||
|
||||
ImageProcessingShader::ImageProcessingShader(const ImageProcessingShader &S): Shader()
|
||||
{
|
||||
program_ = &imageProcessingShadingProgram;
|
||||
reset();
|
||||
brightness = S.brightness;
|
||||
contrast = S.contrast;
|
||||
saturation = S.saturation;
|
||||
hueshift = S.hueshift;
|
||||
threshold = S.threshold;
|
||||
lumakey = S.lumakey;
|
||||
nbColors = S.nbColors;
|
||||
invert = S.invert;
|
||||
filterid = S.filterid;
|
||||
gamma = S.gamma;
|
||||
levels = S.levels;
|
||||
chromakey = S.chromakey;
|
||||
chromadelta = S.chromadelta;
|
||||
ImageProcessingShader::reset();
|
||||
}
|
||||
|
||||
void ImageProcessingShader::use()
|
||||
@@ -56,7 +57,6 @@ void ImageProcessingShader::use()
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ImageProcessingShader::reset()
|
||||
{
|
||||
Shader::reset();
|
||||
@@ -78,10 +78,8 @@ void ImageProcessingShader::reset()
|
||||
|
||||
}
|
||||
|
||||
void ImageProcessingShader::operator = (const ImageProcessingShader &S )
|
||||
void ImageProcessingShader::copy(ImageProcessingShader const& S)
|
||||
{
|
||||
Shader::operator =(S);
|
||||
|
||||
brightness = S.brightness;
|
||||
contrast = S.contrast;
|
||||
saturation = S.saturation;
|
||||
|
||||
@@ -11,13 +11,12 @@ class ImageProcessingShader : public Shader
|
||||
public:
|
||||
|
||||
ImageProcessingShader();
|
||||
ImageProcessingShader(const ImageProcessingShader &model);
|
||||
|
||||
void use() override;
|
||||
void reset() override;
|
||||
void accept(Visitor& v) override;
|
||||
|
||||
void operator = (const ImageProcessingShader &S);
|
||||
void copy(ImageProcessingShader const& S);
|
||||
|
||||
// color effects
|
||||
float brightness; // [-1 1]
|
||||
|
||||
157
ImageShader.cpp
157
ImageShader.cpp
@@ -1,71 +1,85 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Visitor.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
#include "rsc/fonts/IconsFontAwesome5.h"
|
||||
|
||||
static ShadingProgram imageShadingProgram("shaders/image.vs", "shaders/image.fs");
|
||||
#include "ImageShader.h"
|
||||
|
||||
const char* ImageShader::mask_names[11] = { "None", "Glow", "Halo", "Circle", "Round", "Vignette", "Top", "Botton", "Left", "Right", "Custom" };
|
||||
std::vector< uint > ImageShader::mask_presets;
|
||||
ShadingProgram imageShadingProgram("shaders/image.vs", "shaders/image.fs");
|
||||
ShadingProgram imageAlphaProgram ("shaders/image.vs", "shaders/imageblending.fs");
|
||||
std::vector< ShadingProgram > maskPrograms = {
|
||||
ShadingProgram("shaders/simple.vs", "shaders/simple.fs"),
|
||||
ShadingProgram("shaders/image.vs", "shaders/mask_draw.fs"),
|
||||
ShadingProgram("shaders/simple.vs", "shaders/mask_elipse.fs"),
|
||||
ShadingProgram("shaders/simple.vs", "shaders/mask_round.fs"),
|
||||
ShadingProgram("shaders/simple.vs", "shaders/mask_box.fs"),
|
||||
ShadingProgram("shaders/simple.vs", "shaders/mask_horizontal.fs"),
|
||||
ShadingProgram("shaders/simple.vs", "shaders/mask_vertical.fs")
|
||||
};
|
||||
|
||||
ImageShader::ImageShader(): Shader(), mask(0), custom_textureindex(0), stipple(0.0)
|
||||
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" };
|
||||
|
||||
ImageShader::ImageShader(): Shader(), stipple(0.f), mask_texture(0)
|
||||
{
|
||||
// first initialization
|
||||
if ( mask_presets.empty() ) {
|
||||
mask_presets.push_back(Resource::getTextureWhite());
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_glow.png"));
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_halo.png"));
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_circle.png"));
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_roundcorner.png"));
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_vignette.png"));
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_top.png"));
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_bottom.png"));
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_left.png"));
|
||||
mask_presets.push_back(Resource::getTextureImage("images/mask_linear_right.png"));
|
||||
}
|
||||
// static program shader
|
||||
program_ = &imageShadingProgram;
|
||||
// reset instance
|
||||
reset();
|
||||
ImageShader::reset();
|
||||
}
|
||||
|
||||
void ImageShader::use()
|
||||
{
|
||||
Shader::use();
|
||||
|
||||
// set stippling
|
||||
program_->setUniform("stipple", stipple);
|
||||
|
||||
// default mask
|
||||
if (mask_texture == 0)
|
||||
mask_texture = Resource::getTextureWhite();
|
||||
|
||||
// setup mask texture
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
if ( mask < 10 )
|
||||
glBindTexture(GL_TEXTURE_2D, mask_presets[mask]);
|
||||
else
|
||||
glBindTexture(GL_TEXTURE_2D, custom_textureindex);
|
||||
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();
|
||||
mask_texture = 0;
|
||||
|
||||
// default mask
|
||||
mask = 0;
|
||||
custom_textureindex = mask_presets[0];
|
||||
// no stippling
|
||||
stipple = 0.f;
|
||||
}
|
||||
|
||||
void ImageShader::operator = (const ImageShader &S )
|
||||
void ImageShader::copy(ImageShader const& S)
|
||||
{
|
||||
Shader::operator =(S);
|
||||
|
||||
mask = S.mask;
|
||||
custom_textureindex = S.custom_textureindex;
|
||||
mask_texture = S.mask_texture;
|
||||
stipple = S.stipple;
|
||||
}
|
||||
|
||||
@@ -74,3 +88,80 @@ 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)
|
||||
{
|
||||
// reset instance
|
||||
MaskShader::reset();
|
||||
// static program shader
|
||||
program_ = &maskPrograms[0];
|
||||
}
|
||||
|
||||
void MaskShader::use()
|
||||
{
|
||||
// select program to use
|
||||
mode = MINI(mode, 2);
|
||||
shape = MINI(shape, 4);
|
||||
program_ = mode < 2 ? &maskPrograms[mode] : &maskPrograms[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::copy(MaskShader const& S)
|
||||
{
|
||||
mode = S.mode;
|
||||
shape = S.shape;
|
||||
blur = S.blur;
|
||||
size = S.size;
|
||||
}
|
||||
|
||||
|
||||
void MaskShader::accept(Visitor& v) {
|
||||
Shader::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef IMAGESHADER_H
|
||||
#ifndef IMAGESHADER_H
|
||||
#define IMAGESHADER_H
|
||||
|
||||
#include <string>
|
||||
@@ -14,21 +14,66 @@ class ImageShader : public Shader
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
ImageShader();
|
||||
|
||||
void use() override;
|
||||
void reset() override;
|
||||
void accept(Visitor& v) override;
|
||||
void copy(ImageShader const& S);
|
||||
|
||||
void operator = (const ImageShader &S);
|
||||
uint mask_texture;
|
||||
|
||||
uint mask;
|
||||
uint custom_textureindex;
|
||||
// uniforms
|
||||
float stipple;
|
||||
};
|
||||
|
||||
static const char* mask_names[11];
|
||||
static std::vector< uint > mask_presets;
|
||||
class AlphaShader : public ImageShader
|
||||
{
|
||||
|
||||
public:
|
||||
AlphaShader();
|
||||
|
||||
};
|
||||
|
||||
|
||||
class MaskShader : public Shader
|
||||
{
|
||||
|
||||
public:
|
||||
MaskShader();
|
||||
|
||||
void use() override;
|
||||
void reset() override;
|
||||
void accept(Visitor& v) override;
|
||||
void copy(MaskShader const& S);
|
||||
|
||||
enum Modes {
|
||||
NONE = 0,
|
||||
PAINT = 1,
|
||||
SHAPE = 2
|
||||
};
|
||||
uint mode;
|
||||
|
||||
enum Shapes {
|
||||
ELIPSE = 0,
|
||||
OBLONG = 1,
|
||||
RECTANGLE = 2,
|
||||
HORIZONTAL = 3,
|
||||
VERTICAL = 4
|
||||
};
|
||||
uint shape;
|
||||
|
||||
// uniforms
|
||||
glm::vec2 size;
|
||||
float blur;
|
||||
|
||||
int option;
|
||||
int effect;
|
||||
glm::vec4 cursor;
|
||||
glm::vec3 brush;
|
||||
|
||||
static const char* mask_names[3];
|
||||
static const char* mask_shapes[5];
|
||||
};
|
||||
|
||||
#endif // IMAGESHADER_H
|
||||
|
||||
298
InfoVisitor.cpp
Normal file
298
InfoVisitor.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
|
||||
#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 "MultiFileSource.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "Settings.h"
|
||||
#include "Mixer.h"
|
||||
#include "ActionManager.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
#include "InfoVisitor.h"
|
||||
|
||||
|
||||
|
||||
InfoVisitor::InfoVisitor() : brief_(true), current_id_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Node &)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Group &)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Switch &)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Scene &)
|
||||
{
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Primitive &)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit(MediaPlayer &mp)
|
||||
{
|
||||
// do not ask twice
|
||||
if (current_id_ == mp.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << SystemToolkit::filename(mp.filename()) << std::endl;
|
||||
oss << mp.width() << " x " << mp.height() << ", ";
|
||||
oss << mp.media().codec_name.substr(0, mp.media().codec_name.find_first_of(" (,"));
|
||||
if (!mp.isImage())
|
||||
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
|
||||
}
|
||||
else {
|
||||
oss << mp.filename() << std::endl;
|
||||
oss << mp.media().codec_name << std::endl;
|
||||
oss << mp.width() << " x " << mp.height() ;
|
||||
if (!mp.isImage())
|
||||
oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
|
||||
// remember (except if codec was not identified yet)
|
||||
if ( !mp.media().codec_name.empty() )
|
||||
current_id_ = mp.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit(Stream &n)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << BaseToolkit::splitted(n.description(), '!').front();
|
||||
}
|
||||
else {
|
||||
oss << n.description();
|
||||
}
|
||||
information_ = oss.str();
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit (MediaSource& s)
|
||||
{
|
||||
s.mediaplayer()->accept(*this);
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (SessionFileSource& s)
|
||||
{
|
||||
if (current_id_ == s.id() || s.session() == nullptr)
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << SystemToolkit::filename(s.path()) << " (";
|
||||
oss << s.session()->numSource() << " sources)" << std::endl;
|
||||
}
|
||||
else
|
||||
oss << s.path() << std::endl;
|
||||
|
||||
if (s.session()->frame()){
|
||||
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
|
||||
oss << "RGB";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (SessionGroupSource& s)
|
||||
{
|
||||
if (current_id_ == s.id() || s.session() == nullptr)
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << s.session()->numSource() << " sources in group" << std::endl;
|
||||
if (s.session()->frame()){
|
||||
oss << s.session()->frame()->width() << " x " << s.session()->frame()->height() << ", ";
|
||||
oss << "RGB";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (RenderSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
information_ = "Rendering Output";
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (CloneSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
information_ = "Clone of " + s.origin()->name();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (PatternSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << Pattern::get(s.pattern()->type()).label << std::endl;
|
||||
if (s.pattern()) {
|
||||
oss << s.pattern()->width() << " x " << s.pattern()->height();
|
||||
oss << ", RGB";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (DeviceSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
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);
|
||||
|
||||
if (brief_) {
|
||||
oss << best.width << " x " << best.height << ", ";
|
||||
oss << best.stream << " " << best.format << ", ";
|
||||
oss << std::fixed << std::setprecision(1) << fps << " fps";
|
||||
}
|
||||
else {
|
||||
oss << s.device() << std::endl;
|
||||
oss << best.width << " x " << best.height << ", ";
|
||||
oss << best.stream << " " << best.format << ", ";
|
||||
oss << std::fixed << std::setprecision(1) << fps << " fps";
|
||||
}
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (NetworkSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
NetworkStream *ns = s.networkStream();
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << ns->resolution().x << " x " << ns->resolution().y << ", ";
|
||||
oss << NetworkToolkit::protocol_name[ns->protocol()] << std::endl;
|
||||
oss << "IP " << ns->serverAddress();
|
||||
}
|
||||
else {
|
||||
oss << s.connection() << " (IP " << ns->serverAddress() << ")" << std::endl;
|
||||
oss << ns->resolution().x << " x " << ns->resolution().y << ", ";
|
||||
oss << NetworkToolkit::protocol_name[ns->protocol()];
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
|
||||
void InfoVisitor::visit (MultiFileSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (brief_) {
|
||||
oss << s.sequence().width << " x " << s.sequence().height << ", ";
|
||||
oss << s.sequence().codec << std::endl;
|
||||
oss << s.sequence().max - s.sequence().min + 1 << " images [";
|
||||
oss << s.sequence().min << " - " << s.sequence().max << "]";
|
||||
}
|
||||
else {
|
||||
oss << s.sequence().location << " [";
|
||||
oss << s.sequence().min << " - " << s.sequence().max << "]" << std::endl;
|
||||
oss << s.sequence().width << " x " << s.sequence().height << ", ";
|
||||
oss << s.sequence().codec << " (";
|
||||
oss << s.sequence().max - s.sequence().min + 1 << " images)";
|
||||
}
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
|
||||
void InfoVisitor::visit (GenericStreamSource& s)
|
||||
{
|
||||
if (current_id_ == s.id())
|
||||
return;
|
||||
|
||||
std::ostringstream oss;
|
||||
if (s.stream()) {
|
||||
std::string src_element = s.gstElements().front();
|
||||
src_element = src_element.substr(0, src_element.find(" "));
|
||||
oss << "gstreamer '" << src_element << "'" << std::endl;
|
||||
oss << s.stream()->width() << " x " << s.stream()->height();
|
||||
oss << ", RGB";
|
||||
}
|
||||
else
|
||||
oss << "Undefined";
|
||||
|
||||
information_ = oss.str();
|
||||
current_id_ = s.id();
|
||||
}
|
||||
41
InfoVisitor.h
Normal file
41
InfoVisitor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef INFOVISITOR_H
|
||||
#define INFOVISITOR_H
|
||||
|
||||
#include "Visitor.h"
|
||||
|
||||
class InfoVisitor : public Visitor
|
||||
{
|
||||
std::string information_;
|
||||
bool brief_;
|
||||
uint64_t current_id_;
|
||||
|
||||
public:
|
||||
InfoVisitor();
|
||||
inline void setBriefStringMode () { brief_ = true; current_id_ = 0; }
|
||||
inline void setExtendedStringMode () { brief_ = false; current_id_ = 0; }
|
||||
inline void reset () { current_id_ = 0; }
|
||||
inline std::string str () const { return information_; }
|
||||
|
||||
// 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;
|
||||
|
||||
// Elements with attributes
|
||||
void visit (Stream& n) override;
|
||||
void visit (MediaPlayer& n) override;
|
||||
void visit (MediaSource& s) override;
|
||||
void visit (SessionFileSource& s) override;
|
||||
void visit (SessionGroupSource& s) override;
|
||||
void visit (RenderSource& s) override;
|
||||
void visit (CloneSource& s) override;
|
||||
void visit (PatternSource& s) override;
|
||||
void visit (DeviceSource& s) override;
|
||||
void visit (NetworkSource& s) override;
|
||||
void visit (MultiFileSource& s) override;
|
||||
void visit (GenericStreamSource& s) override;
|
||||
};
|
||||
|
||||
#endif // INFOVISITOR_H
|
||||
189
Interpolator.cpp
Normal file
189
Interpolator.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Source.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "UpdateCallback.h"
|
||||
|
||||
#include "Interpolator.h"
|
||||
|
||||
|
||||
SourceInterpolator::SourceInterpolator(Source *subject, const SourceCore &target) :
|
||||
subject_(subject), from_(static_cast<SourceCore>(*subject)), to_(target), current_cursor_(0.f)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void SourceInterpolator::interpolateGroup(View::Mode m)
|
||||
{
|
||||
current_state_.group(m)->translation_ =
|
||||
(1.f - current_cursor_) * from_.group(m)->translation_
|
||||
+ current_cursor_ * to_.group(m)->translation_;
|
||||
current_state_.group(m)->scale_ =
|
||||
(1.f - current_cursor_) * from_.group(m)->scale_
|
||||
+ current_cursor_ * to_.group(m)->scale_;
|
||||
current_state_.group(m)->rotation_ =
|
||||
(1.f - current_cursor_) * from_.group(m)->rotation_
|
||||
+ current_cursor_ * to_.group(m)->rotation_;
|
||||
current_state_.group(m)->crop_ =
|
||||
(1.f - current_cursor_) * from_.group(m)->crop_
|
||||
+ current_cursor_ * to_.group(m)->crop_;
|
||||
|
||||
CopyCallback *anim = new CopyCallback( current_state_.group(m) );
|
||||
subject_->group(m)->update_callbacks_.clear();
|
||||
subject_->group(m)->update_callbacks_.push_back(anim);
|
||||
}
|
||||
|
||||
void SourceInterpolator::interpolateImageProcessing()
|
||||
{
|
||||
current_state_.processingShader()->brightness =
|
||||
(1.f - current_cursor_) * from_.processingShader()->brightness
|
||||
+ current_cursor_ * to_.processingShader()->brightness;
|
||||
|
||||
current_state_.processingShader()->contrast =
|
||||
(1.f - current_cursor_) * from_.processingShader()->contrast
|
||||
+ current_cursor_ * to_.processingShader()->contrast;
|
||||
|
||||
current_state_.processingShader()->saturation =
|
||||
(1.f - current_cursor_) * from_.processingShader()->saturation
|
||||
+ current_cursor_ * to_.processingShader()->saturation;
|
||||
|
||||
current_state_.processingShader()->hueshift =
|
||||
(1.f - current_cursor_) * from_.processingShader()->hueshift
|
||||
+ current_cursor_ * to_.processingShader()->hueshift;
|
||||
|
||||
current_state_.processingShader()->threshold =
|
||||
(1.f - current_cursor_) * from_.processingShader()->threshold
|
||||
+ current_cursor_ * to_.processingShader()->threshold;
|
||||
|
||||
current_state_.processingShader()->lumakey =
|
||||
(1.f - current_cursor_) * from_.processingShader()->lumakey
|
||||
+ current_cursor_ * to_.processingShader()->lumakey;
|
||||
|
||||
current_state_.processingShader()->nbColors =
|
||||
(1.f - current_cursor_) * from_.processingShader()->nbColors
|
||||
+ current_cursor_ * to_.processingShader()->nbColors;
|
||||
|
||||
current_state_.processingShader()->gamma =
|
||||
(1.f - current_cursor_) * from_.processingShader()->gamma
|
||||
+ current_cursor_ * to_.processingShader()->gamma;
|
||||
|
||||
current_state_.processingShader()->levels =
|
||||
(1.f - current_cursor_) * from_.processingShader()->levels
|
||||
+ current_cursor_ * to_.processingShader()->levels;
|
||||
|
||||
current_state_.processingShader()->chromakey =
|
||||
(1.f - current_cursor_) * from_.processingShader()->chromakey
|
||||
+ current_cursor_ * to_.processingShader()->chromakey;
|
||||
|
||||
current_state_.processingShader()->chromadelta =
|
||||
(1.f - current_cursor_) * from_.processingShader()->chromadelta
|
||||
+ current_cursor_ * to_.processingShader()->chromadelta;
|
||||
|
||||
subject_->processingShader()->copy( *current_state_.processingShader() );
|
||||
|
||||
// not interpolated : invert , filterid
|
||||
}
|
||||
|
||||
float SourceInterpolator::current() const
|
||||
{
|
||||
return current_cursor_;
|
||||
}
|
||||
|
||||
void SourceInterpolator::apply(float percent)
|
||||
{
|
||||
percent = CLAMP( percent, 0.f, 1.f);
|
||||
|
||||
if ( subject_ && ABS_DIFF(current_cursor_, percent) > EPSILON)
|
||||
{
|
||||
current_cursor_ = percent;
|
||||
|
||||
if (current_cursor_ < EPSILON) {
|
||||
current_cursor_ = 0.f;
|
||||
current_state_ = from_;
|
||||
subject_->copy(current_state_);
|
||||
}
|
||||
else if (current_cursor_ > 1.f - EPSILON) {
|
||||
current_cursor_ = 1.f;
|
||||
current_state_ = to_;
|
||||
subject_->copy(current_state_);
|
||||
}
|
||||
else {
|
||||
interpolateGroup(View::MIXING);
|
||||
interpolateGroup(View::GEOMETRY);
|
||||
interpolateGroup(View::LAYER);
|
||||
interpolateGroup(View::TEXTURE);
|
||||
interpolateImageProcessing();
|
||||
// Log::Info("SourceInterpolator::update %f", cursor);
|
||||
}
|
||||
|
||||
subject_->touch();
|
||||
}
|
||||
}
|
||||
|
||||
Interpolator::Interpolator()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Interpolator::~Interpolator()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void Interpolator::clear()
|
||||
{
|
||||
for (auto i = interpolators_.begin(); i != interpolators_.end(); ) {
|
||||
delete *i;
|
||||
i = interpolators_.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Interpolator::add (Source *s, const SourceCore &target)
|
||||
{
|
||||
SourceInterpolator *i = new SourceInterpolator(s, target);
|
||||
interpolators_.push_back(i);
|
||||
}
|
||||
|
||||
|
||||
float Interpolator::current() const
|
||||
{
|
||||
float ret = 0.f;
|
||||
if (interpolators_.size() > 0)
|
||||
ret = interpolators_.front()->current();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Interpolator::apply(float percent)
|
||||
{
|
||||
for (auto i = interpolators_.begin(); i != interpolators_.end(); ++i)
|
||||
(*i)->apply( percent );
|
||||
|
||||
}
|
||||
|
||||
|
||||
44
Interpolator.h
Normal file
44
Interpolator.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef INTERPOLATOR_H
|
||||
#define INTERPOLATOR_H
|
||||
|
||||
#include "Source.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
class SourceInterpolator
|
||||
{
|
||||
public:
|
||||
SourceInterpolator(Source *subject, const SourceCore &target);
|
||||
|
||||
void apply (float percent);
|
||||
float current() const;
|
||||
|
||||
protected:
|
||||
Source *subject_;
|
||||
|
||||
SourceCore from_;
|
||||
SourceCore to_;
|
||||
SourceCore current_state_;
|
||||
float current_cursor_;
|
||||
|
||||
void interpolateGroup (View::Mode m);
|
||||
void interpolateImageProcessing ();
|
||||
};
|
||||
|
||||
class Interpolator
|
||||
{
|
||||
public:
|
||||
Interpolator();
|
||||
~Interpolator();
|
||||
|
||||
void clear ();
|
||||
void add (Source *s, const SourceCore &target );
|
||||
|
||||
void apply (float percent);
|
||||
float current() const;
|
||||
|
||||
protected:
|
||||
std::list<SourceInterpolator *> interpolators_;
|
||||
|
||||
};
|
||||
|
||||
#endif // INTERPOLATOR_H
|
||||
410
LayerView.cpp
Normal file
410
LayerView.cpp
Normal file
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#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 "ImGuiToolkit.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "defines.h"
|
||||
#include "Source.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)
|
||||
{
|
||||
scene.root()->scale_ = glm::vec3(LAYER_DEFAULT_SCALE, LAYER_DEFAULT_SCALE, 1.0f);
|
||||
scene.root()->translation_ = glm::vec3(2.2f, 1.2f, 0.0f);
|
||||
// read default settings
|
||||
if ( Settings::application.views[mode_].name.empty() ) {
|
||||
// no settings found: store application default
|
||||
Settings::application.views[mode_].name = "Layer";
|
||||
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)->call( new SetDepth(depth, 80.f) );
|
||||
}
|
||||
Action::manager().store(std::string("Selection: Layer Distribute"));
|
||||
}
|
||||
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)->call( new SetDepth(depth, 80.f) );
|
||||
}
|
||||
Action::manager().store(std::string("Selection: Layer Compress"));
|
||||
}
|
||||
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)->call( new SetDepth((*rit)->depth(), 80.f) );
|
||||
}
|
||||
Action::manager().store(std::string("Selection: Layer Reverse order"));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// prevent invalid scaling
|
||||
float s = CLAMP(scene.root()->scale_.x, LAYER_MIN_SCALE, LAYER_MAX_SCALE);
|
||||
scene.root()->scale_.x = s;
|
||||
scene.root()->scale_.y = s;
|
||||
}
|
||||
|
||||
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(2.f, 1.f, 0.f);
|
||||
scene.root()->translation_ = glm::clamp(scene.root()->translation_, -border, border * 2.f);
|
||||
}
|
||||
|
||||
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 ( UserInterface::manager().ctrlModifier() && pick.first == s->lock_) {
|
||||
lock(s, false);
|
||||
// pick = { s->locker_, pick.second };
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick on the open lock icon; lock source and cancel pick
|
||||
else if ( UserInterface::manager().ctrlModifier() && 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>)
|
||||
{
|
||||
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) );
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
info << "Depth " << std::fixed << std::setprecision(2) << d << " ";
|
||||
current_action_ = s->name() + ": " + info.str();
|
||||
|
||||
return Cursor(Cursor_ResizeNESW, info.str() );
|
||||
}
|
||||
|
||||
void LayerView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static float accumulator = 0.f;
|
||||
accumulator += dt_;
|
||||
|
||||
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 > 100.f) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.21f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
accumulator = 0.f;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
accumulator = 0.f;
|
||||
}
|
||||
|
||||
// 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_;
|
||||
}
|
||||
}
|
||||
35
LayerView.h
Normal file
35
LayerView.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef LAYERVIEW_H
|
||||
#define LAYERVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class LayerView : public View
|
||||
{
|
||||
public:
|
||||
LayerView();
|
||||
// non assignable class
|
||||
LayerView(LayerView const&) = delete;
|
||||
LayerView& operator=(LayerView const&) = delete;
|
||||
|
||||
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) override;
|
||||
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
|
||||
void arrow (glm::vec2) override;
|
||||
|
||||
float setDepth (Source *, float d = -1.f);
|
||||
|
||||
private:
|
||||
void updateSelectionOverlay() override;
|
||||
|
||||
float aspect_ratio;
|
||||
Mesh *persp_left_, *persp_right_;
|
||||
Group *frame_;
|
||||
|
||||
};
|
||||
|
||||
#endif // LAYERVIEW_H
|
||||
101
Log.cpp
101
Log.cpp
@@ -1,22 +1,33 @@
|
||||
#include "Log.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include "ImGuiToolkit.h"
|
||||
#include "defines.h"
|
||||
|
||||
// multiplatform
|
||||
#include <tinyfiledialogs.h>
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
using namespace std;
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
#include "DialogToolkit.h"
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
static std::mutex mtx;
|
||||
|
||||
struct AppLog
|
||||
@@ -43,6 +54,7 @@ struct AppLog
|
||||
{
|
||||
mtx.lock();
|
||||
int old_size = Buf.size();
|
||||
Buf.appendf("%04d ", LineOffsets.size()); // this adds 6 characters to show line number
|
||||
Buf.appendfv(fmt, args);
|
||||
Buf.append("\n");
|
||||
|
||||
@@ -58,7 +70,7 @@ struct AppLog
|
||||
ImGui::SetNextWindowPos(ImVec2(430, 660), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(1150, 220), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(600, 180), ImVec2(FLT_MAX, FLT_MAX));
|
||||
if (!ImGui::Begin(title, p_open))
|
||||
if ( !ImGui::Begin(title, p_open))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
@@ -66,6 +78,9 @@ struct AppLog
|
||||
|
||||
// window
|
||||
ImGui::SameLine(0, 0);
|
||||
static bool numbering = true;
|
||||
ImGuiToolkit::ButtonIconToggle(4, 12, 4, 12, &numbering );
|
||||
ImGui::SameLine();
|
||||
bool clear = ImGui::Button( ICON_FA_BACKSPACE " Clear");
|
||||
ImGui::SameLine();
|
||||
bool copy = ImGui::Button( ICON_FA_COPY " Copy");
|
||||
@@ -73,7 +88,12 @@ struct AppLog
|
||||
Filter.Draw("Filter", -60.0f);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
if ( !ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_AlwaysHorizontalScrollbar) )
|
||||
{
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (clear)
|
||||
Clear();
|
||||
@@ -118,7 +138,7 @@ struct AppLog
|
||||
{
|
||||
for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
|
||||
{
|
||||
const char* line_start = buf + LineOffsets[line_no];
|
||||
const char* line_start = buf + LineOffsets[line_no] + (numbering?0:6);
|
||||
const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
|
||||
ImGui::TextUnformatted(line_start, line_end);
|
||||
}
|
||||
@@ -140,7 +160,10 @@ struct AppLog
|
||||
}
|
||||
};
|
||||
|
||||
static AppLog logs;
|
||||
AppLog logs;
|
||||
list<string> notifications;
|
||||
list<string> warnings;
|
||||
float notifications_timeout = 0.f;
|
||||
|
||||
void Log::Info(const char* fmt, ...)
|
||||
{
|
||||
@@ -153,12 +176,9 @@ void Log::Info(const char* fmt, ...)
|
||||
void Log::ShowLogWindow(bool* p_open)
|
||||
{
|
||||
ImGui::SetNextWindowSize(ImVec2(700, 600), ImGuiCond_FirstUseEver);
|
||||
logs.Draw( ICON_FA_LIST_UL " Logs", p_open);
|
||||
logs.Draw( IMGUI_TITLE_LOGS, p_open);
|
||||
}
|
||||
|
||||
static list<string> notifications;
|
||||
static float notifications_timeout = 0.f;
|
||||
|
||||
void Log::Notify(const char* fmt, ...)
|
||||
{
|
||||
ImGuiTextBuffer buf;
|
||||
@@ -173,12 +193,9 @@ void Log::Notify(const char* fmt, ...)
|
||||
notifications_timeout = 0.f;
|
||||
|
||||
// always log
|
||||
Log::Info("%s", buf.c_str());
|
||||
Log::Info(ICON_FA_INFO_CIRCLE " %s", buf.c_str());
|
||||
}
|
||||
|
||||
|
||||
static list<string> warnings;
|
||||
|
||||
void Log::Warning(const char* fmt, ...)
|
||||
{
|
||||
ImGuiTextBuffer buf;
|
||||
@@ -192,18 +209,18 @@ void Log::Warning(const char* fmt, ...)
|
||||
warnings.push_back(buf.c_str());
|
||||
|
||||
// always log
|
||||
Log::Info("Warning - %s\n", buf.c_str());
|
||||
Log::Info(ICON_FA_EXCLAMATION_TRIANGLE " Warning - %s", buf.c_str());
|
||||
}
|
||||
|
||||
void Log::Render(bool showNofitications, bool showWarnings)
|
||||
void Log::Render(bool *showWarnings)
|
||||
{
|
||||
bool show_warnings = !warnings.empty() & showWarnings;
|
||||
bool show_notification = !notifications.empty() & showNofitications;
|
||||
bool show_warnings = !warnings.empty();
|
||||
bool show_notification = !notifications.empty();
|
||||
|
||||
if (!show_notification && !show_warnings)
|
||||
return;
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
float width = io.DisplaySize.x * 0.4f;
|
||||
float pos = io.DisplaySize.x * 0.3f;
|
||||
|
||||
@@ -238,12 +255,13 @@ void Log::Render(bool showNofitications, bool showWarnings)
|
||||
notifications.clear();
|
||||
}
|
||||
|
||||
|
||||
if (show_warnings) {
|
||||
ImGui::OpenPopup("Warning");
|
||||
if (ImGui::BeginPopupModal("Warning", NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
ImGuiToolkit::Icon(9, 4);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
||||
ImGui::SetNextItemWidth(width);
|
||||
ImGui::TextColored(ImVec4(1.0f,0.6f,0.0f,1.0f), "%ld error(s) occured.\n\n", warnings.size());
|
||||
ImGui::Dummy(ImVec2(width, 0));
|
||||
@@ -255,8 +273,21 @@ void Log::Render(bool showNofitications, bool showWarnings)
|
||||
}
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
ImGui::Dummy(ImVec2(width * 0.8f, 0)); ImGui::SameLine(); // right align
|
||||
if (ImGui::Button(" Ok ", ImVec2(width * 0.2f, 0))) {
|
||||
bool close = false;
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button("Show logs", ImVec2(width * 0.2f, 0))) {
|
||||
close = true;
|
||||
if (showWarnings!= nullptr)
|
||||
*showWarnings = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Dummy(ImVec2(width * 0.6f, 0)); // right align
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(" Ok ", ImVec2(width * 0.2f, 0)))
|
||||
close = true;
|
||||
|
||||
if (close) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
// messages have been seen
|
||||
warnings.clear();
|
||||
@@ -267,7 +298,6 @@ void Log::Render(bool showNofitications, bool showWarnings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Log::Error(const char* fmt, ...)
|
||||
@@ -279,7 +309,8 @@ void Log::Error(const char* fmt, ...)
|
||||
buf.appendfv(fmt, args);
|
||||
va_end(args);
|
||||
|
||||
tinyfd_messageBox( APP_TITLE, buf.c_str(), "ok", "error", 0);
|
||||
DialogToolkit::ErrorDialog(buf.c_str());
|
||||
|
||||
Log::Info("Error - %s\n", buf.c_str());
|
||||
}
|
||||
|
||||
|
||||
2
Log.h
2
Log.h
@@ -12,7 +12,7 @@ namespace Log
|
||||
// Draw logs
|
||||
void ShowLogWindow(bool* p_open = nullptr);
|
||||
|
||||
void Render(bool showNofitications = true, bool showWarnings = true);
|
||||
void Render(bool *showWarnings = nullptr);
|
||||
}
|
||||
|
||||
#endif // __LOG_H_
|
||||
|
||||
79
Loopback.cpp
79
Loopback.cpp
@@ -1,4 +1,21 @@
|
||||
#include <thread>
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
@@ -151,20 +168,17 @@ bool Loopback::systemLoopbackInitialized()
|
||||
|
||||
Loopback::Loopback() : FrameGrabber()
|
||||
{
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 60);
|
||||
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // fixed 30 FPS
|
||||
}
|
||||
|
||||
void Loopback::init(GstCaps *caps)
|
||||
std::string Loopback::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
return std::string("Invalid caps");
|
||||
|
||||
if (!Loopback::systemLoopbackInitialized()){
|
||||
Log::Warning("Loopback system shall be initialized first.");
|
||||
finished_ = true;
|
||||
return;
|
||||
return std::string("Loopback system shall be initialized first.");
|
||||
}
|
||||
|
||||
// create a gstreamer pipeline
|
||||
@@ -174,10 +188,9 @@ void Loopback::init(GstCaps *caps)
|
||||
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);
|
||||
std::string msg = std::string("Loopback : Could not construct pipeline ") + description + "\n" + std::string(error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
return msg;
|
||||
}
|
||||
|
||||
// setup device sink
|
||||
@@ -190,49 +203,51 @@ void Loopback::init(GstCaps *caps)
|
||||
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 );
|
||||
// configure stream
|
||||
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
|
||||
gst_app_src_set_latency( src_, -1, 0);
|
||||
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
// Set buffer size
|
||||
gst_app_src_set_max_bytes( src_, buffering_size_ );
|
||||
|
||||
// specify streaming framerate in the given caps
|
||||
GstCaps *tmp = gst_caps_copy( caps );
|
||||
GValue v = { 0, };
|
||||
g_value_init (&v, GST_TYPE_FRACTION);
|
||||
gst_value_set_fraction (&v, 30, 1); // fixed 30 FPS
|
||||
gst_caps_set_value(tmp, "framerate", &v);
|
||||
g_value_unset (&v);
|
||||
|
||||
// instruct src to use the caps
|
||||
caps_ = gst_caps_copy( tmp );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
gst_caps_unref (tmp);
|
||||
|
||||
// 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);
|
||||
gst_app_src_set_callbacks( src_, &callbacks, this, NULL);
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("Loopback Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
return std::string("Loopback : Could not configure source.");
|
||||
}
|
||||
|
||||
// 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;
|
||||
return std::string("Loopback : Could not open ") + Loopback::system_loopback_name;
|
||||
}
|
||||
|
||||
// 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;
|
||||
initialized_ = true;
|
||||
|
||||
return std::string("Loopback started on ") + Loopback::system_loopback_name;
|
||||
}
|
||||
|
||||
void Loopback::terminate()
|
||||
|
||||
@@ -15,7 +15,7 @@ class Loopback : public FrameGrabber
|
||||
static std::string system_loopback_name;
|
||||
static bool system_loopback_initialized;
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
std::string init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
|
||||
public:
|
||||
|
||||
716
MediaPlayer.cpp
716
MediaPlayer.cpp
File diff suppressed because it is too large
Load Diff
@@ -12,17 +12,17 @@
|
||||
#include <gst/app/gstappsink.h>
|
||||
|
||||
#include "Timeline.h"
|
||||
#include "Metronome.h"
|
||||
|
||||
// Forward declare classes referenced
|
||||
class Visitor;
|
||||
|
||||
#define MAX_PLAY_SPEED 20.0
|
||||
#define MIN_PLAY_SPEED 0.1
|
||||
#define N_VFRAME 3
|
||||
#define N_VFRAME 5
|
||||
|
||||
struct MediaInfo {
|
||||
|
||||
Timeline timeline;
|
||||
guint width;
|
||||
guint par_width; // width to match pixel aspect ratio
|
||||
guint height;
|
||||
@@ -34,6 +34,8 @@ struct MediaInfo {
|
||||
bool interlaced;
|
||||
bool seekable;
|
||||
bool valid;
|
||||
GstClockTime dt;
|
||||
GstClockTime end;
|
||||
|
||||
MediaInfo() {
|
||||
width = par_width = 640;
|
||||
@@ -41,19 +43,20 @@ struct MediaInfo {
|
||||
bitrate = 0;
|
||||
framerate_n = 1;
|
||||
framerate_d = 25;
|
||||
codec_name = "unknown";
|
||||
codec_name = "";
|
||||
isimage = false;
|
||||
interlaced = false;
|
||||
seekable = false;
|
||||
valid = false;
|
||||
dt = GST_CLOCK_TIME_NONE;
|
||||
end = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
|
||||
inline MediaInfo& operator = (const MediaInfo& b)
|
||||
{
|
||||
if (this != &b) {
|
||||
this->timeline.setEnd( b.timeline.end() );
|
||||
this->timeline.setStep( b.timeline.step() );
|
||||
this->timeline.setFirst( b.timeline.first() );
|
||||
this->dt = b.dt;
|
||||
this->end = b.end;
|
||||
this->width = b.width;
|
||||
this->par_width = b.par_width;
|
||||
this->height = b.height;
|
||||
@@ -89,7 +92,8 @@ public:
|
||||
/**
|
||||
* Open a media using gstreamer URI
|
||||
* */
|
||||
void open( std::string path);
|
||||
void open ( const std::string &filename, const std::string &uri = "");
|
||||
void reopen ();
|
||||
/**
|
||||
* Get name of the media
|
||||
* */
|
||||
@@ -101,7 +105,7 @@ public:
|
||||
/**
|
||||
* Get name of Codec of the media
|
||||
* */
|
||||
std::string codec() const;
|
||||
MediaInfo media() const;
|
||||
/**
|
||||
* True if a media was oppenned
|
||||
* */
|
||||
@@ -184,7 +188,15 @@ public:
|
||||
/**
|
||||
* Seek to zero
|
||||
* */
|
||||
void rewind();
|
||||
void rewind(bool force = false);
|
||||
/**
|
||||
* pending
|
||||
* */
|
||||
bool pending() const { return pending_; }
|
||||
/**
|
||||
* Get position time
|
||||
* */
|
||||
GstClockTime position();
|
||||
/**
|
||||
* go to a valid position in media timeline
|
||||
* pos in nanoseconds.
|
||||
@@ -204,13 +216,9 @@ public:
|
||||
* - frame duration : timeline.step()
|
||||
*/
|
||||
Timeline *timeline();
|
||||
void setTimeline(Timeline tl);
|
||||
void setTimeline(const Timeline &tl);
|
||||
|
||||
float currentTimelineFading();
|
||||
/**
|
||||
* Get position time
|
||||
* */
|
||||
GstClockTime position();
|
||||
/**
|
||||
* Get framerate of the media
|
||||
* */
|
||||
@@ -229,7 +237,7 @@ public:
|
||||
* */
|
||||
guint height() const;
|
||||
/**
|
||||
* Get frames displayt aspect ratio
|
||||
* Get frames display aspect ratio
|
||||
* NB: can be different than width() / height()
|
||||
* */
|
||||
float aspectRatio() const;
|
||||
@@ -238,9 +246,32 @@ public:
|
||||
* Must be called in OpenGL context
|
||||
* */
|
||||
guint texture() const;
|
||||
/**
|
||||
* Get the name of the decoder used,
|
||||
* return 'software' if no hardware decoder is used
|
||||
* NB: perform request on pipeline on first call
|
||||
* */
|
||||
std::string decoderName();
|
||||
/**
|
||||
* Forces open using software decoding
|
||||
* (i.e. without hadrware decoding)
|
||||
* NB: this reopens the video and reset decoder name
|
||||
* */
|
||||
void setSoftwareDecodingForced(bool on);
|
||||
bool softwareDecodingForced();
|
||||
/**
|
||||
* Option to automatically rewind each time the player is disabled
|
||||
* (i.e. when enable(false) is called )
|
||||
* */
|
||||
inline void setRewindOnDisabled(bool on) { rewind_on_disable_ = on; }
|
||||
inline bool rewindOnDisabled() const { return rewind_on_disable_; }
|
||||
/**
|
||||
* Option to synchronize with metronome
|
||||
* */
|
||||
inline void setSyncToMetronome(Metronome::Synchronicity s) { metro_sync_ = s; }
|
||||
inline Metronome::Synchronicity syncToMetronome() const { return metro_sync_; }
|
||||
/**
|
||||
* Accept visitors
|
||||
* Used for saving session file
|
||||
* */
|
||||
void accept(Visitor& v);
|
||||
/**
|
||||
@@ -251,6 +282,8 @@ public:
|
||||
static std::list<MediaPlayer*>::const_iterator begin() { return registered_.cbegin(); }
|
||||
static std::list<MediaPlayer*>::const_iterator end() { return registered_.cend(); }
|
||||
|
||||
static MediaInfo UriDiscoverer(const std::string &uri);
|
||||
|
||||
private:
|
||||
|
||||
// video player description
|
||||
@@ -261,6 +294,7 @@ private:
|
||||
|
||||
// general properties of media
|
||||
MediaInfo media_;
|
||||
Timeline timeline_;
|
||||
std::future<MediaInfo> discoverer_;
|
||||
|
||||
// GST & Play status
|
||||
@@ -270,24 +304,26 @@ private:
|
||||
GstState desired_state_;
|
||||
GstElement *pipeline_;
|
||||
GstVideoInfo v_frame_video_info_;
|
||||
std::atomic<bool> ready_;
|
||||
std::atomic<bool> opened_;
|
||||
std::atomic<bool> failed_;
|
||||
bool force_update_;
|
||||
bool pending_;
|
||||
bool seeking_;
|
||||
bool enabled_;
|
||||
bool rewind_on_disable_;
|
||||
bool force_software_decoding_;
|
||||
std::string decoder_name_;
|
||||
Metronome::Synchronicity metro_sync_;
|
||||
|
||||
// fps counter
|
||||
struct TimeCounter {
|
||||
|
||||
GstClockTime last_time;
|
||||
GstClockTime tic_time;
|
||||
int nbFrames;
|
||||
GTimer *timer;
|
||||
gdouble fps;
|
||||
public:
|
||||
TimeCounter();
|
||||
GstClockTime dt();
|
||||
~TimeCounter();
|
||||
void tic();
|
||||
void reset();
|
||||
gdouble frameRate() const;
|
||||
inline gdouble frameRate() const { return fps; }
|
||||
};
|
||||
TimeCounter timecount_;
|
||||
|
||||
@@ -311,6 +347,7 @@ private:
|
||||
status = INVALID;
|
||||
position = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
void unmap();
|
||||
};
|
||||
Frame frame_[N_VFRAME];
|
||||
guint write_index_;
|
||||
@@ -324,8 +361,9 @@ private:
|
||||
|
||||
// gst pipeline control
|
||||
void execute_open();
|
||||
void execute_play_command(bool on);
|
||||
void execute_loop_command();
|
||||
void execute_seek_command(GstClockTime target = GST_CLOCK_TIME_NONE);
|
||||
void execute_seek_command(GstClockTime target = GST_CLOCK_TIME_NONE, bool force = false);
|
||||
|
||||
// gst frame filling
|
||||
void init_texture(guint index);
|
||||
|
||||
107
MediaSource.cpp
107
MediaSource.cpp
@@ -1,6 +1,24 @@
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include "MediaSource.h"
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
@@ -11,7 +29,9 @@
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
MediaSource::MediaSource() : Source(), path_("")
|
||||
#include "MediaSource.h"
|
||||
|
||||
MediaSource::MediaSource(uint64_t id) : Source(id), path_("")
|
||||
{
|
||||
// create media player
|
||||
mediaplayer_ = new MediaPlayer;
|
||||
@@ -25,11 +45,14 @@ MediaSource::~MediaSource()
|
||||
|
||||
void MediaSource::setPath(const std::string &p)
|
||||
{
|
||||
Log::Notify("Creating Source with media '%s'", p.c_str());
|
||||
|
||||
path_ = p;
|
||||
|
||||
// open gstreamer
|
||||
mediaplayer_->open(path_);
|
||||
mediaplayer_->play(true);
|
||||
|
||||
// will be ready after init and one frame rendered
|
||||
ready_ = false;
|
||||
}
|
||||
|
||||
std::string MediaSource::path() const
|
||||
@@ -45,9 +68,14 @@ MediaPlayer *MediaSource::mediaplayer() const
|
||||
glm::ivec2 MediaSource::icon() const
|
||||
{
|
||||
if (mediaplayer_->isImage())
|
||||
return glm::ivec2(2, 9);
|
||||
return glm::ivec2(ICON_SOURCE_IMAGE);
|
||||
else
|
||||
return glm::ivec2(18, 13);
|
||||
return glm::ivec2(ICON_SOURCE_VIDEO);
|
||||
}
|
||||
|
||||
std::string MediaSource::info() const
|
||||
{
|
||||
return std::string("media '") + path_ + "'";
|
||||
}
|
||||
|
||||
bool MediaSource::failed() const
|
||||
@@ -79,20 +107,22 @@ void MediaSource::init()
|
||||
|
||||
// icon in mixing view
|
||||
if (mediaplayer_->isImage())
|
||||
symbol_ = new Symbol(Symbol::IMAGE, glm::vec3(0.8f, 0.8f, 0.01f));
|
||||
symbol_ = new Symbol(Symbol::IMAGE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
else
|
||||
symbol_ = new Symbol(Symbol::VIDEO, glm::vec3(0.8f, 0.8f, 0.01f));
|
||||
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);
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str());
|
||||
|
||||
// force update of activation mode
|
||||
active_ = true;
|
||||
touch();
|
||||
|
||||
// deep update to reorder
|
||||
++View::need_deep_update_;
|
||||
|
||||
// done init
|
||||
Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,14 +132,48 @@ void MediaSource::setActive (bool on)
|
||||
{
|
||||
bool was_active = active_;
|
||||
|
||||
// try to activate (may fail if source is cloned)
|
||||
Source::setActive(on);
|
||||
|
||||
// change status of media player (only if status changed)
|
||||
if ( active_ != was_active ) {
|
||||
if ( active_ != was_active )
|
||||
mediaplayer_->enable(active_);
|
||||
|
||||
// change visibility of active surface (show preview of media when inactive)
|
||||
if (activesurface_) {
|
||||
if (active_)
|
||||
activesurface_->setTextureIndex(Resource::getTextureTransparent());
|
||||
else
|
||||
activesurface_->setTextureIndex(mediaplayer_->texture());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool MediaSource::playing () const
|
||||
{
|
||||
return mediaplayer_->isPlaying();
|
||||
}
|
||||
|
||||
void MediaSource::play (bool on)
|
||||
{
|
||||
mediaplayer_->play(on);
|
||||
}
|
||||
|
||||
bool MediaSource::playable () const
|
||||
{
|
||||
return !mediaplayer_->isImage();
|
||||
}
|
||||
|
||||
void MediaSource::replay ()
|
||||
{
|
||||
mediaplayer_->rewind();
|
||||
}
|
||||
|
||||
guint64 MediaSource::playtime () const
|
||||
{
|
||||
return mediaplayer_->position();
|
||||
}
|
||||
|
||||
void MediaSource::update(float dt)
|
||||
{
|
||||
Source::update(dt);
|
||||
@@ -120,21 +184,16 @@ void MediaSource::update(float dt)
|
||||
|
||||
void MediaSource::render()
|
||||
{
|
||||
if (!initialized_)
|
||||
if ( renderbuffer_ == nullptr )
|
||||
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();
|
||||
// apply fading
|
||||
texturesurface_->shader()->color = glm::vec4( glm::vec3(mediaplayer_->currentTimelineFading()), 1.f);
|
||||
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
|
||||
renderbuffer_->end();
|
||||
ready_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,17 @@ class MediaPlayer;
|
||||
class MediaSource : public Source
|
||||
{
|
||||
public:
|
||||
MediaSource();
|
||||
MediaSource(uint64_t id = 0);
|
||||
~MediaSource();
|
||||
|
||||
// implementation of source API
|
||||
void update (float dt) override;
|
||||
void setActive (bool on) override;
|
||||
bool playing () const override;
|
||||
void play (bool) override;
|
||||
bool playable () const override;
|
||||
void replay () override;
|
||||
guint64 playtime () const override;
|
||||
void render() override;
|
||||
bool failed() const override;
|
||||
uint texture() const override;
|
||||
@@ -25,6 +30,7 @@ public:
|
||||
MediaPlayer *mediaplayer() const;
|
||||
|
||||
glm::ivec2 icon() const override;
|
||||
std::string info() const override;
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
36
Mesh.cpp
36
Mesh.cpp
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <istream>
|
||||
@@ -16,9 +35,10 @@
|
||||
#include "Resource.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
#include "Mesh.h"
|
||||
#include "GlmToolkit.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "Mesh.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace glm;
|
||||
@@ -31,11 +51,8 @@ typedef struct prop {
|
||||
std::string name;
|
||||
bool is_float;
|
||||
bool is_list;
|
||||
prop(std::string n, bool t, bool l = false){
|
||||
name = n;
|
||||
is_float = t;
|
||||
is_list = l;
|
||||
}
|
||||
prop(const std::string &n, bool t, bool l = false) :
|
||||
name(n), is_float(t), is_list(l) { }
|
||||
} plyProperty;
|
||||
|
||||
typedef std::map<std::string, std::vector<plyProperty> > plyElementProperties;
|
||||
@@ -45,9 +62,9 @@ template <typename T>
|
||||
T parseValue(std::istream& istream) {
|
||||
|
||||
T v;
|
||||
char space = ' ';
|
||||
istream >> v;
|
||||
if (!istream.eof()) {
|
||||
char space = ' ';
|
||||
istream >> space >> std::ws;
|
||||
}
|
||||
|
||||
@@ -230,7 +247,6 @@ bool parsePLY(string ascii,
|
||||
// a numerical property
|
||||
if ( ! prop.is_list ) {
|
||||
|
||||
float value;
|
||||
switch ( prop.name[0] ) {
|
||||
case 'x':
|
||||
point.x = parseValue<float>(stringstream);
|
||||
@@ -266,7 +282,7 @@ bool parsePLY(string ascii,
|
||||
break;
|
||||
default:
|
||||
// ignore normals or other types
|
||||
value = parseValue<float>(stringstream);
|
||||
parseValue<float>(stringstream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
1
Mesh.h
1
Mesh.h
@@ -18,6 +18,7 @@ public:
|
||||
Mesh(const std::string& ply_path, const std::string& tex_path = "");
|
||||
|
||||
void setTexture(uint textureindex);
|
||||
inline uint texture() const { return textureindex_; }
|
||||
|
||||
void init () override;
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
|
||||
253
Metronome.cpp
Normal file
253
Metronome.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
#include <atomic>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
/// Ableton Link is a technology that synchronizes musical beat, tempo,
|
||||
/// and phase across multiple applications running on one or more devices.
|
||||
/// Applications on devices connected to a local network discover each other
|
||||
/// automatically and form a musical session in which each participant can
|
||||
/// perform independently: anyone can start or stop while still staying in time.
|
||||
/// Anyone can change the tempo, the others will follow.
|
||||
/// Anyone can join or leave without disrupting the session.
|
||||
///
|
||||
/// https://ableton.github.io/link/
|
||||
///
|
||||
#include <ableton/Link.hpp>
|
||||
|
||||
#include "Settings.h"
|
||||
#include "Metronome.h"
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
namespace ableton
|
||||
{
|
||||
|
||||
/// Inspired from Dummy audio platform example
|
||||
/// https://github.com/Ableton/link/blob/master/examples/linkaudio/AudioPlatform_Dummy.hpp
|
||||
class Engine
|
||||
{
|
||||
public:
|
||||
Engine(Link& link)
|
||||
: mLink(link)
|
||||
, mQuantum(4.)
|
||||
{
|
||||
}
|
||||
|
||||
void startPlaying()
|
||||
{
|
||||
auto sessionState = mLink.captureAppSessionState();
|
||||
sessionState.setIsPlayingAndRequestBeatAtTime(true, now(), 0., mQuantum);
|
||||
mLink.commitAppSessionState(sessionState);
|
||||
}
|
||||
|
||||
void stopPlaying()
|
||||
{
|
||||
auto sessionState = mLink.captureAppSessionState();
|
||||
sessionState.setIsPlaying(false, now());
|
||||
mLink.commitAppSessionState(sessionState);
|
||||
}
|
||||
|
||||
bool isPlaying() const
|
||||
{
|
||||
return mLink.captureAppSessionState().isPlaying();
|
||||
}
|
||||
|
||||
double beatTime() const
|
||||
{
|
||||
auto sessionState = mLink.captureAppSessionState();
|
||||
return sessionState.beatAtTime(now(), mQuantum);
|
||||
}
|
||||
|
||||
std::chrono::microseconds timeNextBeat() const
|
||||
{
|
||||
auto sessionState = mLink.captureAppSessionState();
|
||||
double beat = ceil(sessionState.beatAtTime(now(), mQuantum));
|
||||
return sessionState.timeAtBeat(beat, mQuantum);
|
||||
}
|
||||
|
||||
double phaseTime() const
|
||||
{
|
||||
auto sessionState = mLink.captureAppSessionState();
|
||||
return sessionState.phaseAtTime(now(), mQuantum);
|
||||
}
|
||||
|
||||
std::chrono::microseconds timeNextPhase() const
|
||||
{
|
||||
auto sessionState = mLink.captureAppSessionState();
|
||||
double phase = ceil(sessionState.phaseAtTime(now(), mQuantum));
|
||||
double beat = ceil(sessionState.beatAtTime(now(), mQuantum));
|
||||
return sessionState.timeAtBeat(beat + (mQuantum-phase), mQuantum);
|
||||
}
|
||||
|
||||
double tempo() const
|
||||
{
|
||||
auto sessionState = mLink.captureAppSessionState();
|
||||
return sessionState.tempo();
|
||||
}
|
||||
|
||||
double setTempo(double tempo)
|
||||
{
|
||||
auto sessionState = mLink.captureAppSessionState();
|
||||
sessionState.setTempo(tempo, now());
|
||||
mLink.commitAppSessionState(sessionState);
|
||||
return sessionState.tempo();
|
||||
}
|
||||
|
||||
double quantum() const
|
||||
{
|
||||
return mQuantum;
|
||||
}
|
||||
|
||||
void setQuantum(double quantum)
|
||||
{
|
||||
mQuantum = quantum;
|
||||
}
|
||||
|
||||
bool isStartStopSyncEnabled() const
|
||||
{
|
||||
return mLink.isStartStopSyncEnabled();
|
||||
}
|
||||
|
||||
void setStartStopSyncEnabled(bool enabled)
|
||||
{
|
||||
mLink.enableStartStopSync(enabled);
|
||||
}
|
||||
|
||||
std::chrono::microseconds now() const
|
||||
{
|
||||
return mLink.clock().micros();
|
||||
}
|
||||
|
||||
private:
|
||||
Link& mLink;
|
||||
double mQuantum;
|
||||
};
|
||||
|
||||
|
||||
} // namespace ableton
|
||||
|
||||
|
||||
ableton::Link link_(120.);
|
||||
ableton::Engine engine_(link_);
|
||||
|
||||
Metronome::Metronome()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool Metronome::init()
|
||||
{
|
||||
// set parameters
|
||||
setEnabled(Settings::application.timer.link_enabled);
|
||||
setTempo(Settings::application.timer.link_tempo);
|
||||
setQuantum(Settings::application.timer.link_quantum);
|
||||
setStartStopSync(Settings::application.timer.link_start_stop_sync);
|
||||
|
||||
// no reason for failure?
|
||||
return true;
|
||||
}
|
||||
|
||||
void Metronome::terminate()
|
||||
{
|
||||
// save current tempo
|
||||
Settings::application.timer.link_tempo = tempo();
|
||||
|
||||
// disconnect
|
||||
link_.enable(false);
|
||||
}
|
||||
|
||||
void Metronome::setEnabled (bool on)
|
||||
{
|
||||
link_.enable(on);
|
||||
Settings::application.timer.link_enabled = link_.isEnabled();
|
||||
Log::Info("Metronome Ableton Link %s", Settings::application.timer.link_enabled ? "Enabled" : "Disabled");
|
||||
}
|
||||
|
||||
bool Metronome::enabled () const
|
||||
{
|
||||
return link_.isEnabled();
|
||||
}
|
||||
|
||||
double Metronome::beats() const
|
||||
{
|
||||
return engine_.beatTime();
|
||||
}
|
||||
|
||||
double Metronome::phase() const
|
||||
{
|
||||
return engine_.phaseTime();
|
||||
}
|
||||
|
||||
void Metronome::setQuantum(double q)
|
||||
{
|
||||
engine_.setQuantum(q);
|
||||
Settings::application.timer.link_quantum = engine_.quantum();
|
||||
}
|
||||
|
||||
double Metronome::quantum() const
|
||||
{
|
||||
return engine_.quantum();
|
||||
}
|
||||
|
||||
void Metronome::setTempo(double t)
|
||||
{
|
||||
// set the tempo to t
|
||||
// OR
|
||||
// adopt the last tempo value that have been proposed on the network
|
||||
Settings::application.timer.link_tempo = engine_.setTempo(t);
|
||||
}
|
||||
|
||||
double Metronome::tempo() const
|
||||
{
|
||||
return engine_.tempo();
|
||||
}
|
||||
|
||||
|
||||
void Metronome::setStartStopSync (bool on)
|
||||
{
|
||||
engine_.setStartStopSyncEnabled(on);
|
||||
Settings::application.timer.link_start_stop_sync = engine_.isStartStopSyncEnabled();
|
||||
Log::Info("Metronome Ableton Link start & stop sync %s", Settings::application.timer.link_start_stop_sync ? "Enabled" : "Disabled");
|
||||
}
|
||||
|
||||
bool Metronome::startStopSync () const
|
||||
{
|
||||
return engine_.isStartStopSyncEnabled();
|
||||
}
|
||||
|
||||
void Metronome::restart()
|
||||
{
|
||||
engine_.startPlaying();
|
||||
}
|
||||
|
||||
std::chrono::microseconds Metronome::timeToBeat()
|
||||
{
|
||||
return engine_.timeNextBeat() - engine_.now();
|
||||
}
|
||||
|
||||
std::chrono::microseconds Metronome::timeToPhase()
|
||||
{
|
||||
return engine_.timeNextPhase() - engine_.now();
|
||||
}
|
||||
|
||||
void delay(std::function<void()> f, std::chrono::microseconds us)
|
||||
{
|
||||
std::this_thread::sleep_for(us);
|
||||
f();
|
||||
}
|
||||
|
||||
void Metronome::executeAtBeat( std::function<void()> f )
|
||||
{
|
||||
std::thread( delay, f, timeToBeat() ).detach();
|
||||
}
|
||||
|
||||
void Metronome::executeAtPhase( std::function<void()> f )
|
||||
{
|
||||
std::thread( delay, f, timeToPhase() ).detach();
|
||||
}
|
||||
|
||||
size_t Metronome::peers() const
|
||||
{
|
||||
return link_.numPeers();
|
||||
}
|
||||
72
Metronome.h
Normal file
72
Metronome.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef METRONOME_H
|
||||
#define METRONOME_H
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
class Metronome
|
||||
{
|
||||
// Private Constructor
|
||||
Metronome();
|
||||
Metronome(Metronome const& copy) = delete;
|
||||
Metronome& operator=(Metronome const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
typedef enum {
|
||||
SYNC_NONE = 0,
|
||||
SYNC_BEAT,
|
||||
SYNC_PHASE
|
||||
} Synchronicity;
|
||||
|
||||
static Metronome& manager ()
|
||||
{
|
||||
// The only instance
|
||||
static Metronome _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
bool init ();
|
||||
void terminate ();
|
||||
|
||||
void setEnabled (bool on);
|
||||
bool enabled () const;
|
||||
|
||||
void setTempo (double t);
|
||||
double tempo () const;
|
||||
|
||||
void setQuantum (double q);
|
||||
double quantum () const;
|
||||
|
||||
void setStartStopSync (bool on);
|
||||
bool startStopSync () const;
|
||||
void restart();
|
||||
|
||||
// get beat and phase
|
||||
double beats () const;
|
||||
double phase () const;
|
||||
|
||||
// mechanisms to delay execution to next beat
|
||||
std::chrono::microseconds timeToBeat();
|
||||
void executeAtBeat( std::function<void()> f );
|
||||
|
||||
// mechanisms to delay execution to next phase
|
||||
std::chrono::microseconds timeToPhase();
|
||||
void executeAtPhase( std::function<void()> f );
|
||||
|
||||
size_t peers () const;
|
||||
|
||||
};
|
||||
|
||||
/// Example calls to executeAtBeat
|
||||
///
|
||||
/// With a Lamda function calling a member function of an object
|
||||
/// - without parameter
|
||||
/// Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p) { p->rewind(); }, mediaplayer_) );
|
||||
///
|
||||
/// - with parameter
|
||||
/// Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p, bool o) { p->play(o); }, mediaplayer_, on) );
|
||||
///
|
||||
|
||||
|
||||
#endif // METRONOME_H
|
||||
65
Mixer.h
65
Mixer.h
@@ -1,16 +1,26 @@
|
||||
#ifndef MIXER_H
|
||||
#define MIXER_H
|
||||
|
||||
#include "View.h"
|
||||
#include "GeometryView.h"
|
||||
#include "MixingView.h"
|
||||
#include "LayerView.h"
|
||||
#include "TextureView.h"
|
||||
#include "TransitionView.h"
|
||||
#include "Session.h"
|
||||
#include "Selection.h"
|
||||
|
||||
namespace tinyxml2 {
|
||||
class XMLElement;
|
||||
}
|
||||
|
||||
class SessionSource;
|
||||
|
||||
class Mixer
|
||||
{
|
||||
// Private Constructor
|
||||
Mixer();
|
||||
Mixer(Mixer const& copy); // Not Implemented
|
||||
Mixer& operator=(Mixer const& copy); // Not Implemented
|
||||
Mixer(Mixer const& copy) = delete;
|
||||
Mixer& operator=(Mixer const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
@@ -30,27 +40,32 @@ public:
|
||||
|
||||
// update session and all views
|
||||
void update();
|
||||
inline float dt() const { return dt_;}
|
||||
inline float dt() const { return dt_;} // in miliseconds
|
||||
inline int fps() const { return int(roundf(1000.f/dt__));}
|
||||
|
||||
// draw session and current view
|
||||
void draw();
|
||||
|
||||
// creation of sources
|
||||
Source * createSourceFile (const std::string &path);
|
||||
Source * createSourceMultifile(const std::list<std::string> &list_files, uint fps);
|
||||
Source * createSourceClone (const std::string &namesource = "");
|
||||
Source * createSourceRender ();
|
||||
Source * createSourceStream (const std::string &gstreamerpipeline);
|
||||
Source * createSourcePattern(uint pattern, glm::ivec2 res);
|
||||
Source * createSourceDevice (const std::string &namedevice);
|
||||
Source * createSourceNetwork(const std::string &nameconnection);
|
||||
Source * createSourceGroup ();
|
||||
|
||||
// operations on sources
|
||||
void addSource (Source *s);
|
||||
void deleteSource (Source *s, bool withundo=true);
|
||||
void renameSource (Source *s, const std::string &newname);
|
||||
void deleteSource (Source *s);
|
||||
void renameSource (Source *s, const std::string &newname = "");
|
||||
void attach (Source *s);
|
||||
void detach (Source *s);
|
||||
void deselect (Source *s);
|
||||
void deleteSelection();
|
||||
void groupSelection();
|
||||
|
||||
// current source
|
||||
Source * currentSource ();
|
||||
@@ -59,41 +74,53 @@ public:
|
||||
void setCurrentSource (Node *node);
|
||||
void setCurrentSource (uint64_t id);
|
||||
void setCurrentNext ();
|
||||
void setCurrentPrevious ();
|
||||
void unsetCurrentSource ();
|
||||
|
||||
Source *sourceAtIndex (int index);
|
||||
void setCurrentIndex (int index);
|
||||
int indexCurrentSource ();
|
||||
void moveIndex (int current_index, int target_index);
|
||||
int indexCurrentSource () const;
|
||||
int count() const;
|
||||
|
||||
// browsing into sources
|
||||
Source * findSource (Node *node);
|
||||
Source * findSource (std::string name);
|
||||
Source * findSource (uint64_t id);
|
||||
SourceList findSources (float depth_from, float depth_to);
|
||||
SourceList validate(const SourceList &list);
|
||||
|
||||
// management of view
|
||||
View *view (View::Mode m = View::INVALID);
|
||||
void setView (View::Mode m);
|
||||
|
||||
void conceal(Source *s);
|
||||
void uncover(Source *s);
|
||||
void conceal (Source *s);
|
||||
void uncover (Source *s);
|
||||
bool concealed(Source *s);
|
||||
|
||||
// manipulate, load and save sessions
|
||||
inline Session *session () const { return session_; }
|
||||
void clear ();
|
||||
void save ();
|
||||
void saveas (const std::string& filename);
|
||||
void save (bool with_version = false);
|
||||
void saveas (const std::string& filename, bool with_version = false);
|
||||
void load (const std::string& filename);
|
||||
void import (const std::string& filename);
|
||||
void merge (Session *s);
|
||||
void set (Session *s);
|
||||
void import (SessionSource *source);
|
||||
void merge (Session *session);
|
||||
void merge (SessionSource *source);
|
||||
void set (Session *session);
|
||||
void setResolution(glm::vec3 res);
|
||||
|
||||
// operations depending on transition mode
|
||||
void close ();
|
||||
void open (const std::string& filename);
|
||||
void close (bool smooth = false);
|
||||
void open (const std::string& filename, bool smooth = false);
|
||||
|
||||
// create sources if clipboard contains well-formed xml text
|
||||
void paste (const std::string& clipboard);
|
||||
|
||||
// version and undo management
|
||||
void restore(tinyxml2::XMLElement *sessionNode);
|
||||
|
||||
protected:
|
||||
|
||||
Session *session_;
|
||||
@@ -104,7 +131,9 @@ protected:
|
||||
|
||||
SourceList candidate_sources_;
|
||||
SourceList stash_;
|
||||
void insertSource(Source *s, View::Mode m = View::INVALID);
|
||||
void insertSource (Source *s, View::Mode m = View::INVALID);
|
||||
bool replaceSource (Source *from, Source *to);
|
||||
bool recreateSource(Source *s);
|
||||
|
||||
void setCurrentSource(SourceList::iterator it);
|
||||
SourceList::iterator current_source_;
|
||||
@@ -114,11 +143,11 @@ protected:
|
||||
MixingView mixing_;
|
||||
GeometryView geometry_;
|
||||
LayerView layer_;
|
||||
AppearanceView appearance_;
|
||||
TextureView appearance_;
|
||||
TransitionView transition_;
|
||||
|
||||
guint64 update_time_;
|
||||
float dt_;
|
||||
float dt__;
|
||||
};
|
||||
|
||||
#endif // MIXER_H
|
||||
|
||||
392
MixingGroup.cpp
Normal file
392
MixingGroup.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <glm/gtx/rotate_vector.hpp>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Source.h"
|
||||
#include "Decorations.h"
|
||||
#include "Visitor.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "MixingGroup.h"
|
||||
|
||||
|
||||
MixingGroup::MixingGroup (SourceList sources) : parent_(nullptr), root_(nullptr), lines_(nullptr), center_(nullptr),
|
||||
center_pos_(glm::vec2(0.f, 0.f)), active_(true), update_action_(ACTION_NONE), updated_source_(nullptr)
|
||||
{
|
||||
// create unique id
|
||||
id_ = BaseToolkit::uniqueId();
|
||||
|
||||
// fill the vector of sources with the given list
|
||||
for (auto it = sources.begin(); it != sources.end(); ++it){
|
||||
// add only if not linked already
|
||||
if ((*it)->mixinggroup_ == nullptr) {
|
||||
(*it)->mixinggroup_ = this;
|
||||
sources_.push_back(*it);
|
||||
}
|
||||
}
|
||||
|
||||
// scene elements
|
||||
root_ = new Group;
|
||||
root_->visible_ = false;
|
||||
center_ = new Symbol(Symbol::CIRCLE_POINT);
|
||||
center_->visible_ = false;
|
||||
center_->color = glm::vec4(COLOR_MIXING_GROUP, 0.75f);
|
||||
center_->scale_ = glm::vec3(0.6f, 0.6f, 1.f);
|
||||
root_->attach(center_);
|
||||
|
||||
// create
|
||||
recenter();
|
||||
createLineStrip();
|
||||
}
|
||||
|
||||
MixingGroup::~MixingGroup ()
|
||||
{
|
||||
for (auto it = sources_.begin(); it != sources_.end(); ++it)
|
||||
(*it)->clearMixingGroup();
|
||||
|
||||
if (parent_)
|
||||
parent_->detach( root_ );
|
||||
delete root_;
|
||||
}
|
||||
|
||||
void MixingGroup::accept(Visitor& v)
|
||||
{
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
void MixingGroup::attachTo( Group *parent )
|
||||
{
|
||||
if (parent_ != nullptr)
|
||||
parent_->detach( root_ );
|
||||
|
||||
parent_ = parent;
|
||||
|
||||
if (parent_ != nullptr)
|
||||
parent_->attach(root_);
|
||||
}
|
||||
|
||||
void MixingGroup::recenter()
|
||||
{
|
||||
// compute barycenter (0)
|
||||
center_pos_ = glm::vec2(0.f, 0.f);
|
||||
for (auto it = sources_.begin(); it != sources_.end(); ++it){
|
||||
// compute barycenter (1)
|
||||
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
}
|
||||
// compute barycenter (2)
|
||||
center_pos_ /= sources_.size();
|
||||
|
||||
// set center
|
||||
center_->translation_ = glm::vec3(center_pos_, 0.f);
|
||||
}
|
||||
|
||||
void MixingGroup::setAction (Action a)
|
||||
{
|
||||
if (a == ACTION_UPDATE) {
|
||||
// accept UPDATE action only if no other action is ongoing
|
||||
if (update_action_ == ACTION_NONE)
|
||||
update_action_ = ACTION_UPDATE;
|
||||
}
|
||||
else if (a == ACTION_FINISH) {
|
||||
// only needs to finish if an action was done
|
||||
if (update_action_ != ACTION_NONE)
|
||||
update_action_ = ACTION_FINISH;
|
||||
}
|
||||
else
|
||||
update_action_ = a;
|
||||
}
|
||||
|
||||
void MixingGroup::update (float)
|
||||
{
|
||||
// after creation, root is not visible: wait that all sources are initialized to make it visible
|
||||
if (!root_->visible_) {
|
||||
auto unintitializedsource = std::find_if_not(sources_.begin(), sources_.end(), Source::isInitialized);
|
||||
root_->visible_ = (unintitializedsource == sources_.end());
|
||||
}
|
||||
|
||||
// group is active if one source in the group is current
|
||||
auto currentsource = std::find_if(sources_.begin(), sources_.end(), Source::isCurrent);
|
||||
setActive(currentsource != sources_.end());
|
||||
|
||||
// perform action
|
||||
if (update_action_ == ACTION_FINISH ) {
|
||||
// update barycenter
|
||||
recenter();
|
||||
// clear index, delete lines_, and recreate path and index with remaining sources
|
||||
createLineStrip();
|
||||
// update only once
|
||||
update_action_ = ACTION_NONE;
|
||||
}
|
||||
else if (update_action_ == ACTION_UPDATE ) {
|
||||
|
||||
std::vector<glm::vec2> p = lines_->path();
|
||||
|
||||
// compute barycenter (0)
|
||||
center_pos_ = glm::vec2(0.f, 0.f);
|
||||
auto it = sources_.begin();
|
||||
for (; it != sources_.end(); ++it){
|
||||
// update point
|
||||
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
|
||||
// compute barycenter (1)
|
||||
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
}
|
||||
// compute barycenter (2)
|
||||
center_pos_ /= sources_.size();
|
||||
center_->translation_ = glm::vec3(center_pos_, 0.f);
|
||||
|
||||
// update path
|
||||
lines_->changePath(p);
|
||||
|
||||
// update only once
|
||||
update_action_ = ACTION_NONE;
|
||||
}
|
||||
else if (update_action_ != ACTION_NONE && updated_source_ != nullptr) {
|
||||
|
||||
if (update_action_ == ACTION_GRAB_ONE ) {
|
||||
|
||||
// update path
|
||||
move(updated_source_);
|
||||
recenter();
|
||||
}
|
||||
else if (update_action_ == ACTION_GRAB_ALL ) {
|
||||
|
||||
std::vector<glm::vec2> p = lines_->path();
|
||||
glm::vec2 displacement = glm::vec2(updated_source_->group(View::MIXING)->translation_);
|
||||
displacement -= p[ index_points_[updated_source_] ];
|
||||
|
||||
// compute barycenter (0)
|
||||
center_pos_ = glm::vec2(0.f, 0.f);
|
||||
auto it = sources_.begin();
|
||||
for (; it != sources_.end(); ++it){
|
||||
|
||||
// modify all but the already updated source
|
||||
if ( *it != updated_source_ && !(*it)->locked() ) {
|
||||
(*it)->group(View::MIXING)->translation_.x += displacement.x;
|
||||
(*it)->group(View::MIXING)->translation_.y += displacement.y;
|
||||
(*it)->touch();
|
||||
}
|
||||
|
||||
// update point
|
||||
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
|
||||
// compute barycenter (1)
|
||||
center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
}
|
||||
// compute barycenter (2)
|
||||
center_pos_ /= sources_.size();
|
||||
center_->translation_ = glm::vec3(center_pos_, 0.f);
|
||||
|
||||
// update path
|
||||
lines_->changePath(p);
|
||||
}
|
||||
else if (update_action_ == ACTION_ROTATE_ALL ) {
|
||||
|
||||
std::vector<glm::vec2> p = lines_->path();
|
||||
|
||||
// get angle rotation and distance scaling
|
||||
glm::vec2 pos_first = glm::vec2(updated_source_->group(View::MIXING)->translation_) -center_pos_;
|
||||
float angle = glm::orientedAngle( glm::normalize(pos_first), glm::vec2(1.f, 0.f) );
|
||||
float dist = glm::length( pos_first );
|
||||
glm::vec2 pos_second = glm::vec2(p[ index_points_[updated_source_] ]) -center_pos_;
|
||||
angle -= glm::orientedAngle( glm::normalize(pos_second), glm::vec2(1.f, 0.f) );
|
||||
dist /= glm::length( pos_second );
|
||||
|
||||
int numactions = 0;
|
||||
auto it = sources_.begin();
|
||||
for (; it != sources_.end(); ++it){
|
||||
|
||||
// modify all but the already updated source
|
||||
if ( *it != updated_source_ && !(*it)->locked() ) {
|
||||
glm::vec2 vec = glm::vec2((*it)->group(View::MIXING)->translation_) -center_pos_;
|
||||
vec = glm::rotate(vec, -angle) * dist;
|
||||
vec += center_pos_;
|
||||
|
||||
(*it)->group(View::MIXING)->translation_.x = vec.x;
|
||||
(*it)->group(View::MIXING)->translation_.y = vec.y;
|
||||
(*it)->touch();
|
||||
numactions++;
|
||||
}
|
||||
|
||||
// update point
|
||||
p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
}
|
||||
// update path
|
||||
lines_->changePath(p);
|
||||
// no source was rotated? better action is thus to grab
|
||||
if (numactions<1)
|
||||
update_action_ = ACTION_GRAB_ALL;
|
||||
}
|
||||
|
||||
// done
|
||||
updated_source_ = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MixingGroup::setActive (bool on)
|
||||
{
|
||||
active_ = on;
|
||||
|
||||
// overlays
|
||||
lines_->shader()->color.a = active_ ? 0.96f : 0.5f;
|
||||
center_->visible_ = update_action_ > ACTION_GRAB_ONE;
|
||||
}
|
||||
|
||||
void MixingGroup::detach (Source *s)
|
||||
{
|
||||
// find the source
|
||||
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), s);
|
||||
// ok, its in the list !
|
||||
if (its != sources_.end()) {
|
||||
// tell the source
|
||||
(*its)->clearMixingGroup();
|
||||
// erase the source from the list
|
||||
sources_.erase(its);
|
||||
// update barycenter
|
||||
recenter();
|
||||
// clear index, delete lines_, and recreate path and index with remaining sources
|
||||
createLineStrip();
|
||||
}
|
||||
}
|
||||
|
||||
void MixingGroup::detach (SourceList l)
|
||||
{
|
||||
for (auto sit = l.begin(); sit != l.end(); ++sit) {
|
||||
// find the source
|
||||
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), *sit);
|
||||
// ok, its in the list !
|
||||
if (its != sources_.end()) {
|
||||
// tell the source
|
||||
(*its)->clearMixingGroup();
|
||||
// erase the source from the list
|
||||
sources_.erase(its);
|
||||
}
|
||||
}
|
||||
// update barycenter
|
||||
recenter();
|
||||
// clear index, delete lines_, and recreate path and index with remaining sources
|
||||
createLineStrip();
|
||||
}
|
||||
|
||||
|
||||
void MixingGroup::attach (Source *s)
|
||||
{
|
||||
// if source is not already in a group (this or other)
|
||||
if (s->mixinggroup_ == nullptr) {
|
||||
// tell the source
|
||||
s->mixinggroup_ = this;
|
||||
// add the source
|
||||
sources_.push_back(s);
|
||||
// update barycenter
|
||||
recenter();
|
||||
// clear index, delete lines_, and recreate path and index with remaining sources
|
||||
createLineStrip();
|
||||
}
|
||||
}
|
||||
|
||||
void MixingGroup::attach (SourceList l)
|
||||
{
|
||||
for (auto sit = l.begin(); sit != l.end(); ++sit) {
|
||||
if ( (*sit)->mixinggroup_ == nullptr) {
|
||||
// tell the source
|
||||
(*sit)->mixinggroup_ = this;
|
||||
// add the source
|
||||
sources_.push_back(*sit);
|
||||
}
|
||||
}
|
||||
// update barycenter
|
||||
recenter();
|
||||
// clear index, delete lines_, and recreate path and index with remaining sources
|
||||
createLineStrip();
|
||||
}
|
||||
|
||||
uint MixingGroup::size()
|
||||
{
|
||||
return sources_.size();
|
||||
}
|
||||
|
||||
SourceList MixingGroup::getCopy() const
|
||||
{
|
||||
SourceList sl = sources_;
|
||||
return sl;
|
||||
}
|
||||
|
||||
SourceList::iterator MixingGroup::begin ()
|
||||
{
|
||||
return sources_.begin();
|
||||
}
|
||||
|
||||
SourceList::iterator MixingGroup::end ()
|
||||
{
|
||||
return sources_.end();
|
||||
}
|
||||
|
||||
bool MixingGroup::contains (Source *s)
|
||||
{
|
||||
// find the source
|
||||
SourceList::iterator its = std::find(sources_.begin(), sources_.end(), s);
|
||||
// in the list ?
|
||||
return its != sources_.end();
|
||||
}
|
||||
|
||||
void MixingGroup::move (Source *s)
|
||||
{
|
||||
if (contains(s) && lines_) {
|
||||
// modify one point in the path
|
||||
lines_->editPath(index_points_[s], glm::vec2(s->group(View::MIXING)->translation_));
|
||||
}
|
||||
}
|
||||
|
||||
void MixingGroup::createLineStrip()
|
||||
{
|
||||
if (sources_.size() > 1) {
|
||||
|
||||
if (lines_) {
|
||||
root_->detach(lines_);
|
||||
delete lines_;
|
||||
}
|
||||
|
||||
// sort the vector of sources in clockwise order around the center pos_
|
||||
sources_ = mixing_sorted( sources_, center_pos_);
|
||||
|
||||
// start afresh list of indices
|
||||
index_points_.clear();
|
||||
|
||||
// path linking all sources
|
||||
std::vector<glm::vec2> path;
|
||||
for (auto it = sources_.begin(); it != sources_.end(); ++it){
|
||||
index_points_[*it] = path.size();
|
||||
path.push_back(glm::vec2((*it)->group(View::MIXING)->translation_));
|
||||
}
|
||||
|
||||
// create
|
||||
lines_ = new LineLoop(path, 1.5f);
|
||||
lines_->shader()->color = glm::vec4(COLOR_MIXING_GROUP, 0.96f);
|
||||
root_->attach(lines_);
|
||||
}
|
||||
}
|
||||
86
MixingGroup.h
Normal file
86
MixingGroup.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#ifndef MIXINGGROUP_H
|
||||
#define MIXINGGROUP_H
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "View.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
class LineLoop;
|
||||
class Symbol;
|
||||
|
||||
class MixingGroup
|
||||
{
|
||||
|
||||
public:
|
||||
MixingGroup (SourceList sources);
|
||||
// non assignable class
|
||||
MixingGroup(MixingGroup const&) = delete;
|
||||
MixingGroup& operator=(MixingGroup const&) = delete;
|
||||
virtual ~MixingGroup ();
|
||||
|
||||
// Get unique id
|
||||
inline uint64_t id () const { return id_; }
|
||||
|
||||
// Source list manipulation
|
||||
SourceList getCopy() const;
|
||||
SourceList::iterator begin ();
|
||||
SourceList::iterator end ();
|
||||
uint size ();
|
||||
bool contains (Source *s);
|
||||
void detach (Source *s);
|
||||
void detach (SourceList l);
|
||||
void attach (Source *s);
|
||||
void attach (SourceList l);
|
||||
|
||||
// actions for update
|
||||
typedef enum {
|
||||
ACTION_NONE = 0,
|
||||
ACTION_UPDATE = 1,
|
||||
ACTION_GRAB_ONE = 2,
|
||||
ACTION_GRAB_ALL = 3,
|
||||
ACTION_ROTATE_ALL = 4,
|
||||
ACTION_FINISH = 5
|
||||
} Action;
|
||||
void setAction (Action a) ;
|
||||
inline Action action () { return update_action_; }
|
||||
inline void follow (Source *s) { updated_source_ = s; }
|
||||
|
||||
// to update in Session
|
||||
void update (float);
|
||||
|
||||
// active if one of its source is current
|
||||
inline bool active () const { return active_; }
|
||||
void setActive (bool on);
|
||||
|
||||
// attach node to draw in Mixing View
|
||||
void attachTo( Group *parent );
|
||||
|
||||
// accept all kind of visitors
|
||||
virtual void accept (Visitor& v);
|
||||
|
||||
private:
|
||||
|
||||
// Drawing elements
|
||||
Group *parent_;
|
||||
Group *root_;
|
||||
LineLoop *lines_;
|
||||
Symbol *center_;
|
||||
void createLineStrip();
|
||||
void recenter();
|
||||
void move (Source *s);
|
||||
|
||||
// properties linked to sources
|
||||
glm::vec2 center_pos_;
|
||||
SourceList sources_;
|
||||
std::map< Source *, uint> index_points_;
|
||||
|
||||
// status and actions
|
||||
uint64_t id_;
|
||||
bool active_;
|
||||
Action update_action_;
|
||||
Source *updated_source_;
|
||||
|
||||
};
|
||||
|
||||
#endif // MIXINGGROUP_H
|
||||
777
MixingView.cpp
Normal file
777
MixingView.cpp
Normal file
@@ -0,0 +1,777 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#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 "ImGuiToolkit.h"
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "defines.h"
|
||||
#include "Source.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_MIN_THRESHOLD)
|
||||
{
|
||||
scene.root()->scale_ = glm::vec3(MIXING_DEFAULT_SCALE, MIXING_DEFAULT_SCALE, 1.0f);
|
||||
scene.root()->translation_ = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
// read default settings
|
||||
if ( Settings::application.views[mode_].name.empty() ) {
|
||||
// no settings found: store application default
|
||||
Settings::application.views[mode_].name = "Mixing";
|
||||
saveSettings();
|
||||
}
|
||||
else
|
||||
restoreSettings();
|
||||
|
||||
// Mixing scene background
|
||||
limbo_ = new Mesh("mesh/disk.ply");
|
||||
limbo_->scale_ = glm::vec3(limbo_scale_, limbo_scale_, 1.f);
|
||||
limbo_->shader()->color = glm::vec4( COLOR_LIMBO_CIRCLE, 1.f );
|
||||
scene.bg()->attach(limbo_);
|
||||
|
||||
// interactive limbo scaling slider
|
||||
limbo_slider_root_ = new Group;
|
||||
limbo_slider_root_->translation_ = glm::vec3(0.0f, limbo_scale_, 0.f);
|
||||
scene.bg()->attach(limbo_slider_root_);
|
||||
limbo_slider_ = new Disk();
|
||||
limbo_slider_->translation_ = glm::vec3(0.f, -0.01f, 0.f);
|
||||
limbo_slider_->scale_ = glm::vec3(0.1f, 0.1f, 1.f);
|
||||
limbo_slider_->color = glm::vec4( COLOR_LIMBO_CIRCLE, 1.f );
|
||||
limbo_slider_root_->attach(limbo_slider_);
|
||||
limbo_up_ = new Mesh("mesh/triangle_point.ply");
|
||||
limbo_up_->scale_ = glm::vec3(0.8f, 0.8f, 1.f);
|
||||
limbo_up_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, 0.01f );
|
||||
limbo_slider_root_->attach(limbo_up_);
|
||||
limbo_down_ = new Mesh("mesh/triangle_point.ply");
|
||||
limbo_down_->translation_ = glm::vec3(0.f, -0.02f, 0.f);
|
||||
limbo_down_->scale_ = glm::vec3(0.8f, 0.8f, 1.f);
|
||||
limbo_down_->rotation_ = glm::vec3(0.f, 0.f, M_PI);
|
||||
limbo_down_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, 0.01f );
|
||||
limbo_slider_root_->attach(limbo_down_);
|
||||
|
||||
mixingCircle_ = new Mesh("mesh/disk.ply");
|
||||
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 (non interactive; no picking detected on Mesh)
|
||||
Mesh *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()
|
||||
{
|
||||
// set texture
|
||||
if (mixingCircle_->texture() == 0)
|
||||
mixingCircle_->setTexture(textureMixingQuadratic());
|
||||
|
||||
// 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 " Dispatch" )){
|
||||
glm::vec2 center = glm::vec2(0.f, 0.f);
|
||||
// distribute with equal angle
|
||||
float angle = 0.f;
|
||||
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
||||
|
||||
glm::vec2 P = center + glm::rotate(glm::vec2(0.f, 1.2), 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(Mixer::selection().size());
|
||||
}
|
||||
Action::manager().store(std::string("Selection: Mixing Dispatch"));
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_FAN " 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 in circle"));
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_ELLIPSIS_V " Align & 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();
|
||||
// distribute with equal angle
|
||||
float i = 0.f;
|
||||
float sign = -1.f;
|
||||
for (SourceList::iterator it = list.begin(); it != list.end(); ++it, sign*=-1.f) {
|
||||
(*it)->group(View::MIXING)->translation_.x = center.x;
|
||||
(*it)->group(View::MIXING)->translation_.y = center.y + sign * i;
|
||||
if (sign<0) i+=0.32f;
|
||||
(*it)->touch();
|
||||
}
|
||||
Action::manager().store(std::string("Selection: Mixing Align & Distribute"));
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_CLOUD_SUN " Expand & hide" )){
|
||||
SourceList::iterator it = Mixer::selection().begin();
|
||||
for (; it != Mixer::selection().end(); ++it) {
|
||||
(*it)->call( new 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)->call( new SetAlpha(0.999f) );
|
||||
}
|
||||
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::vec2 res = resolution();
|
||||
glm::vec3 border(2.3f * res.x/res.y, 2.3f, 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)
|
||||
{
|
||||
// calculate screen area visible in the default view
|
||||
GlmToolkit::AxisAlignedBoundingBox view_box;
|
||||
glm::mat4 modelview = GlmToolkit::transform(scene.root()->translation_, scene.root()->rotation_, scene.root()->scale_);
|
||||
view_box.extend( Rendering::manager().unProject(glm::vec2(0.f, Rendering::manager().mainWindow().height()), modelview) );
|
||||
view_box.extend( Rendering::manager().unProject(glm::vec2(Rendering::manager().mainWindow().width(), 0.f), modelview) );
|
||||
|
||||
// check if upper-left corner of source is in view box
|
||||
glm::vec3 pos_source = s->group(mode_)->translation_ + glm::vec3( -s->group(mode_)->scale_.x, s->group(mode_)->scale_.y, 0.f);
|
||||
if ( !view_box.contains(pos_source)) {
|
||||
// not visible so shift view
|
||||
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::vec4 pos_delta = glm::vec4(pos_to.x, pos_to.y, 0.f, 0.f) - glm::vec4(pos_source.x, pos_source.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 limbo scale according to session
|
||||
//
|
||||
const float p = CLAMP(Mixer::manager().session()->activationThreshold(), MIXING_MIN_THRESHOLD, MIXING_MAX_THRESHOLD);
|
||||
limbo_slider_root_->translation_.y = p;
|
||||
limbo_->scale_ = glm::vec3(p, p, 1.f);
|
||||
|
||||
//
|
||||
// prevent invalid scaling
|
||||
//
|
||||
float s = CLAMP(scene.root()->scale_.x, MIXING_MIN_SCALE, MIXING_MAX_SCALE);
|
||||
scene.root()->scale_.x = s;
|
||||
scene.root()->scale_.y = s;
|
||||
}
|
||||
|
||||
// the current view is the mixing view
|
||||
if (Mixer::manager().view() == this ){
|
||||
|
||||
//
|
||||
// Set slider to match the actual fading of the session
|
||||
//
|
||||
float 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);
|
||||
|
||||
// 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_ ) {
|
||||
|
||||
// animate clic
|
||||
pick.first->update_callbacks_.push_back(new BounceScaleCallback(0.3f));
|
||||
|
||||
// animated fading in session
|
||||
if (pick.first == button_white_)
|
||||
Mixer::manager().session()->setFadingTarget(0.f, 500.f);
|
||||
else
|
||||
Mixer::manager().session()->setFadingTarget(1.f, 500.f);
|
||||
|
||||
}
|
||||
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 ( UserInterface::manager().ctrlModifier() && pick.first == s->lock_) {
|
||||
lock(s, false);
|
||||
// pick = { s->locker_, pick.second };
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick on the open lock icon; lock source and cancel pick
|
||||
else if ( UserInterface::manager().ctrlModifier() && 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_ ) {
|
||||
if (UserInterface::manager().shiftModifier())
|
||||
s->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ONE );
|
||||
else
|
||||
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;
|
||||
|
||||
// calculate fading from angle
|
||||
float f = sin( ABS(angle) * 0.5f);
|
||||
Mixer::manager().session()->setFadingTarget(f);
|
||||
|
||||
// cursor feedback
|
||||
slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f );
|
||||
std::ostringstream info;
|
||||
info << "Output " << 100 - int(f * 100.0) << " %";
|
||||
return Cursor(Cursor_Hand, info.str() );
|
||||
}
|
||||
else if (pick.first == limbo_slider_) {
|
||||
|
||||
// move slider scaling limbo area
|
||||
const float p = CLAMP(gl_Position_to.y, MIXING_MIN_THRESHOLD, MIXING_MAX_THRESHOLD);
|
||||
limbo_slider_root_->translation_.y = p;
|
||||
limbo_->scale_ = glm::vec3(p, p, 1.f);
|
||||
Mixer::manager().session()->setActivationThreshold(p);
|
||||
|
||||
// color change of arrow indicators
|
||||
limbo_up_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, p < MIXING_MAX_THRESHOLD ? 0.15f : 0.01f );
|
||||
limbo_down_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, p > MIXING_MIN_THRESHOLD ? 0.15f : 0.01f );
|
||||
|
||||
std::ostringstream info;
|
||||
info << ICON_FA_SNOWFLAKE " Deactivation limit";
|
||||
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);
|
||||
// special cursor for rotation
|
||||
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 );
|
||||
|
||||
if ( pick.first == limbo_slider_ ) {
|
||||
limbo_up_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, limbo_slider_root_->translation_.y < MIXING_MAX_THRESHOLD ? 0.1f : 0.01f );
|
||||
limbo_down_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, limbo_slider_root_->translation_.y > MIXING_MIN_THRESHOLD ? 0.1f : 0.01f );
|
||||
ret.type = Cursor_Hand;
|
||||
}
|
||||
else {
|
||||
limbo_up_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, 0.01f );
|
||||
limbo_down_->shader()->color = glm::vec4( COLOR_CIRCLE_OVER, 0.01f );
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MixingView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static float accumulator = 0.f;
|
||||
accumulator += dt_;
|
||||
|
||||
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 > 100.f) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.1f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
dest_translation.y = ROUND(dest_translation.y, 10.f);
|
||||
accumulator = 0.f;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
accumulator = 0.f;
|
||||
}
|
||||
|
||||
// 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.f
|
||||
//#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];
|
||||
int l = -CIRCLE_PIXELS / 2 + 1;
|
||||
|
||||
for (int i = 0; i < CIRCLE_PIXELS ; ++i) {
|
||||
int c = -CIRCLE_PIXELS / 2 + 1;
|
||||
for (int j = 0; j < CIRCLE_PIXELS ; ++j) {
|
||||
// distance to the center
|
||||
GLfloat 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
|
||||
GLfloat alpha = 255.f * CLAMP( distance , 0.f, 1.f);
|
||||
GLubyte A = static_cast<GLubyte>(alpha);
|
||||
|
||||
// luminance adjustment
|
||||
GLfloat luminance = 255.f * CLAMP( 0.2f + 0.75f * distance, 0.f, 1.f);
|
||||
GLubyte L = static_cast<GLubyte>(luminance);
|
||||
|
||||
// fill pixel RGBA
|
||||
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 0 ] = L;
|
||||
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 1 ] = L;
|
||||
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 2 ] = L;
|
||||
matrix[ i * CIRCLE_PIXELS * 4 + j * 4 + 3 ] = A;
|
||||
|
||||
++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);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
return texid;
|
||||
}
|
||||
49
MixingView.h
Normal file
49
MixingView.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef MIXINGVIEW_H
|
||||
#define MIXINGVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
//class MixingGroup;
|
||||
|
||||
class MixingView : public View
|
||||
{
|
||||
public:
|
||||
MixingView();
|
||||
// non assignable class
|
||||
MixingView(MixingView const&) = delete;
|
||||
MixingView& operator=(MixingView const&) = delete;
|
||||
|
||||
void draw () override;
|
||||
void update (float dt) override;
|
||||
void resize (int) override;
|
||||
int size () override;
|
||||
void centerSource(Source *) override;
|
||||
|
||||
std::pair<Node *, glm::vec2> pick(glm::vec2) override;
|
||||
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2>) override;
|
||||
void terminate() override;
|
||||
Cursor over (glm::vec2) override;
|
||||
void arrow (glm::vec2) override;
|
||||
|
||||
void setAlpha (Source *s);
|
||||
|
||||
private:
|
||||
void updateSelectionOverlay() override;
|
||||
|
||||
float limbo_scale_;
|
||||
|
||||
Group *slider_root_;
|
||||
Disk *slider_;
|
||||
Disk *button_white_;
|
||||
Disk *button_black_;
|
||||
// Disk *stashCircle_;
|
||||
Mesh *mixingCircle_;
|
||||
Mesh *circle_;
|
||||
Mesh *limbo_;
|
||||
Group *limbo_slider_root_;
|
||||
Mesh *limbo_up_, *limbo_down_;
|
||||
Disk *limbo_slider_;
|
||||
};
|
||||
|
||||
|
||||
#endif // MIXINGVIEW_H
|
||||
272
MultiFileSource.cpp
Normal file
272
MultiFileSource.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Decorations.h"
|
||||
#include "Stream.h"
|
||||
#include "Visitor.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "MultiFileSource.h"
|
||||
|
||||
// example test gstreamer pipelines
|
||||
//
|
||||
// multifile : sequence of numbered images
|
||||
// gst-launch-1.0 multifilesrc location="/home/bhbn/Images/sequence/frames%03d.png" caps="image/png,framerate=\(fraction\)12/1" loop=1 ! decodebin ! videoconvert ! autovideosink
|
||||
//
|
||||
// imagesequencesrc : sequence of numbered images (cannot loop)
|
||||
// gst-launch-1.0 imagesequencesrc location=frames%03d.png start-index=1 framerate=24/1 ! decodebin ! videoconvert ! autovideosink
|
||||
//
|
||||
|
||||
MultiFileSequence::MultiFileSequence() : width(0), height(0), min(0), max(0)
|
||||
{
|
||||
}
|
||||
|
||||
MultiFileSequence::MultiFileSequence(const std::list<std::string> &list_files)
|
||||
{
|
||||
location = BaseToolkit::common_numbered_pattern(list_files, &min, &max);
|
||||
|
||||
// sanity check: the location pattern looks like a filename and seems consecutive numbered
|
||||
if ( SystemToolkit::extension_filename(location).empty() ||
|
||||
SystemToolkit::path_filename(location) != SystemToolkit::path_filename(list_files.front()) ||
|
||||
list_files.size() != (size_t) (max - min) + 1 ) {
|
||||
Log::Info("MultiFileSequence '%s' invalid.", location.c_str());
|
||||
location.clear();
|
||||
}
|
||||
|
||||
if ( !location.empty() ) {
|
||||
MediaInfo media = MediaPlayer::UriDiscoverer( GstToolkit::filename_to_uri( list_files.front() ) );
|
||||
if (media.valid && media.isimage) {
|
||||
codec.resize(media.codec_name.size());
|
||||
std::transform(media.codec_name.begin(), media.codec_name.end(), codec.begin(), ::tolower);
|
||||
width = media.width;
|
||||
height = media.height;
|
||||
}
|
||||
else
|
||||
Log::Info("MultiFileSequence '%s' does not list images.", location.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiFileSequence::valid() const
|
||||
{
|
||||
return !( location.empty() || codec.empty() || width < 1 || height < 1 || max == min);
|
||||
}
|
||||
|
||||
inline MultiFileSequence& MultiFileSequence::operator = (const MultiFileSequence& b)
|
||||
{
|
||||
if (this != &b) {
|
||||
this->width = b.width;
|
||||
this->height = b.height;
|
||||
this->min = b.min;
|
||||
this->max = b.max;
|
||||
this->location = b.location;
|
||||
this->codec = b.codec;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
bool MultiFileSequence::operator != (const MultiFileSequence& b)
|
||||
{
|
||||
return ( location != b.location || codec != b.codec || width != b.width ||
|
||||
height != b.height || min != b.min || max != b.max );
|
||||
}
|
||||
|
||||
MultiFile::MultiFile() : Stream(), src_(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MultiFile::open (const MultiFileSequence &sequence, uint framerate )
|
||||
{
|
||||
if (sequence.location.empty())
|
||||
return;
|
||||
|
||||
std::ostringstream gstreamer_pipeline;
|
||||
gstreamer_pipeline << "multifilesrc name=src location=\"";
|
||||
gstreamer_pipeline << sequence.location;
|
||||
gstreamer_pipeline << "\" caps=\"image/";
|
||||
gstreamer_pipeline << sequence.codec;
|
||||
gstreamer_pipeline << ",framerate=(fraction)";
|
||||
gstreamer_pipeline << framerate;
|
||||
gstreamer_pipeline << "/1\" loop=1";
|
||||
gstreamer_pipeline << " start-index=";
|
||||
gstreamer_pipeline << sequence.min;
|
||||
gstreamer_pipeline << " stop-index=";
|
||||
gstreamer_pipeline << sequence.max;
|
||||
gstreamer_pipeline << " ! decodebin ! videoconvert";
|
||||
|
||||
// (private) open stream
|
||||
Stream::open(gstreamer_pipeline.str(), sequence.width, sequence.height);
|
||||
|
||||
// keep multifile source for dynamic properties change
|
||||
src_ = gst_bin_get_by_name (GST_BIN (pipeline_), "src");
|
||||
}
|
||||
|
||||
void MultiFile::close ()
|
||||
{
|
||||
if (src_ != nullptr) {
|
||||
gst_object_unref (src_);
|
||||
src_ = nullptr;
|
||||
}
|
||||
Stream::close();
|
||||
}
|
||||
|
||||
void MultiFile::setIndex(int val)
|
||||
{
|
||||
if (src_) {
|
||||
g_object_set (src_, "index", val, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
int MultiFile::index()
|
||||
{
|
||||
int val = 0;
|
||||
if (src_) {
|
||||
g_object_get (src_, "index", &val, NULL);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void MultiFile::setProperties (int begin, int end, int loop)
|
||||
{
|
||||
if (src_) {
|
||||
g_object_set (src_, "start-index", MAX(begin, 0), NULL);
|
||||
g_object_set (src_, "stop-index", MAX(end, 0), NULL);
|
||||
g_object_set (src_, "loop", MIN(loop, 1), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
MultiFileSource::MultiFileSource (uint64_t id) : StreamSource(id), framerate_(0), begin_(-1), end_(INT_MAX), loop_(1)
|
||||
{
|
||||
// create stream
|
||||
stream_ = static_cast<Stream *>( new MultiFile );
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::SEQUENCE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
|
||||
void MultiFileSource::setFiles (const std::list<std::string> &list_files, uint framerate)
|
||||
{
|
||||
setSequence(MultiFileSequence(list_files), framerate);
|
||||
}
|
||||
|
||||
void MultiFileSource::setSequence (const MultiFileSequence &sequence, uint framerate)
|
||||
{
|
||||
framerate_ = CLAMP( framerate, 1, 30);
|
||||
sequence_ = sequence;
|
||||
|
||||
if (sequence_.valid())
|
||||
{
|
||||
// open gstreamer
|
||||
multifile()->open( sequence_, framerate_ );
|
||||
stream_->play(true);
|
||||
|
||||
// validate range and apply loop_
|
||||
setRange(begin_, end_);
|
||||
|
||||
// will be ready after init and one frame rendered
|
||||
ready_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiFileSource::setFramerate (uint framerate)
|
||||
{
|
||||
if (multifile()) {
|
||||
setSequence(sequence_, framerate);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiFileSource::setLoop (bool on)
|
||||
{
|
||||
if (multifile()) {
|
||||
loop_ = on ? 1 : 0;
|
||||
multifile()->setProperties (begin_, end_, loop_);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiFileSource::setRange (int begin, int end)
|
||||
{
|
||||
begin_ = glm::clamp( begin, sequence_.min, sequence_.max );
|
||||
end_ = glm::clamp( end , sequence_.min, sequence_.max );
|
||||
begin_ = glm::min( begin_, end_ );
|
||||
end_ = glm::max( begin_, end_ );
|
||||
|
||||
if (multifile())
|
||||
multifile()->setProperties (begin_, end_, loop_);
|
||||
}
|
||||
|
||||
void MultiFileSource::replay ()
|
||||
{
|
||||
if (multifile()) {
|
||||
multifile()->setIndex (begin_);
|
||||
stream_->rewind();
|
||||
}
|
||||
}
|
||||
|
||||
guint64 MultiFileSource::playtime () const
|
||||
{
|
||||
guint64 time = 0;
|
||||
|
||||
if (multifile())
|
||||
time += multifile()->index();
|
||||
|
||||
time *= GST_SECOND;
|
||||
time /= framerate_;
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
void MultiFileSource::accept (Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
MultiFile *MultiFileSource::multifile () const
|
||||
{
|
||||
return dynamic_cast<MultiFile *>(stream_);
|
||||
}
|
||||
|
||||
|
||||
glm::ivec2 MultiFileSource::icon () const
|
||||
{
|
||||
return glm::ivec2(ICON_SOURCE_SEQUENCE);
|
||||
}
|
||||
|
||||
std::string MultiFileSource::info() const
|
||||
{
|
||||
return std::string("sequence '") + sequence_.location + "'";
|
||||
}
|
||||
|
||||
|
||||
83
MultiFileSource.h
Normal file
83
MultiFileSource.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#ifndef MULTIFILESOURCE_H
|
||||
#define MULTIFILESOURCE_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include "StreamSource.h"
|
||||
|
||||
struct MultiFileSequence {
|
||||
std::string location;
|
||||
std::string codec;
|
||||
uint width;
|
||||
uint height;
|
||||
int min;
|
||||
int max;
|
||||
|
||||
MultiFileSequence ();
|
||||
MultiFileSequence (const std::list<std::string> &list_files);
|
||||
bool valid () const;
|
||||
MultiFileSequence& operator = (const MultiFileSequence& b);
|
||||
bool operator != (const MultiFileSequence& b);
|
||||
};
|
||||
|
||||
class MultiFile : public Stream
|
||||
{
|
||||
public:
|
||||
MultiFile ();
|
||||
void open (const MultiFileSequence &sequence, uint framerate = 30);
|
||||
void close () override;
|
||||
|
||||
// dynamic change of gstreamer multifile source properties
|
||||
void setProperties(int begin, int end, int loop);
|
||||
|
||||
// image index
|
||||
int index();
|
||||
void setIndex(int val);
|
||||
|
||||
protected:
|
||||
GstElement *src_ ;
|
||||
};
|
||||
|
||||
class MultiFileSource : public StreamSource
|
||||
{
|
||||
public:
|
||||
MultiFileSource (uint64_t id = 0);
|
||||
|
||||
// Source interface
|
||||
void accept (Visitor& v) override;
|
||||
void replay () override;
|
||||
guint64 playtime () const override;
|
||||
|
||||
// StreamSource interface
|
||||
Stream *stream () const override { return stream_; }
|
||||
glm::ivec2 icon() const override;
|
||||
std::string info() const override;
|
||||
|
||||
// specific interface
|
||||
void setFiles (const std::list<std::string> &list_files, uint framerate);
|
||||
|
||||
void setSequence (const MultiFileSequence &sequence, uint framerate);
|
||||
inline MultiFileSequence sequence () const { return sequence_; }
|
||||
|
||||
void setFramerate (uint fps);
|
||||
inline uint framerate () const { return framerate_; }
|
||||
|
||||
void setLoop (bool on);
|
||||
inline bool loop () const { return loop_ > 0 ? true : false; }
|
||||
|
||||
void setRange (int begin, int end);
|
||||
inline int begin() const { return begin_; }
|
||||
inline int end () const { return end_; }
|
||||
|
||||
MultiFile *multifile () const;
|
||||
|
||||
private:
|
||||
MultiFileSequence sequence_;
|
||||
uint framerate_;
|
||||
int begin_, end_, loop_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // MULTIFILESOURCE_H
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
@@ -5,17 +24,17 @@
|
||||
#include <future>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "osc/OscOutboundPacketStream.h"
|
||||
|
||||
#include "SystemToolkit.h"
|
||||
#include "defines.h"
|
||||
#include "Stream.h"
|
||||
#include "Decorations.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
#include "Connection.h"
|
||||
|
||||
#include "NetworkSource.h"
|
||||
|
||||
@@ -25,7 +44,7 @@
|
||||
|
||||
|
||||
// this is called when receiving an answer for streaming request
|
||||
void StreamerResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
void NetworkStream::ResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint )
|
||||
{
|
||||
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
|
||||
@@ -67,10 +86,10 @@ void StreamerResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
}
|
||||
|
||||
|
||||
NetworkStream::NetworkStream(): Stream(), receiver_(nullptr)
|
||||
NetworkStream::NetworkStream(): Stream(),
|
||||
receiver_(nullptr), received_config_(false), connected_(false)
|
||||
{
|
||||
received_config_ = false;
|
||||
connected_ = false;
|
||||
|
||||
}
|
||||
|
||||
glm::ivec2 NetworkStream::resolution() const
|
||||
@@ -204,7 +223,7 @@ void NetworkStream::update()
|
||||
{
|
||||
Stream::update();
|
||||
|
||||
if ( !ready_ && !failed_ && received_config_)
|
||||
if ( !opened_ && !failed_ && received_config_)
|
||||
{
|
||||
// only once
|
||||
received_config_ = false;
|
||||
@@ -242,22 +261,34 @@ void NetworkStream::update()
|
||||
// general case : create pipeline and open
|
||||
if (!failed_) {
|
||||
// build the pipeline depending on stream info
|
||||
std::ostringstream pipeline;
|
||||
// get generic pipeline string
|
||||
std::string pipelinestring = NetworkToolkit::protocol_receive_pipeline[config_.protocol];
|
||||
// find placeholder for PORT
|
||||
int xxxx = pipelinestring.find("XXXX");
|
||||
// keep beginning of pipeline
|
||||
pipeline << pipelinestring.substr(0, xxxx);
|
||||
// Replace 'XXXX' by info on port config
|
||||
pipeline << parameter;
|
||||
// keep ending of pipeline
|
||||
pipeline << pipelinestring.substr(xxxx + 4);
|
||||
// add a videoconverter
|
||||
pipeline << " ! videoconvert";
|
||||
|
||||
// find placeholder for PORT or SHH socket
|
||||
size_t xxxx = pipelinestring.find("XXXX");
|
||||
if (xxxx != std::string::npos)
|
||||
// Replace 'XXXX' by info on port config
|
||||
pipelinestring.replace(xxxx, 4, parameter);
|
||||
|
||||
// find placeholder for WIDTH
|
||||
size_t wwww = pipelinestring.find("WWWW");
|
||||
if (wwww != std::string::npos)
|
||||
// Replace 'WWWW' by width
|
||||
pipelinestring.replace(wwww, 4, std::to_string(config_.width) );
|
||||
|
||||
// find placeholder for HEIGHT
|
||||
size_t hhhh = pipelinestring.find("HHHH");
|
||||
if (hhhh != std::string::npos)
|
||||
// Replace 'WWWW' by height
|
||||
pipelinestring.replace(hhhh, 4, std::to_string(config_.height) );
|
||||
|
||||
// add a videoconverter
|
||||
pipelinestring.append(" ! videoconvert ");
|
||||
|
||||
#ifdef NETWORK_DEBUG
|
||||
Log::Info("Openning pipeline %s", pipelinestring.c_str());
|
||||
#endif
|
||||
// open the pipeline with generic stream class
|
||||
Stream::open(pipeline.str(), config_.width, config_.height);
|
||||
Stream::open(pipelinestring, config_.width, config_.height);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -268,14 +299,14 @@ void NetworkStream::update()
|
||||
}
|
||||
|
||||
|
||||
NetworkSource::NetworkSource() : StreamSource()
|
||||
NetworkSource::NetworkSource(uint64_t id) : StreamSource(id)
|
||||
{
|
||||
// create stream
|
||||
stream_ = (Stream *) new NetworkStream;
|
||||
stream_ = static_cast<Stream *>( new NetworkStream );
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::SHARE, glm::vec3(0.8f, 0.8f, 0.01f));
|
||||
|
||||
symbol_ = new Symbol(Symbol::SHARE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
|
||||
@@ -292,11 +323,13 @@ NetworkStream *NetworkSource::networkStream() const
|
||||
void NetworkSource::setConnection(const std::string &nameconnection)
|
||||
{
|
||||
connection_name_ = nameconnection;
|
||||
Log::Notify("Network Source connecting to '%s'", connection_name_.c_str());
|
||||
|
||||
// open network stream
|
||||
networkStream()->connect( connection_name_ );
|
||||
stream_->play(true);
|
||||
|
||||
// will be ready after init and one frame rendered
|
||||
ready_ = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,5 +345,13 @@ void NetworkSource::accept(Visitor& v)
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
glm::ivec2 NetworkSource::icon() const
|
||||
{
|
||||
return glm::ivec2(ICON_SOURCE_NETWORK);
|
||||
}
|
||||
|
||||
std::string NetworkSource::info() const
|
||||
{
|
||||
return std::string("connected to '") + connection_name_ + "'";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,13 @@
|
||||
#ifndef NETWORKSOURCE_H
|
||||
#define NETWORKSOURCE_H
|
||||
|
||||
#include "osc/OscReceivedElements.h"
|
||||
#include "osc/OscPacketListener.h"
|
||||
#include "osc/OscOutboundPacketStream.h"
|
||||
#include "ip/UdpSocket.h"
|
||||
|
||||
#include "NetworkToolkit.h"
|
||||
#include "Connection.h"
|
||||
#include "StreamSource.h"
|
||||
|
||||
class NetworkStream;
|
||||
|
||||
class StreamerResponseListener : public osc::OscPacketListener
|
||||
{
|
||||
protected:
|
||||
class NetworkStream *parent_;
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
public:
|
||||
inline void setParent(NetworkStream *s) { parent_ = s; }
|
||||
};
|
||||
|
||||
|
||||
class NetworkStream : public Stream
|
||||
{
|
||||
friend class StreamerResponseListener;
|
||||
|
||||
public:
|
||||
|
||||
NetworkStream();
|
||||
@@ -42,10 +23,22 @@ public:
|
||||
std::string clientAddress() const;
|
||||
std::string serverAddress() const;
|
||||
|
||||
protected:
|
||||
class ResponseListener : public osc::OscPacketListener
|
||||
{
|
||||
protected:
|
||||
class NetworkStream *parent_;
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
public:
|
||||
inline void setParent(NetworkStream *s) { parent_ = s; }
|
||||
ResponseListener() : parent_(nullptr) {}
|
||||
};
|
||||
|
||||
private:
|
||||
// connection information
|
||||
ConnectionInfo streamer_;
|
||||
StreamerResponseListener listener_;
|
||||
ResponseListener listener_;
|
||||
UdpListeningReceiveSocket *receiver_;
|
||||
std::atomic<bool> received_config_;
|
||||
std::atomic<bool> connected_;
|
||||
@@ -59,7 +52,7 @@ class NetworkSource : public StreamSource
|
||||
std::string connection_name_;
|
||||
|
||||
public:
|
||||
NetworkSource();
|
||||
NetworkSource(uint64_t id = 0);
|
||||
~NetworkSource();
|
||||
|
||||
// Source interface
|
||||
@@ -73,7 +66,8 @@ public:
|
||||
void setConnection(const std::string &nameconnection);
|
||||
std::string connection() const;
|
||||
|
||||
glm::ivec2 icon() const override { return glm::ivec2(18, 11); }
|
||||
glm::ivec2 icon() const override;
|
||||
std::string info() const override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -60,32 +78,48 @@
|
||||
* RCV
|
||||
* gst-launch-1.0 shmsrc is-live=true socket-path=/tmp/blah ! video/x-raw, format=RGB, framerate=30/1, width=320, height=240 ! videoconvert ! autovideosink
|
||||
*
|
||||
* RTP UDP JPEG
|
||||
*
|
||||
* SND
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! video/x-raw, format=RGB, framerate=30/1 ! videoconvert ! video/x-raw, format=I420 ! jpegenc quality=95 ! rtpjpegpay ! udpsink port=5000 host=127.0.0
|
||||
* RCV
|
||||
* gst-launch-1.0 udpsrc buffer-size=200000 port=5000 ! application/x-rtp,encoding-name=JPEG ! rtpjpegdepay ! queue max-size-buffers=10 ! jpegdec ! videoconvert ! video/x-raw, format=RGB ! autovideosink
|
||||
*
|
||||
* */
|
||||
|
||||
const char* NetworkToolkit::protocol_name[NetworkToolkit::DEFAULT] = {
|
||||
"Shared Memory",
|
||||
"RTP JPEG Stream",
|
||||
"RTP H264 Stream",
|
||||
"RTP JPEG Broadcast",
|
||||
"RTP H264 Broadcast"
|
||||
"RAW Images",
|
||||
"JPEG Stream",
|
||||
"H264 Stream",
|
||||
"JPEG Broadcast",
|
||||
"H264 Broadcast",
|
||||
"RGB Shared Memory"
|
||||
};
|
||||
|
||||
const std::vector<std::string> NetworkToolkit::protocol_send_pipeline {
|
||||
|
||||
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10 ! shmsink buffer-time=100000 wait-for-connection=true name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! jpegenc ! rtpjpegpay ! udpsink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! udpsink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! rtpstreampay ! tcpserversink name=sink"
|
||||
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=3 ! rtpvrawpay ! application/x-rtp,sampling=RGB ! udpsink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! jpegenc quality=85 ! rtpjpegpay ! udpsink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! x264enc tune=\"zerolatency\" pass=4 quantizer=22 speed-preset=2 ! rtph264pay aggregate-mode=1 ! udpsink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc idct-method=float ! rtpjpegpay ! rtpstreampay ! tcpserversink name=sink",
|
||||
"video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=3 ! x264enc tune=\"zerolatency\" threads=2 ! rtph264pay ! rtpstreampay ! tcpserversink name=sink",
|
||||
"video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10 ! shmsink buffer-time=100000 wait-for-connection=true name=sink"
|
||||
};
|
||||
|
||||
const std::vector<std::string> NetworkToolkit::protocol_receive_pipeline {
|
||||
|
||||
"udpsrc buffer-size=200000 port=XXXX caps=\"application/x-rtp,media=(string)video,encoding-name=(string)RAW,sampling=(string)RGB,width=(string)WWWW,height=(string)HHHH\" ! rtpvrawdepay ! queue max-size-buffers=10",
|
||||
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=JPEG ! rtpjpegdepay ! queue max-size-buffers=10 ! jpegdec",
|
||||
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=H264 ! rtph264depay ! queue max-size-buffers=10 ! avdec_h264",
|
||||
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=JPEG ! rtpstreamdepay ! rtpjpegdepay ! jpegdec",
|
||||
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=H264 ! rtpstreamdepay ! rtph264depay ! avdec_h264",
|
||||
"shmsrc socket-path=XXXX ! video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=10",
|
||||
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=JPEG,payload=26,clock-rate=90000 ! queue max-size-buffers=10 ! rtpjpegdepay ! jpegdec",
|
||||
"udpsrc buffer-size=200000 port=XXXX ! application/x-rtp,encoding-name=H264,payload=96,clock-rate=90000 ! queue ! rtph264depay ! avdec_h264",
|
||||
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=JPEG,payload=26,clock-rate=90000 ! rtpstreamdepay ! rtpjpegdepay ! jpegdec",
|
||||
"tcpclientsrc timeout=1 port=XXXX ! queue max-size-buffers=30 ! application/x-rtp-stream,media=video,encoding-name=H264,payload=96,clock-rate=90000 ! rtpstreamdepay ! rtph264depay ! avdec_h264"
|
||||
};
|
||||
|
||||
const std::vector< std::pair<std::string, std::string> > NetworkToolkit::protocol_h264_send_pipeline {
|
||||
// {"vtenc_h264_hw", "video/x-raw, format=I420, framerate=30/1 ! queue max-size-buffers=10 ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 ! rtph264pay aggregate-mode=1 ! udpsink name=sink"},
|
||||
{"nvh264enc", "video/x-raw, format=RGBA, framerate=30/1 ! queue max-size-buffers=10 ! nvh264enc rc-mode=1 zerolatency=true ! video/x-h264, profile=(string)main ! rtph264pay aggregate-mode=1 ! udpsink name=sink"},
|
||||
{"vaapih264enc", "video/x-raw, format=NV12, framerate=30/1 ! queue max-size-buffers=10 ! vaapih264enc rate-control=cqp init-qp=26 ! video/x-h264, profile=(string)main ! rtph264pay aggregate-mode=1 ! udpsink name=sink"}
|
||||
};
|
||||
|
||||
bool initialized_ = false;
|
||||
@@ -95,12 +129,11 @@ std::vector<unsigned long> iplongs_;
|
||||
|
||||
void add_interface(int fd, const char *name) {
|
||||
struct ifreq ifreq;
|
||||
char host[128];
|
||||
memset(&ifreq, 0, sizeof ifreq);
|
||||
strncpy(ifreq.ifr_name, name, IFNAMSIZ);
|
||||
if(ioctl(fd, SIOCGIFADDR, &ifreq)==0) {
|
||||
int family;
|
||||
switch(family=ifreq.ifr_addr.sa_family) {
|
||||
char host[128];
|
||||
switch(ifreq.ifr_addr.sa_family) {
|
||||
case AF_INET:
|
||||
case AF_INET6:
|
||||
getnameinfo(&ifreq.ifr_addr, sizeof ifreq.ifr_addr, host, sizeof host, 0, 0, NI_NUMERICHOST);
|
||||
@@ -122,14 +155,14 @@ void add_interface(int fd, const char *name) {
|
||||
|
||||
void list_interfaces()
|
||||
{
|
||||
struct ifreq *ifreq;
|
||||
struct ifconf ifconf;
|
||||
char buf[16384];
|
||||
int fd=socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if(fd > -1) {
|
||||
struct ifconf ifconf;
|
||||
ifconf.ifc_len=sizeof buf;
|
||||
ifconf.ifc_buf=buf;
|
||||
if(ioctl(fd, SIOCGIFCONF, &ifconf)==0) {
|
||||
struct ifreq *ifreq;
|
||||
ifreq=ifconf.ifc_req;
|
||||
for(int i=0;i<ifconf.ifc_len;) {
|
||||
size_t len;
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "osc/OscReceivedElements.h"
|
||||
#include "osc/OscPacketListener.h"
|
||||
#include "ip/UdpSocket.h"
|
||||
|
||||
#define OSC_SEPARATOR '/'
|
||||
#define OSC_PREFIX "/vimix"
|
||||
#define OSC_PING "/ping"
|
||||
#define OSC_PONG "/pong"
|
||||
@@ -12,22 +17,18 @@
|
||||
#define OSC_STREAM_REJECT "/reject"
|
||||
#define OSC_STREAM_DISCONNECT "/disconnect"
|
||||
|
||||
|
||||
#define MAX_HANDSHAKE 20
|
||||
#define HANDSHAKE_PORT 71310
|
||||
#define STREAM_REQUEST_PORT 71510
|
||||
#define OSC_DIALOG_PORT 71010
|
||||
#define IP_MTU_SIZE 1536
|
||||
|
||||
namespace NetworkToolkit
|
||||
{
|
||||
|
||||
typedef enum {
|
||||
SHM_RAW = 0,
|
||||
UDP_RAW = 0,
|
||||
UDP_JPEG,
|
||||
UDP_H264,
|
||||
TCP_JPEG,
|
||||
TCP_H264,
|
||||
SHM_RAW,
|
||||
DEFAULT
|
||||
} Protocol;
|
||||
|
||||
@@ -66,6 +67,7 @@ struct StreamConfig {
|
||||
|
||||
extern const char* protocol_name[DEFAULT];
|
||||
extern const std::vector<std::string> protocol_send_pipeline;
|
||||
extern const std::vector< std::pair<std::string, std::string> > protocol_h264_send_pipeline;
|
||||
extern const std::vector<std::string> protocol_receive_pipeline;
|
||||
|
||||
std::string hostname();
|
||||
|
||||
23
Overlay.cpp
Normal file
23
Overlay.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "Overlay.h"
|
||||
|
||||
Overlay::Overlay()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SnapshotOverlay::SnapshotOverlay() : Overlay()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SnapshotOverlay::draw()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void SnapshotOverlay::update (float dt)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
38
Overlay.h
Normal file
38
Overlay.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef OVERLAY_H
|
||||
#define OVERLAY_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class Overlay
|
||||
{
|
||||
public:
|
||||
Overlay();
|
||||
|
||||
virtual void update (float dt) = 0;
|
||||
virtual void draw () = 0;
|
||||
|
||||
virtual std::pair<Node *, glm::vec2> pick(glm::vec2) {
|
||||
return { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
|
||||
virtual View::Cursor grab (Source*, glm::vec2, glm::vec2, std::pair<Node *, glm::vec2>) {
|
||||
return View::Cursor ();
|
||||
}
|
||||
|
||||
virtual View::Cursor over (glm::vec2) {
|
||||
return View::Cursor ();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class SnapshotOverlay: public Overlay
|
||||
{
|
||||
public:
|
||||
SnapshotOverlay();
|
||||
|
||||
void draw () override;
|
||||
void update (float dt) override;
|
||||
};
|
||||
|
||||
|
||||
#endif // OVERLAY_H
|
||||
@@ -1,8 +1,25 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <sstream>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "PatternSource.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
@@ -10,91 +27,70 @@
|
||||
#include "Stream.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
#include "GstToolkit.h"
|
||||
|
||||
#define MAX_PATTERN 23
|
||||
#include "PatternSource.h"
|
||||
|
||||
// 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_[24] = { "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\" "
|
||||
};
|
||||
//
|
||||
// Fill the list of patterns videotestsrc
|
||||
//
|
||||
// Label (for display), feature (for test), pipeline (for gstreamer), animated (true/false), available (false by default)
|
||||
std::vector<pattern_descriptor> Pattern::patterns_ = {
|
||||
{ "Black", "videotestsrc", "videotestsrc pattern=black", false, false },
|
||||
{ "White", "videotestsrc", "videotestsrc pattern=white", false, false },
|
||||
{ "Gradient", "videotestsrc", "videotestsrc pattern=gradient", false, false },
|
||||
{ "Checkers 1x1 px", "videotestsrc", "videotestsrc pattern=checkers-1 ! videobalance saturation=0 contrast=1.5", false, false },
|
||||
{ "Checkers 8x8 px", "videotestsrc", "videotestsrc pattern=checkers-8 ! videobalance saturation=0 contrast=1.5", false, false },
|
||||
{ "Circles", "videotestsrc", "videotestsrc pattern=circular", false, false },
|
||||
{ "Lissajous", "frei0r-src-lissajous0r", "frei0r-src-lissajous0r ratiox=0.001 ratioy=0.999 ! videoconvert", false, false },
|
||||
{ "Pinwheel", "videotestsrc", "videotestsrc pattern=pinwheel", false, false },
|
||||
{ "Spokes", "videotestsrc", "videotestsrc pattern=spokes", false, false },
|
||||
{ "Red", "videotestsrc", "videotestsrc pattern=red", false, false },
|
||||
{ "Green", "videotestsrc", "videotestsrc pattern=green", false, false },
|
||||
{ "Blue", "videotestsrc", "videotestsrc pattern=blue", false, false },
|
||||
{ "Color bars", "videotestsrc", "videotestsrc pattern=smpte100", false, false },
|
||||
{ "RGB grid", "videotestsrc", "videotestsrc pattern=colors", false, false },
|
||||
{ "SMPTE test pattern", "videotestsrc", "videotestsrc pattern=smpte", true, false },
|
||||
{ "Television snow", "videotestsrc", "videotestsrc pattern=snow", true, false },
|
||||
{ "Blink", "videotestsrc", "videotestsrc pattern=blink", true, false },
|
||||
{ "Fresnel zone plate", "videotestsrc", "videotestsrc pattern=zone-plate kx2=XXX ky2=YYY kt=4", true, false },
|
||||
{ "Chroma zone plate", "videotestsrc", "videotestsrc pattern=chroma-zone-plate kx2=XXX ky2=YYY kt=4", true, false },
|
||||
{ "Bar moving", "videotestsrc", "videotestsrc pattern=bar horizontal-speed=5", true, false },
|
||||
{ "Ball bouncing", "videotestsrc", "videotestsrc pattern=ball", true, false },
|
||||
{ "Blob", "frei0r-src-ising0r", "frei0r-src-ising0r", true, false },
|
||||
{ "Timer", "timeoverlay", "videotestsrc pattern=solid-color foreground-color=0 ! timeoverlay halignment=center valignment=center font-desc=\"Sans, 72\" ", true, false },
|
||||
{ "Clock", "clockoverlay", "videotestsrc pattern=solid-color foreground-color=0 ! clockoverlay halignment=center valignment=center font-desc=\"Sans, 72\" ", true, false },
|
||||
{ "Resolution", "textoverlay", "videotestsrc pattern=solid-color foreground-color=0 ! textoverlay text=\"XXXX x YYYY px\" halignment=center valignment=center font-desc=\"Sans, 52\" ", false, false },
|
||||
{ "Frame", "videobox", "videotestsrc pattern=solid-color foreground-color=0 ! videobox fill=white top=-10 bottom=-10 left=-10 right=-10", false, false },
|
||||
{ "Cross", "textoverlay", "videotestsrc pattern=solid-color foreground-color=0 ! textoverlay text=\"+\" halignment=center valignment=center font-desc=\"Sans, 22\" ", false, false },
|
||||
{ "Grid", "frei0r-src-test-pat-g", "frei0r-src-test-pat-g type=0.35", false, false },
|
||||
{ "Point Grid", "frei0r-src-test-pat-g", "frei0r-src-test-pat-g type=0.4", false, false },
|
||||
{ "Ruler", "frei0r-src-test-pat-g", "frei0r-src-test-pat-g type=0.9", false, false },
|
||||
{ "RGB noise", "frei0r-filter-rgbnoise", "videotestsrc pattern=black ! frei0r-filter-rgbnoise noise=0.6", true, false },
|
||||
{ "Philips test pattern", "frei0r-src-test-pat-b", "frei0r-src-test-pat-b type=0.7 ", false, false }
|
||||
};
|
||||
|
||||
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",
|
||||
"Blob",
|
||||
"Timer",
|
||||
"Clock"
|
||||
};
|
||||
|
||||
Pattern::Pattern() : Stream(), type_(MAX_PATTERN+1) // invalid pattern
|
||||
Pattern::Pattern() : Stream(), type_(UINT_MAX) // invalid pattern
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
pattern_descriptor Pattern::get(uint type)
|
||||
{
|
||||
// check availability of feature to use this pattern
|
||||
if (!patterns_[type].available)
|
||||
patterns_[type].available = GstToolkit::has_feature(patterns_[type].feature);
|
||||
|
||||
// return struct
|
||||
return patterns_[type];
|
||||
}
|
||||
|
||||
uint Pattern::count()
|
||||
{
|
||||
return patterns_.size();
|
||||
}
|
||||
|
||||
glm::ivec2 Pattern::resolution()
|
||||
{
|
||||
return glm::ivec2( width_, height_);
|
||||
@@ -103,46 +99,66 @@ glm::ivec2 Pattern::resolution()
|
||||
|
||||
void Pattern::open( uint pattern, glm::ivec2 res )
|
||||
{
|
||||
type_ = MIN(pattern, MAX_PATTERN);
|
||||
std::string gstreamer_pattern = pattern_internal_[type_];
|
||||
// clamp type to be sure
|
||||
type_ = MIN(pattern, Pattern::patterns_.size()-1);
|
||||
std::string gstreamer_pattern = Pattern::patterns_[type_].pipeline;
|
||||
|
||||
// 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;
|
||||
}
|
||||
//
|
||||
// pattern string post-processing: replace placeholders by resolution values
|
||||
// XXXX, YYYY = resolution x and y
|
||||
// XXX, YYY = resolution x and y / 10
|
||||
//
|
||||
// if there is a XXXX parameter to enter
|
||||
std::string::size_type xxxx = gstreamer_pattern.find("XXXX");
|
||||
if (xxxx != std::string::npos)
|
||||
gstreamer_pattern = gstreamer_pattern.replace(xxxx, 4, std::to_string(res.x));
|
||||
// if there is a YYYY parameter to enter
|
||||
std::string::size_type yyyy = gstreamer_pattern.find("YYYY");
|
||||
if (yyyy != std::string::npos)
|
||||
gstreamer_pattern = gstreamer_pattern.replace(yyyy, 4, std::to_string(res.y));
|
||||
// if there is a XXX parameter to enter
|
||||
std::string::size_type xxx = gstreamer_pattern.find("XXX");
|
||||
if (xxx != std::string::npos)
|
||||
gstreamer_pattern = gstreamer_pattern.replace(xxx, 3, std::to_string(res.x/10));
|
||||
// if there is a YYY parameter to enter
|
||||
std::string::size_type yyy = gstreamer_pattern.find("YYY");
|
||||
if (yyy != std::string::npos)
|
||||
gstreamer_pattern = gstreamer_pattern.replace(yyy, 3, std::to_string(res.y/10));
|
||||
|
||||
// all patterns before 'SMPTE test pattern' are single frames (not animated)
|
||||
single_frame_ = type_ < 14;
|
||||
// remember if the pattern is to be updated once or animated
|
||||
single_frame_ = !Pattern::patterns_[type_].animated;
|
||||
|
||||
// (private) open stream
|
||||
Stream::open(gstreamer_pattern, res.x, res.y);
|
||||
}
|
||||
|
||||
PatternSource::PatternSource() : StreamSource()
|
||||
PatternSource::PatternSource(uint64_t id) : StreamSource(id)
|
||||
{
|
||||
// create stream
|
||||
stream_ = (Stream *) new Pattern;
|
||||
stream_ = static_cast<Stream *>( new Pattern );
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::PATTERN, glm::vec3(0.8f, 0.8f, 0.01f));
|
||||
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());
|
||||
// open gstreamer with pattern
|
||||
if ( Pattern::get(type).available) {
|
||||
pattern()->open( (uint) type, resolution );
|
||||
}
|
||||
// revert to pattern Black if not available
|
||||
else {
|
||||
pattern()->open( 0, resolution );
|
||||
Log::Warning("Pattern '%s' is not available in this version of vimix.", Pattern::get(type).label.c_str());
|
||||
}
|
||||
|
||||
pattern()->open( (uint) type, resolution );
|
||||
// play gstreamer
|
||||
stream_->play(true);
|
||||
|
||||
// will be ready after init and one frame rendered
|
||||
ready_ = false;
|
||||
}
|
||||
|
||||
void PatternSource::accept(Visitor& v)
|
||||
@@ -158,3 +174,12 @@ Pattern *PatternSource::pattern() const
|
||||
}
|
||||
|
||||
|
||||
glm::ivec2 PatternSource::icon() const
|
||||
{
|
||||
return glm::ivec2(ICON_SOURCE_PATTERN);
|
||||
}
|
||||
|
||||
std::string PatternSource::info() const
|
||||
{
|
||||
return std::string("pattern '") + Pattern::get(pattern()->type()).label + "'";
|
||||
}
|
||||
|
||||
@@ -5,10 +5,22 @@
|
||||
|
||||
#include "StreamSource.h"
|
||||
|
||||
typedef struct pattern_ {
|
||||
std::string label;
|
||||
std::string feature;
|
||||
std::string pipeline;
|
||||
bool animated;
|
||||
bool available;
|
||||
} pattern_descriptor;
|
||||
|
||||
|
||||
class Pattern : public Stream
|
||||
{
|
||||
static std::vector<pattern_descriptor> patterns_;
|
||||
|
||||
public:
|
||||
static std::vector<std::string> pattern_types;
|
||||
static pattern_descriptor get(uint type);
|
||||
static uint count();
|
||||
|
||||
Pattern();
|
||||
void open( uint pattern, glm::ivec2 res);
|
||||
@@ -23,7 +35,7 @@ private:
|
||||
class PatternSource : public StreamSource
|
||||
{
|
||||
public:
|
||||
PatternSource();
|
||||
PatternSource(uint64_t id = 0);
|
||||
|
||||
// Source interface
|
||||
void accept (Visitor& v) override;
|
||||
@@ -35,7 +47,8 @@ public:
|
||||
Pattern *pattern() const;
|
||||
void setPattern(uint type, glm::ivec2 resolution);
|
||||
|
||||
glm::ivec2 icon() const override { return glm::ivec2(12, 5); }
|
||||
glm::ivec2 icon() const override;
|
||||
std::string info() const override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
#include "PickingVisitor.h"
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include "Log.h"
|
||||
#include "Decorations.h"
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Decorations.h"
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
PickingVisitor::PickingVisitor(glm::vec3 coordinates, bool force) : Visitor(), force_(force)
|
||||
#include "PickingVisitor.h"
|
||||
|
||||
|
||||
PickingVisitor::PickingVisitor(glm::vec3 coordinates, bool force) : Visitor(),
|
||||
force_(force), modelview_(glm::mat4(1.f))
|
||||
{
|
||||
modelview_ = glm::mat4(1.f);
|
||||
points_.push_back( coordinates );
|
||||
}
|
||||
|
||||
PickingVisitor::PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force) : Visitor(), force_(force)
|
||||
PickingVisitor::PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force) : Visitor(),
|
||||
force_(force), modelview_(glm::mat4(1.f))
|
||||
{
|
||||
modelview_ = glm::mat4(1.f);
|
||||
points_.push_back( selectionstart );
|
||||
points_.push_back( selection_end );
|
||||
}
|
||||
@@ -36,7 +55,7 @@ void PickingVisitor::visit(Group &n)
|
||||
return;
|
||||
|
||||
glm::mat4 mv = modelview_;
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
|
||||
if ( (*node)->visible_ || force_)
|
||||
(*node)->accept(*this);
|
||||
modelview_ = mv;
|
||||
@@ -126,6 +145,15 @@ void PickingVisitor::visit(Handles &n)
|
||||
glm::vec4 S = glm::inverse(modelview_) * glm::vec4( 0.05f, 0.05f, 0.f, 0.f );
|
||||
float scale = glm::length( glm::vec2(S) );
|
||||
|
||||
// extract rotation from modelview
|
||||
glm::mat4 ctm;
|
||||
glm::vec3 rot(0.f);
|
||||
glm::vec4 vec = modelview_ * glm::vec4(1.f, 0.f, 0.f, 0.f);
|
||||
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
|
||||
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview_ ;
|
||||
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
|
||||
glm::vec4 mirror = glm::sign(vec);
|
||||
|
||||
bool picked = false;
|
||||
if ( n.type() == Handles::RESIZE ) {
|
||||
// 4 corners
|
||||
@@ -146,21 +174,28 @@ void PickingVisitor::visit(Handles &n)
|
||||
}
|
||||
else if ( n.type() == Handles::ROTATE ){
|
||||
// the icon for rotation is on the right top corner at (0.12, 0.12) in scene coordinates
|
||||
glm::vec4 vec = glm::inverse(modelview_) * glm::vec4( 0.1f, 0.1f, 0.f, 0.f );
|
||||
float l = glm::length( glm::vec2(vec) );
|
||||
picked = glm::length( glm::vec2( 1.f + l, 1.f + l) - glm::vec2(P) ) < 1.5f * scale;
|
||||
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( 0.12f, 0.12f, 0.f, 0.f ) );
|
||||
picked = glm::length( glm::vec2( 1.f, 1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
|
||||
}
|
||||
else if ( n.type() == Handles::SCALE ){
|
||||
// the icon for scaling is on the right bottom corner at (0.12, -0.12) in scene coordinates
|
||||
glm::vec4 vec = glm::inverse(modelview_) * glm::vec4( 0.1f, 0.1f, 0.f, 0.f );
|
||||
float l = glm::length( glm::vec2(vec) );
|
||||
picked = glm::length( glm::vec2( 1.f + l, -1.f - l) - glm::vec2(P) ) < 1.5f * scale;
|
||||
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( 0.12f, -0.12f, 0.f, 0.f ) );
|
||||
picked = glm::length( glm::vec2( 1.f, -1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
|
||||
}
|
||||
else if ( n.type() == Handles::CROP ){
|
||||
// the icon for cropping is on the left bottom corner at (0.12, 0.12) in scene coordinates
|
||||
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( 0.12f, 0.12f, 0.f, 0.f ) );
|
||||
picked = glm::length( glm::vec2( -1.f, -1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
|
||||
}
|
||||
else if ( n.type() == Handles::MENU ){
|
||||
// the icon for restore is on the left top corner at (-0.12, 0.12) in scene coordinates
|
||||
glm::vec4 vec = glm::inverse(modelview_) * glm::vec4( 0.1f, 0.1f, 0.f, 0.f );
|
||||
float l = glm::length( glm::vec2(vec) );
|
||||
picked = glm::length( glm::vec2( -1.f - l, 1.f + l) - glm::vec2(P) ) < 1.5f * scale;
|
||||
// the icon for menu is on the left top corner at (-0.12, 0.12) in scene coordinates
|
||||
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( -0.12f, 0.12f, 0.f, 0.f ) );
|
||||
picked = glm::length( glm::vec2( -1.f, 1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
|
||||
}
|
||||
else if ( n.type() == Handles::LOCKED || n.type() == Handles::UNLOCKED ){
|
||||
// the icon for lock is on the right bottom corner at (-0.12, 0.12) in scene coordinates
|
||||
glm::vec4 pos = glm::inverse(ctm) * ( mirror * glm::vec4( -0.12f, 0.12f, 0.f, 0.f ) );
|
||||
picked = glm::length( glm::vec2( 1.f, -1.f) + glm::vec2(pos) - glm::vec2(P) ) < 1.5f * scale;
|
||||
}
|
||||
|
||||
if ( picked )
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
*/
|
||||
class PickingVisitor: public Visitor
|
||||
{
|
||||
bool force_;
|
||||
std::vector<glm::vec3> points_;
|
||||
glm::mat4 modelview_;
|
||||
std::vector< std::pair<Node *, glm::vec2> > nodes_;
|
||||
bool force_;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
583
Primitives.cpp
583
Primitives.cpp
@@ -1,4 +1,33 @@
|
||||
#include "Primitives.h"
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/rotate_vector.hpp>
|
||||
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
#include "FrameBuffer.h"
|
||||
@@ -6,19 +35,9 @@
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include "Primitives.h"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/rotate_vector.hpp>
|
||||
|
||||
|
||||
|
||||
Surface::Surface(Shader *s) : Primitive(s), textureindex_(0)
|
||||
Surface::Surface(Shader *s) : Primitive(s), textureindex_(0), mirror_(true)
|
||||
{
|
||||
// geometry for a trianglulated simple rectangle surface with UV
|
||||
// (0,0) B +---+ D (1,0)
|
||||
@@ -88,12 +107,8 @@ void Surface::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
if ( textureindex_ ) {
|
||||
glBindTexture(GL_TEXTURE_2D, textureindex_);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
|
||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // TODO add user input to select mode
|
||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mirror_ ? GL_MIRRORED_REPEAT : GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mirror_ ? GL_MIRRORED_REPEAT : GL_REPEAT);
|
||||
}
|
||||
else
|
||||
glBindTexture(GL_TEXTURE_2D, Resource::getTextureBlack());
|
||||
@@ -124,66 +139,8 @@ void ImageSurface::accept(Visitor& v)
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
MediaSurface::MediaSurface(const std::string& p, Shader *s) : Surface(s)
|
||||
{
|
||||
path_ = p;
|
||||
mediaplayer_ = new MediaPlayer;
|
||||
}
|
||||
|
||||
MediaSurface::~MediaSurface()
|
||||
{
|
||||
delete mediaplayer_;
|
||||
}
|
||||
|
||||
void MediaSurface::init()
|
||||
{
|
||||
Surface::init();
|
||||
|
||||
mediaplayer_->open(path_);
|
||||
mediaplayer_->play(true);
|
||||
}
|
||||
|
||||
void MediaSurface::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
if ( !initialized() ) {
|
||||
init();
|
||||
// set the texture to the media player once openned
|
||||
if ( mediaplayer_->isOpen() )
|
||||
textureindex_ = mediaplayer_->texture();
|
||||
}
|
||||
|
||||
Surface::draw(modelview, projection);
|
||||
}
|
||||
|
||||
void MediaSurface::update( float dt )
|
||||
{
|
||||
if ( mediaplayer_->isOpen() ) {
|
||||
mediaplayer_->update();
|
||||
scale_.x = mediaplayer_->aspectRatio();
|
||||
}
|
||||
|
||||
Primitive::update( dt );
|
||||
}
|
||||
|
||||
void MediaSurface::accept(Visitor& v)
|
||||
{
|
||||
Surface::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
FrameBufferSurface::FrameBufferSurface(FrameBuffer *fb, Shader *s) : Surface(s), frame_buffer_(fb)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FrameBufferSurface::init()
|
||||
{
|
||||
Surface::init();
|
||||
|
||||
// set aspect ratio
|
||||
scale_.x = frame_buffer_->aspectRatio();
|
||||
|
||||
}
|
||||
|
||||
void FrameBufferSurface::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
@@ -204,7 +161,6 @@ void FrameBufferSurface::accept(Visitor& v)
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
Points::Points(std::vector<glm::vec3> points, glm::vec4 color, uint pointsize) : Primitive(new Shader)
|
||||
{
|
||||
for(size_t i = 0; i < points.size(); ++i)
|
||||
@@ -218,8 +174,6 @@ Points::Points(std::vector<glm::vec3> points, glm::vec4 color, uint pointsize) :
|
||||
pointsize_ = pointsize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Points::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
if ( !initialized() )
|
||||
@@ -238,63 +192,44 @@ void Points::accept(Visitor& v)
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
LineStrip::LineStrip(std::vector<glm::vec3> points, std::vector<glm::vec4> colors, uint linewidth) : Primitive(new Shader), linewidth_(linewidth)
|
||||
|
||||
|
||||
HLine::HLine(float linewidth): Primitive(new Shader), width(linewidth)
|
||||
{
|
||||
for(size_t i = 0; i < points.size(); ++i)
|
||||
{
|
||||
points_.push_back( points[i] );
|
||||
colors_.push_back( colors[i] );
|
||||
indices_.push_back ( i );
|
||||
}
|
||||
|
||||
drawMode_ = GL_LINE_STRIP;
|
||||
}
|
||||
|
||||
void LineStrip::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
if ( !initialized() )
|
||||
init();
|
||||
|
||||
// glLineWidth(linewidth_ * 2.f * Rendering::manager().mainWindow().dpiScale());
|
||||
|
||||
glm::mat4 mv = modelview;
|
||||
glm::mat4 scale = glm::scale(glm::identity<glm::mat4>(), glm::vec3(1.001f, 1.001f, 1.f));
|
||||
|
||||
// TODO FIXME drawing multiple times is not correct to draw lines of different width
|
||||
// TODO Draw LineStrip using polygons
|
||||
for (uint i = 0 ; i < linewidth_ ; ++i ) {
|
||||
Primitive::draw(mv, projection);
|
||||
mv *= scale;
|
||||
}
|
||||
|
||||
// glLineWidth(1);
|
||||
}
|
||||
|
||||
void LineStrip::accept(Visitor& v)
|
||||
{
|
||||
Primitive::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const std::vector<glm::vec3> square_points {
|
||||
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 ),
|
||||
glm::vec3( -1.f, -1.f, 0.f )
|
||||
};
|
||||
|
||||
static const std::vector<glm::vec4> square_colors {
|
||||
// 1 3
|
||||
// +-------+ ^
|
||||
// / | / | \ |
|
||||
// +-----+ => 0 + | / | + 5 | linewidth
|
||||
// -1 1 \ | / | / |
|
||||
// +-------+ v
|
||||
// 2 4
|
||||
//
|
||||
points_ = std::vector<glm::vec3> { glm::vec3( -1.f, 0.f, 0.f ),
|
||||
glm::vec3( -0.999f, 0.001f, 0.f ),
|
||||
glm::vec3( -0.999f, -0.001f, 0.f ),
|
||||
glm::vec3( 0.999f, 0.001f, 0.f ),
|
||||
glm::vec3( 0.999f, -0.001f, 0.f ),
|
||||
glm::vec3( 1.f, 0.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 ),
|
||||
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 ), glm::vec4( 1.f, 1.f, 1.f, 1.f ) };
|
||||
indices_ = std::vector<uint> { 0, 1, 2, 3, 4, 5 };
|
||||
drawMode_ = GL_TRIANGLE_STRIP;
|
||||
|
||||
LineSquare::LineSquare(uint linewidth) : LineStrip(square_points, square_colors, linewidth)
|
||||
{
|
||||
// default scale
|
||||
scale_.y = width;
|
||||
|
||||
//default color
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
void LineSquare::init()
|
||||
HLine::~HLine()
|
||||
{
|
||||
// do NOT delete vao_ (unique)
|
||||
vao_ = 0;
|
||||
}
|
||||
|
||||
void HLine::init()
|
||||
{
|
||||
// use static unique vertex array object
|
||||
static uint unique_vao_ = 0;
|
||||
@@ -319,43 +254,59 @@ void LineSquare::init()
|
||||
// 2. remember global vertex array object
|
||||
unique_vao_ = vao_;
|
||||
unique_drawCount = drawCount_;
|
||||
// 3. unique_vao_ will NOT be deleted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LineSquare::accept(Visitor& v)
|
||||
void HLine::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
Primitive::accept(v);
|
||||
v.visit(*this);
|
||||
// extract pure scaling from modelview (without rotation)
|
||||
glm::mat4 ctm;
|
||||
glm::vec3 rot(0.f);
|
||||
glm::vec4 vec = modelview * glm::vec4(1.f, 0.f, 0.f, 0.f);
|
||||
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
|
||||
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview ;
|
||||
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
|
||||
|
||||
// Change transform to use linewidth independently of scale in Y (vertical)
|
||||
scale_.y = (float) width / vec.y;
|
||||
update(0);
|
||||
|
||||
// change color
|
||||
shader_->color = color;
|
||||
|
||||
Primitive::draw(modelview, projection);
|
||||
}
|
||||
|
||||
LineSquare::~LineSquare()
|
||||
VLine::VLine(float linewidth): Primitive(new Shader), width(linewidth)
|
||||
{
|
||||
points_ = std::vector<glm::vec3> { glm::vec3( 0.f, -1.f, 0.f ),
|
||||
glm::vec3( 0.001f, -0.999f, 0.f ),
|
||||
glm::vec3( -0.001f, -0.999f, 0.f ),
|
||||
glm::vec3( 0.001f, 0.999f, 0.f ),
|
||||
glm::vec3( -0.001f, 0.999f, 0.f ),
|
||||
glm::vec3( 0.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 ),
|
||||
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ) };
|
||||
indices_ = std::vector<uint> { 0, 1, 2, 3, 4, 5 };
|
||||
drawMode_ = GL_TRIANGLE_STRIP;
|
||||
|
||||
// default scale
|
||||
scale_.x = width;
|
||||
|
||||
// default color
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
VLine::~VLine()
|
||||
{
|
||||
// do NOT delete vao_ (unique)
|
||||
vao_ = 0;
|
||||
}
|
||||
|
||||
|
||||
LineCircle::LineCircle(uint linewidth) : LineStrip(std::vector<glm::vec3>(), std::vector<glm::vec4>(), linewidth)
|
||||
{
|
||||
static int N = 72;
|
||||
static float a = glm::two_pi<float>() / static_cast<float>(N);
|
||||
static glm::vec4 circle_color_points = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
||||
// loop to build a circle
|
||||
glm::vec3 P(1.f, 0.f, 0.f);
|
||||
for (int i = 0; i < N ; i++ ){
|
||||
points_.push_back( glm::vec3(P) );
|
||||
colors_.push_back( circle_color_points );
|
||||
indices_.push_back ( i );
|
||||
|
||||
P = glm::rotateZ(P, a);
|
||||
}
|
||||
// close loop
|
||||
points_.push_back( glm::vec3(1.f, 0.f, 0.f) );
|
||||
colors_.push_back( circle_color_points );
|
||||
indices_.push_back ( N );
|
||||
}
|
||||
|
||||
void LineCircle::init()
|
||||
void VLine::init()
|
||||
{
|
||||
// use static unique vertex array object
|
||||
static uint unique_vao_ = 0;
|
||||
@@ -365,10 +316,10 @@ void LineCircle::init()
|
||||
Node::init();
|
||||
// 2. use the global vertex array object
|
||||
vao_ = unique_vao_;
|
||||
drawCount_ = unique_drawCount;
|
||||
// replace AxisAlignedBoundingBox
|
||||
drawCount_ = unique_drawCount;
|
||||
// compute AxisAlignedBoundingBox
|
||||
bbox_.extend(points_);
|
||||
// arrays of vertices are not needed anymore (STATIC DRAW of vertex object)
|
||||
// arrays of vertices are not needed anymore
|
||||
points_.clear();
|
||||
colors_.clear();
|
||||
texCoords_.clear();
|
||||
@@ -380,19 +331,319 @@ void LineCircle::init()
|
||||
// 2. remember global vertex array object
|
||||
unique_vao_ = vao_;
|
||||
unique_drawCount = drawCount_;
|
||||
// 3. unique_vao_ will NOT be deleted because LineCircle::deleteGLBuffers_() is empty
|
||||
// 3. unique_vao_ will NOT be deleted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void VLine::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
// extract pure scaling from modelview (without rotation)
|
||||
glm::mat4 ctm;
|
||||
glm::vec3 rot(0.f);
|
||||
glm::vec4 vec = modelview * glm::vec4(1.f, 0.f, 0.f, 0.f);
|
||||
rot.z = glm::orientedAngle( glm::vec3(1.f, 0.f, 0.f), glm::normalize(glm::vec3(vec)), glm::vec3(0.f, 0.f, 1.f) );
|
||||
ctm = glm::rotate(glm::identity<glm::mat4>(), -rot.z, glm::vec3(0.f, 0.f, 1.f)) * modelview ;
|
||||
vec = ctm * glm::vec4(1.f, 1.f, 0.f, 0.f);
|
||||
|
||||
// Change transform to use linewidth independently of scale in X (horizontal)
|
||||
scale_.x = width / vec.x;
|
||||
update(0);
|
||||
|
||||
// change color
|
||||
shader_->color = color;
|
||||
|
||||
Primitive::draw(modelview, projection);
|
||||
}
|
||||
|
||||
LineSquare::LineSquare(float linewidth) : Group()
|
||||
{
|
||||
top_ = new HLine(linewidth);
|
||||
top_->translation_ = glm::vec3(0.f, 1.f, 0.f);
|
||||
attach(top_);
|
||||
bottom_ = new HLine(linewidth);
|
||||
bottom_->translation_ = glm::vec3(0.f, -1.f, 0.f);
|
||||
attach(bottom_);
|
||||
left_ = new VLine(linewidth);
|
||||
left_->translation_ = glm::vec3(-1.f, 0.f, 0.f);
|
||||
attach(left_);
|
||||
right_ = new VLine(linewidth);
|
||||
right_->translation_ = glm::vec3(1.f, 0.f, 0.f);
|
||||
attach(right_);
|
||||
}
|
||||
|
||||
|
||||
LineSquare::LineSquare(const LineSquare &square)
|
||||
{
|
||||
top_ = new HLine(square.top_->width);
|
||||
top_->translation_ = glm::vec3(0.f, 1.f, 0.f);
|
||||
attach(top_);
|
||||
bottom_ = new HLine(square.bottom_->width);
|
||||
bottom_->translation_ = glm::vec3(0.f, -1.f, 0.f);
|
||||
attach(bottom_);
|
||||
left_ = new VLine(square.left_->width);
|
||||
left_->translation_ = glm::vec3(-1.f, 0.f, 0.f);
|
||||
attach(left_);
|
||||
right_ = new VLine(square.right_->width);
|
||||
right_->translation_ = glm::vec3(1.f, 0.f, 0.f);
|
||||
attach(right_);
|
||||
|
||||
setColor(square.color());
|
||||
}
|
||||
|
||||
void LineSquare::setLineWidth(float v)
|
||||
{
|
||||
top_->width = v;
|
||||
bottom_->width = v;
|
||||
left_->width = v;
|
||||
right_->width = v;
|
||||
}
|
||||
|
||||
void LineSquare::setColor(glm::vec4 c)
|
||||
{
|
||||
top_->color = c;
|
||||
bottom_->color = c;
|
||||
left_->color = c;
|
||||
right_->color = c;
|
||||
}
|
||||
|
||||
LineStrip::LineStrip(const std::vector<glm::vec2> &path, float linewidth) : Primitive(new Shader),
|
||||
arrayBuffer_(0), path_(path)
|
||||
{
|
||||
linewidth_ = 0.002f * linewidth;
|
||||
|
||||
for(size_t i = 1; i < path_.size(); ++i)
|
||||
{
|
||||
glm::vec3 begin = glm::vec3(path_[i-1], 0.f);
|
||||
glm::vec3 end = glm::vec3(path_[i], 0.f);
|
||||
glm::vec3 dir = end - begin;
|
||||
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
|
||||
|
||||
points_.push_back( begin + perp * linewidth_ );
|
||||
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
|
||||
indices_.push_back ( indices_.size() );
|
||||
|
||||
points_.push_back( begin - perp * linewidth_ );
|
||||
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
|
||||
indices_.push_back ( indices_.size() );
|
||||
|
||||
points_.push_back( end + perp * linewidth_ );
|
||||
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
|
||||
indices_.push_back ( indices_.size() );
|
||||
|
||||
points_.push_back( end - perp * linewidth_ );
|
||||
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
|
||||
indices_.push_back ( indices_.size() );
|
||||
}
|
||||
|
||||
drawMode_ = GL_TRIANGLE_STRIP;
|
||||
}
|
||||
|
||||
LineStrip::~LineStrip()
|
||||
{
|
||||
// delete buffer
|
||||
if ( arrayBuffer_ )
|
||||
glDeleteBuffers ( 1, &arrayBuffer_);
|
||||
}
|
||||
|
||||
void LineStrip::init()
|
||||
{
|
||||
if ( vao_ )
|
||||
glDeleteVertexArrays ( 1, &vao_);
|
||||
|
||||
// Vertex Array
|
||||
glGenVertexArrays( 1, &vao_ );
|
||||
|
||||
// Create and initialize buffer objects
|
||||
if ( arrayBuffer_ )
|
||||
glDeleteBuffers ( 1, &arrayBuffer_);
|
||||
glGenBuffers( 1, &arrayBuffer_ );
|
||||
uint elementBuffer_;
|
||||
glGenBuffers( 1, &elementBuffer_);
|
||||
glBindVertexArray( vao_ );
|
||||
|
||||
// setup the array buffers for vertices
|
||||
std::size_t sizeofPoints = sizeof(glm::vec3) * points_.size();
|
||||
std::size_t sizeofColors = sizeof(glm::vec4) * colors_.size();
|
||||
glBindBuffer( GL_ARRAY_BUFFER, arrayBuffer_ );
|
||||
glBufferData( GL_ARRAY_BUFFER, sizeofPoints + sizeofColors, NULL, GL_DYNAMIC_DRAW);
|
||||
glBufferSubData( GL_ARRAY_BUFFER, 0, sizeofPoints, &points_[0] );
|
||||
glBufferSubData( GL_ARRAY_BUFFER, sizeofPoints, sizeofColors, &colors_[0] );
|
||||
|
||||
// setup the element array for indices
|
||||
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, elementBuffer_);
|
||||
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(uint) * indices_.size(), &(indices_[0]), GL_STATIC_DRAW);
|
||||
|
||||
// explain how to read attributes 0 and 1
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), (void *)0 );
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void *)(sizeofPoints) );
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
// done
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
// drawing indications
|
||||
drawCount_ = indices_.size();
|
||||
|
||||
if ( elementBuffer_ )
|
||||
glDeleteBuffers ( 1, &elementBuffer_);
|
||||
indices_.clear();
|
||||
|
||||
// compute AxisAlignedBoundingBox
|
||||
bbox_.extend(points_);
|
||||
|
||||
Node::init();
|
||||
}
|
||||
|
||||
void LineStrip::updatePath()
|
||||
{
|
||||
// redo points_ array
|
||||
points_.clear();
|
||||
for(size_t i = 1; i < path_.size(); ++i)
|
||||
{
|
||||
glm::vec3 begin = glm::vec3(path_[i-1], 0.f);
|
||||
glm::vec3 end = glm::vec3(path_[i], 0.f);
|
||||
glm::vec3 dir = end - begin;
|
||||
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
|
||||
|
||||
points_.push_back( begin + perp * linewidth_ );
|
||||
points_.push_back( begin - perp * linewidth_ );
|
||||
points_.push_back( end + perp * linewidth_ );
|
||||
points_.push_back( end - perp * linewidth_ );
|
||||
}
|
||||
|
||||
// bind the vertex array and change the point coordinates
|
||||
glBindVertexArray( vao_ );
|
||||
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer_);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::vec3) * points_.size(), &points_[0] );
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
// reset and compute AxisAlignedBoundingBox
|
||||
GlmToolkit::AxisAlignedBoundingBox b;
|
||||
bbox_ = b;
|
||||
bbox_.extend(points_);
|
||||
}
|
||||
|
||||
void LineStrip::editPath(uint index, glm::vec2 position)
|
||||
{
|
||||
if (index < path_.size()) {
|
||||
path_[index] = position;
|
||||
updatePath();
|
||||
}
|
||||
}
|
||||
|
||||
void LineCircle::accept(Visitor& v)
|
||||
void LineStrip::changePath(std::vector<glm::vec2> path)
|
||||
{
|
||||
// invalid if not enough points given
|
||||
size_t N = path_.size();
|
||||
if (path.size() < N)
|
||||
return;
|
||||
|
||||
// replace path but keep number of points
|
||||
path_ = path;
|
||||
path_.resize(N);
|
||||
|
||||
updatePath();
|
||||
}
|
||||
|
||||
void LineStrip::setLineWidth(float linewidth) {
|
||||
|
||||
linewidth_ = 0.002f * linewidth;
|
||||
updatePath();
|
||||
}
|
||||
|
||||
void LineStrip::accept(Visitor& v)
|
||||
{
|
||||
Primitive::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
LineCircle::~LineCircle()
|
||||
LineLoop::LineLoop(const std::vector<glm::vec2> &path, float linewidth) : LineStrip(path, linewidth)
|
||||
{
|
||||
// do NOT delete vao_ (unique)
|
||||
vao_ = 0;
|
||||
// close linestrip loop
|
||||
glm::vec3 begin = glm::vec3(path_[path_.size()-1], 0.f);
|
||||
glm::vec3 end = glm::vec3(path_[0], 0.f);
|
||||
glm::vec3 dir = end - begin;
|
||||
glm::vec3 perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
|
||||
|
||||
points_.push_back( begin + perp * linewidth_ );
|
||||
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
|
||||
indices_.push_back ( indices_.size() );
|
||||
|
||||
points_.push_back( begin - perp * linewidth_ );
|
||||
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
|
||||
indices_.push_back ( indices_.size() );
|
||||
|
||||
points_.push_back( end + perp * linewidth_ );
|
||||
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
|
||||
indices_.push_back ( indices_.size() );
|
||||
|
||||
points_.push_back( end - perp * linewidth_ );
|
||||
colors_.push_back( glm::vec4( 1.f, 1.f, 1.f, 1.f ) );
|
||||
indices_.push_back ( indices_.size() );
|
||||
}
|
||||
|
||||
void LineLoop::updatePath()
|
||||
{
|
||||
glm::vec3 begin;
|
||||
glm::vec3 end;
|
||||
glm::vec3 dir;
|
||||
glm::vec3 perp;
|
||||
|
||||
// redo points_ array
|
||||
points_.clear();
|
||||
size_t i = 1;
|
||||
for(; i < path_.size(); ++i)
|
||||
{
|
||||
begin = glm::vec3(path_[i-1], 0.f);
|
||||
end = glm::vec3(path_[i], 0.f);
|
||||
dir = end - begin;
|
||||
perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
|
||||
|
||||
points_.push_back( begin + perp * linewidth_ );
|
||||
points_.push_back( begin - perp * linewidth_ );
|
||||
points_.push_back( end + perp * linewidth_ );
|
||||
points_.push_back( end - perp * linewidth_ );
|
||||
}
|
||||
|
||||
// close linestrip loop
|
||||
begin = glm::vec3(path_[i-1], 0.f);
|
||||
end = glm::vec3(path_[0], 0.f);
|
||||
dir = end - begin;
|
||||
perp = glm::normalize(glm::cross(dir, glm::vec3(0.f, 0.f, 1.f)));
|
||||
points_.push_back( begin + perp * linewidth_ );
|
||||
points_.push_back( begin - perp * linewidth_ );
|
||||
points_.push_back( end + perp * linewidth_ );
|
||||
points_.push_back( end - perp * linewidth_ );
|
||||
|
||||
// bind the vertex array and change the point coordinates
|
||||
glBindVertexArray( vao_ );
|
||||
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer_);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::vec3) * points_.size(), &points_[0] );
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
// re-compute AxisAlignedBoundingBox
|
||||
bbox_.extend(points_);
|
||||
}
|
||||
|
||||
#define LINE_CIRCLE_DENSITY 72
|
||||
|
||||
LineCircle::LineCircle(float linewidth) : LineLoop(std::vector<glm::vec2>(LINE_CIRCLE_DENSITY), linewidth)
|
||||
{
|
||||
static float a = glm::two_pi<float>() / static_cast<float>(LINE_CIRCLE_DENSITY-1);
|
||||
// loop to build a circle
|
||||
glm::vec3 P(1.f, 0.f, 0.f);
|
||||
|
||||
for (int i = 0; i < LINE_CIRCLE_DENSITY - 1; i++ ){
|
||||
path_[i] = glm::vec2(P);
|
||||
P = glm::rotateZ(P, a);
|
||||
}
|
||||
updatePath();
|
||||
}
|
||||
|
||||
|
||||
|
||||
140
Primitives.h
140
Primitives.h
@@ -30,8 +30,12 @@ public:
|
||||
inline void setTextureIndex(uint t) { textureindex_ = t; }
|
||||
inline uint textureIndex() const { return textureindex_; }
|
||||
|
||||
inline void setMirrorTexture(bool m) { mirror_ = m; }
|
||||
inline bool mirrorTexture() { return mirror_; }
|
||||
|
||||
protected:
|
||||
uint textureindex_;
|
||||
bool mirror_;
|
||||
};
|
||||
|
||||
|
||||
@@ -58,31 +62,6 @@ protected:
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief The MediaSurface class is a Surface to draw a video
|
||||
*
|
||||
* URI is passed to a Media Player to handle the video playback
|
||||
* Height = 1.0, Width is set by the aspect ratio of the image
|
||||
*/
|
||||
class MediaSurface : public Surface {
|
||||
|
||||
public:
|
||||
MediaSurface(const std::string& p, Shader *s = new ImageShader);
|
||||
~MediaSurface();
|
||||
|
||||
void init () override;
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
void update (float dt) override;
|
||||
|
||||
inline std::string path() const { return path_; }
|
||||
inline MediaPlayer *mediaPlayer() const { return mediaplayer_; }
|
||||
|
||||
protected:
|
||||
std::string path_;
|
||||
MediaPlayer *mediaplayer_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The FrameBufferSurface class is a Surface to draw a framebuffer
|
||||
*
|
||||
@@ -94,11 +73,11 @@ class FrameBufferSurface : public Surface {
|
||||
public:
|
||||
FrameBufferSurface(FrameBuffer *fb, Shader *s = new ImageShader);
|
||||
|
||||
void init () override;
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
inline FrameBuffer *getFrameBuffer() const { return frame_buffer_; }
|
||||
inline void setFrameBuffer(FrameBuffer *fb) { frame_buffer_ = fb; }
|
||||
inline FrameBuffer *frameBuffer() const { return frame_buffer_; }
|
||||
|
||||
protected:
|
||||
FrameBuffer *frame_buffer_;
|
||||
@@ -125,54 +104,101 @@ public:
|
||||
};
|
||||
|
||||
|
||||
class HLine : public Primitive {
|
||||
|
||||
public:
|
||||
|
||||
HLine(float width = 1.f);
|
||||
virtual ~HLine();
|
||||
|
||||
void init () override;
|
||||
void draw(glm::mat4 modelview, glm::mat4 projection) override;
|
||||
|
||||
glm::vec4 color;
|
||||
float width;
|
||||
};
|
||||
|
||||
class VLine : public Primitive {
|
||||
|
||||
public:
|
||||
|
||||
VLine(float width = 1.f);
|
||||
virtual ~VLine();
|
||||
|
||||
void init () override;
|
||||
void draw(glm::mat4 modelview, glm::mat4 projection) override;
|
||||
|
||||
glm::vec4 color;
|
||||
float width;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The LineSquare class is a group of 4 lines (width & height = 1.0)
|
||||
*/
|
||||
class LineSquare : public Group {
|
||||
|
||||
HLine *top_, *bottom_;
|
||||
VLine *left_, *right_;
|
||||
|
||||
public:
|
||||
LineSquare(float linewidth = 1.f);
|
||||
LineSquare(const LineSquare &square);
|
||||
|
||||
void setLineWidth(float v);
|
||||
inline float lineWidth() const { return top_->width; }
|
||||
|
||||
void setColor(glm::vec4 c);
|
||||
inline glm::vec4 color() const { return top_->color; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The LineStrip class is a Primitive to draw lines
|
||||
*/
|
||||
class LineStrip : public Primitive {
|
||||
|
||||
uint linewidth_;
|
||||
|
||||
public:
|
||||
LineStrip(std::vector<glm::vec3> points, std::vector<glm::vec4> colors, uint linewidth = 1);
|
||||
LineStrip(const std::vector<glm::vec2> &path, float linewidth = 1.f);
|
||||
virtual ~LineStrip();
|
||||
|
||||
virtual void draw(glm::mat4 modelview, glm::mat4 projection) override;
|
||||
virtual void init () override;
|
||||
virtual void accept(Visitor& v) override;
|
||||
|
||||
std::vector<glm::vec3> getPoints() { return points_; }
|
||||
std::vector<glm::vec4> getColors() { return colors_; }
|
||||
inline std::vector<glm::vec2> path() { return path_; }
|
||||
inline float lineWidth() const { return linewidth_ * 500.f; }
|
||||
|
||||
inline void setLineWidth(uint v) { linewidth_ = v; }
|
||||
inline uint getLineWidth() const { return linewidth_; }
|
||||
void changePath(std::vector<glm::vec2> path);
|
||||
void editPath(uint index, glm::vec2 position);
|
||||
void setLineWidth(float linewidth);
|
||||
|
||||
protected:
|
||||
float linewidth_;
|
||||
uint arrayBuffer_;
|
||||
std::vector<glm::vec2> path_;
|
||||
virtual void updatePath();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The LineLoop class is a LineStrip with closed path
|
||||
*/
|
||||
class LineLoop : public LineStrip {
|
||||
|
||||
public:
|
||||
LineLoop(const std::vector<glm::vec2> &path, float linewidth = 1.f);
|
||||
|
||||
protected:
|
||||
void updatePath() override;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief The LineSquare class is a square LineStrip (width & height = 1.0)
|
||||
* @brief The LineCircle class is a circular LineLoop (diameter = 1.0)
|
||||
*/
|
||||
class LineSquare : public LineStrip {
|
||||
class LineCircle : public LineLoop {
|
||||
|
||||
public:
|
||||
LineSquare(uint linewidth = 1);
|
||||
LineCircle(float linewidth = 1.f);
|
||||
|
||||
void init() override;
|
||||
void accept(Visitor& v) override;
|
||||
|
||||
virtual ~LineSquare();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief The LineCircle class is a circular LineStrip (diameter = 1.0)
|
||||
*/
|
||||
class LineCircle : public LineStrip {
|
||||
|
||||
public:
|
||||
LineCircle(uint linewidth = 1);
|
||||
|
||||
void init() override;
|
||||
void accept(Visitor& v) override;
|
||||
|
||||
virtual ~LineCircle();
|
||||
};
|
||||
|
||||
|
||||
|
||||
62
README.md
62
README.md
@@ -12,13 +12,22 @@ monitor or a projector, but can be recorded live (no audio).
|
||||
|
||||
vimix is the successor for GLMixer - https://sourceforge.net/projects/glmixer/
|
||||
|
||||
# License
|
||||
|
||||
GPL-3.0-or-later
|
||||
See [LICENSE](https://github.com/brunoherbelin/vimix/blob/master/LICENSE)
|
||||
|
||||
# Install
|
||||
|
||||
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
|
||||
|
||||
snap install vimix
|
||||
$ snap install vimix
|
||||
|
||||
NB: You'll need to setup the snap permissions.
|
||||
|
||||
### Mac OSX
|
||||
|
||||
@@ -27,17 +36,26 @@ NB: You'll need to accept the exception in OSX security preference.
|
||||
|
||||
## Clone
|
||||
|
||||
git clone --recursive https://github.com/brunoherbelin/vimix.git
|
||||
$ 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.
|
||||
|
||||
To only update a cloned git copy:
|
||||
|
||||
$ git pull
|
||||
|
||||
## Compile
|
||||
|
||||
mkdir vimix-build
|
||||
cd vimix-build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ../vimix
|
||||
cmake --build .
|
||||
First time after git clone:
|
||||
|
||||
$ mkdir vimix-build
|
||||
$ cd vimix-build
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Release ../vimix
|
||||
|
||||
Compile (or re-compile after pull):
|
||||
|
||||
$ cmake --build .
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -46,20 +64,42 @@ and (recursively) clone all the internal git Dependencies.
|
||||
- gcc
|
||||
- make
|
||||
- cmake
|
||||
- git
|
||||
|
||||
**Libraries:**
|
||||
|
||||
- gstreamer
|
||||
- gst-plugins : base, good, bad & ugly
|
||||
- libpng
|
||||
- gst-plugins : libav, base, good, bad & ugly
|
||||
- libglfw3
|
||||
- libicu
|
||||
|
||||
#### Install Dependencies
|
||||
|
||||
**Ubuntu**
|
||||
|
||||
apt-get install build-essential cmake libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||
$ 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
|
||||
|
||||
**OSX with Brew**
|
||||
|
||||
brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly
|
||||
$ brew install cmake libpng glfw gstreamer gst-libav gst-plugins-bad gst-plugins-base gst-plugins-good gst-plugins-ugly icu4c
|
||||
|
||||
|
||||
#### Generate snap
|
||||
|
||||
To generate the snap (from vimix directory):
|
||||
|
||||
$ snapcraft
|
||||
|
||||
To install the locally created snap:
|
||||
|
||||
$ snap install --dangerous vimix_0.5_amd64.snap
|
||||
|
||||
### Memcheck
|
||||
|
||||
To generate memory usage plots in [massif format](https://valgrind.org/docs/manual/ms-manual.html):
|
||||
|
||||
$ G_SLICE=always-malloc valgrind --tool=massif ./vimix
|
||||
|
||||
To check for memory leaks:
|
||||
|
||||
$ G_SLICE=always-malloc valgrind --leak-check=full --log-file=vimix_mem.txt ./vimix
|
||||
|
||||
319
Recorder.cpp
319
Recorder.cpp
@@ -1,4 +1,25 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <thread>
|
||||
#include<algorithm> // for copy() and assign()
|
||||
#include<iterator> // for back_inserter
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
@@ -24,11 +45,11 @@ PNGRecorder::PNGRecorder() : FrameGrabber()
|
||||
{
|
||||
}
|
||||
|
||||
void PNGRecorder::init(GstCaps *caps)
|
||||
std::string PNGRecorder::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
return std::string("Invalid caps");
|
||||
|
||||
// create a gstreamer pipeline
|
||||
std::string description = "appsrc name=src ! videoconvert ! pngenc ! filesink name=sink";
|
||||
@@ -37,10 +58,9 @@ void PNGRecorder::init(GstCaps *caps)
|
||||
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);
|
||||
std::string msg = std::string("PNG Capture Could not construct pipeline ") + description + "\n" + std::string(error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
return msg;
|
||||
}
|
||||
|
||||
// verify location path (path is always terminated by the OS dependent separator)
|
||||
@@ -60,12 +80,13 @@ void PNGRecorder::init(GstCaps *caps)
|
||||
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);
|
||||
|
||||
// configure stream
|
||||
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
|
||||
gst_app_src_set_latency( src_, -1, 0);
|
||||
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
|
||||
@@ -82,38 +103,35 @@ void PNGRecorder::init(GstCaps *caps)
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("PNG Capture Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
return std::string("PNG Capture : Failed to configure frame grabber.");
|
||||
}
|
||||
|
||||
// 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;
|
||||
return std::string("PNG Capture : Failed to start frame grabber.");
|
||||
}
|
||||
|
||||
// all good
|
||||
Log::Info("PNG Capture started.");
|
||||
initialized_ = true;
|
||||
|
||||
// start recording !!
|
||||
active_ = true;
|
||||
return std::string("PNG Capture started ");
|
||||
}
|
||||
|
||||
void PNGRecorder::terminate()
|
||||
{
|
||||
// remember and inform
|
||||
Settings::application.recentRecordings.push(filename_);
|
||||
Log::Notify("PNG Capture %s is ready.", filename_.c_str());
|
||||
}
|
||||
|
||||
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps, float dt)
|
||||
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps)
|
||||
{
|
||||
FrameGrabber::addFrame(buffer, caps, dt);
|
||||
FrameGrabber::addFrame(buffer, caps);
|
||||
|
||||
// PNG Recorder specific :
|
||||
// stop after one frame
|
||||
if (timestamp_ > 0) {
|
||||
if (frame_count_ > 0) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
@@ -123,12 +141,13 @@ const char* VideoRecorder::profile_name[VideoRecorder::DEFAULT] = {
|
||||
"H264 (Realtime)",
|
||||
"H264 (High 4:4:4)",
|
||||
"H265 (Realtime)",
|
||||
"H265 (HQ Animation)",
|
||||
"H265 (HQ)",
|
||||
"ProRes (Standard)",
|
||||
"ProRes (HQ 4444)",
|
||||
"WebM VP8 (2MB/s)",
|
||||
"WebM VP8 (Realtime)",
|
||||
"Multiple JPEG"
|
||||
};
|
||||
|
||||
const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// Control x264 encoder quality :
|
||||
// pass
|
||||
@@ -138,19 +157,17 @@ const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// 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
|
||||
// ultrafast (1)
|
||||
// superfast (2)
|
||||
// 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 ! 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 ! ",
|
||||
"video/x-raw, format=I420 ! x264enc tune=\"zerolatency\" pass=4 quantizer=22 speed-preset=2 ! video/x-h264, profile=baseline ! h264parse ! ",
|
||||
"video/x-raw, format=Y444_10LE ! x264enc pass=4 quantizer=18 speed-preset=3 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
|
||||
// Control x265 encoder quality :
|
||||
// NB: apparently x265 only accepts I420 format :(
|
||||
// speed-preset
|
||||
// superfast (2)
|
||||
// veryfast (3)
|
||||
// faster (4)
|
||||
// fast (5)
|
||||
@@ -162,56 +179,176 @@ const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// 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 ! ",
|
||||
// default 28
|
||||
// 24 for x265 should be visually transparent; anything lower will probably just waste file size
|
||||
"video/x-raw, format=I420 ! x265enc tune=2 speed-preset=2 option-string=\"crf=24\" ! video/x-h265, profile=(string)main ! h265parse ! ",
|
||||
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=2 option-string=\"crf=12\" ! 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 ! ",
|
||||
// 0 ‘proxy’ 45Mbps YUV 4:2:2
|
||||
// 1 ‘lt’ 102Mbps YUV 4:2:2
|
||||
// 2 ‘standard’ 147Mbps YUV 4:2:2
|
||||
// 3 ‘hq’ 220Mbps YUV 4:2:2
|
||||
// 4 ‘4444’ 330Mbps YUVA 4:4:4:4
|
||||
// quant-mat
|
||||
// -1 auto
|
||||
// 0 proxy
|
||||
// 2 lt
|
||||
// 3 standard
|
||||
// 4 hq
|
||||
// 6 default
|
||||
"video/x-raw, format=I422_10LE ! avenc_prores_ks pass=2 bits_per_mb=8000 profile=2 quant-mat=6 quantizer=8 ! ",
|
||||
"video/x-raw, format=Y444_10LE ! avenc_prores_ks pass=2 bits_per_mb=8000 profile=4 quant-mat=6 quantizer=4 ! ",
|
||||
// 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 ! "
|
||||
// deadline per frame (usec)
|
||||
// 0=best,
|
||||
// 1=realtime
|
||||
// see https://www.webmproject.org/docs/encoder-parameters/
|
||||
// "vp8enc end-usage=cbr deadline=1 cpu-used=8 threads=4 target-bitrate=400000 undershoot=95 "
|
||||
// "buffer-size=6000 buffer-initial-size=4000 buffer-optimal-size=5000 "
|
||||
// "keyframe-max-dist=999999 min-quantizer=4 max-quantizer=50 ! ",
|
||||
"vp8enc end-usage=vbr deadline=1 cpu-used=8 threads=4 target-bitrate=400000 keyframe-max-dist=360 "
|
||||
"token-partitions=2 static-threshold=1000 min-quantizer=4 max-quantizer=20 ! ",
|
||||
// JPEG encoding
|
||||
"jpegenc idct-method=float ! "
|
||||
};
|
||||
|
||||
// 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";
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
|
||||
// under GLX (Linux), gstreamer might have nvidia or vaapi encoders
|
||||
// the hardware encoder will be filled at first instanciation of VideoRecorder
|
||||
std::vector<std::string> VideoRecorder::hardware_encoder;
|
||||
std::vector<std::string> VideoRecorder::hardware_profile_description;
|
||||
|
||||
std::vector<std::string> nvidia_encoder = {
|
||||
"nvh264enc",
|
||||
"nvh264enc",
|
||||
"nvh265enc",
|
||||
"nvh265enc",
|
||||
"", "", "", ""
|
||||
};
|
||||
|
||||
std::vector<std::string> nvidia_profile_description {
|
||||
// qp-const Constant quantizer (-1 = from NVENC preset)
|
||||
// Range: -1 - 51 Default: -1
|
||||
// rc-mode Rate Control Mode
|
||||
// (0): default - Default
|
||||
// (1): constqp - Constant Quantization
|
||||
// (2): cbr - Constant Bit Rate
|
||||
// (3): vbr - Variable Bit Rate
|
||||
// (4): vbr-minqp - Variable Bit Rate (with minimum quantization parameter, DEPRECATED)
|
||||
// (5): cbr-ld-hq - Low-Delay CBR, High Quality
|
||||
// (6): cbr-hq - CBR, High Quality (slower)
|
||||
// (7): vbr-hq - VBR, High Quality (slower)
|
||||
// Control nvh264enc encoder
|
||||
"video/x-raw, format=RGBA ! nvh264enc rc-mode=1 zerolatency=true ! video/x-h264, profile=(string)main ! h264parse ! ",
|
||||
"video/x-raw, format=RGBA ! nvh264enc rc-mode=1 qp-const=18 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
|
||||
// Control nvh265enc encoder
|
||||
"video/x-raw, format=RGBA ! nvh265enc rc-mode=1 zerolatency=true ! video/x-h265, profile=(string)main-10 ! h265parse ! ",
|
||||
"video/x-raw, format=RGBA ! nvh265enc rc-mode=1 qp-const=18 ! video/x-h265, profile=(string)main-444 ! h265parse ! ",
|
||||
"", "", "", ""
|
||||
};
|
||||
|
||||
std::vector<std::string> vaapi_encoder = {
|
||||
"vaapih264enc",
|
||||
"vaapih264enc",
|
||||
"vaapih265enc",
|
||||
"vaapih265enc",
|
||||
"", "", "", ""
|
||||
};
|
||||
|
||||
std::vector<std::string> vaapi_profile_description {
|
||||
|
||||
// Control vaapih264enc encoder
|
||||
"video/x-raw, format=NV12 ! vaapih264enc rate-control=cqp init-qp=26 ! video/x-h264, profile=(string)main ! h264parse ! ",
|
||||
"video/x-raw, format=NV12 ! vaapih264enc rate-control=cqp init-qp=14 quality-level=4 keyframe-period=0 max-bframes=2 ! video/x-h264, profile=(string)high ! h264parse ! ",
|
||||
// Control vaapih265enc encoder
|
||||
"video/x-raw, format=NV12 ! vaapih265enc ! video/x-h265, profile=(string)main ! h265parse ! ",
|
||||
"video/x-raw, format=NV12 ! vaapih265enc rate-control=cqp init-qp=14 quality-level=4 keyframe-period=0 max-bframes=2 ! video/x-h265, profile=(string)main-444 ! h265parse ! ",
|
||||
"", "", "", ""
|
||||
};
|
||||
|
||||
#elif GST_GL_HAVE_PLATFORM_CGL
|
||||
// under CGL (Mac), gstreamer might have the VideoToolbox
|
||||
std::vector<std::string> VideoRecorder::hardware_encoder = {
|
||||
"vtenc_h264_hw",
|
||||
"vtenc_h264_hw",
|
||||
"", "", "", "", "", ""
|
||||
};
|
||||
|
||||
std::vector<std::string> VideoRecorder::hardware_profile_description {
|
||||
// Control vtenc_h264_hw encoder
|
||||
"video/x-raw, format=I420 ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 ! h264parse ! ",
|
||||
"video/x-raw, format=UYVY ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 quality=0.9 ! h264parse ! ",
|
||||
"", "", "", "", "", ""
|
||||
};
|
||||
|
||||
#else
|
||||
// in other platforms, no hardware encoder
|
||||
std::vector<std::string> VideoRecorder::hardware_encoder;
|
||||
std::vector<std::string> VideoRecorder::hardware_profile_description;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
const char* VideoRecorder::buffering_preset_name[6] = { "Minimum", "100 MB", "200 MB", "500 MB", "1 GB", "2 GB" };
|
||||
const guint64 VideoRecorder::buffering_preset_value[6] = { MIN_BUFFER_SIZE, 104857600, 209715200, 524288000, 1073741824, 2147483648 };
|
||||
|
||||
const char* VideoRecorder::framerate_preset_name[3] = { "15 FPS", "25 FPS", "30 FPS" };
|
||||
const gint VideoRecorder::framerate_preset_value[3] = { 15, 25, 30 };
|
||||
|
||||
|
||||
VideoRecorder::VideoRecorder() : FrameGrabber()
|
||||
{
|
||||
// first run initialization of hardware encoders in linux
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
if (hardware_encoder.size() < 1) {
|
||||
// test nvidia encoder
|
||||
if ( GstToolkit::has_feature(nvidia_encoder[0] ) ) {
|
||||
// consider that if first nvidia encoder is valid, all others should also be available
|
||||
hardware_encoder.assign(nvidia_encoder.begin(), nvidia_encoder.end());
|
||||
hardware_profile_description.assign(nvidia_profile_description.begin(), nvidia_profile_description.end());
|
||||
}
|
||||
// test vaapi encoder
|
||||
else if ( GstToolkit::has_feature(vaapi_encoder[0] ) ) {
|
||||
hardware_encoder.assign(vaapi_encoder.begin(), vaapi_encoder.end());
|
||||
hardware_profile_description.assign(vaapi_profile_description.begin(), vaapi_profile_description.end());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VideoRecorder::init(GstCaps *caps)
|
||||
std::string VideoRecorder::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
return std::string("Invalid caps");
|
||||
|
||||
// apply settings
|
||||
buffering_size_ = MAX( MIN_BUFFER_SIZE, buffering_preset_value[Settings::application.record.buffering_mode]);
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, framerate_preset_value[Settings::application.record.framerate_mode]);
|
||||
timestamp_on_clock_ = Settings::application.record.priority_mode < 1;
|
||||
|
||||
// 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];
|
||||
|
||||
// test for a hardware accelerated encoder
|
||||
if (Settings::application.render.gpu_decoding && (int) hardware_encoder.size() > 0 &&
|
||||
GstToolkit::has_feature(hardware_encoder[Settings::application.record.profile]) ) {
|
||||
|
||||
description += hardware_profile_description[Settings::application.record.profile];
|
||||
Log::Info("Video Recording using hardware accelerated encoder (%s)", hardware_encoder[Settings::application.record.profile].c_str());
|
||||
}
|
||||
// revert to software encoder
|
||||
else
|
||||
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);
|
||||
@@ -238,10 +375,9 @@ void VideoRecorder::init(GstCaps *caps)
|
||||
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);
|
||||
std::string msg = std::string("Video Recording : Could not construct pipeline ") + description + "\n" + std::string(error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
return msg;
|
||||
}
|
||||
|
||||
// setup file sink
|
||||
@@ -255,18 +391,32 @@ void VideoRecorder::init(GstCaps *caps)
|
||||
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 );
|
||||
if (timestamp_on_clock_)
|
||||
g_object_set (G_OBJECT (src_),"do-timestamp", TRUE,NULL);
|
||||
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
// configure stream
|
||||
gst_app_src_set_stream_type( src_, GST_APP_STREAM_TYPE_STREAM);
|
||||
gst_app_src_set_latency( src_, -1, 0);
|
||||
|
||||
// Set buffer size
|
||||
gst_app_src_set_max_bytes( src_, buffering_size_);
|
||||
|
||||
// specify recorder framerate in the given caps
|
||||
GstCaps *tmp = gst_caps_copy( caps );
|
||||
GValue v = { 0, };
|
||||
g_value_init (&v, GST_TYPE_FRACTION);
|
||||
gst_value_set_fraction (&v, framerate_preset_value[Settings::application.record.framerate_mode], 1);
|
||||
gst_caps_set_value(tmp, "framerate", &v);
|
||||
g_value_unset (&v);
|
||||
|
||||
// instruct src to use the caps
|
||||
caps_ = gst_caps_copy( tmp );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
gst_caps_unref (tmp);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
@@ -277,35 +427,52 @@ void VideoRecorder::init(GstCaps *caps)
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("VideoRecorder Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
return std::string("Video Recording : Failed to configure frame grabber.");
|
||||
}
|
||||
|
||||
// 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;
|
||||
return std::string("Video Recording : Failed to start frame grabber.");
|
||||
}
|
||||
|
||||
// all good
|
||||
Log::Info("Video Recording started (%s)", profile_name[Settings::application.record.profile]);
|
||||
initialized_ = true;
|
||||
|
||||
return std::string("Video Recording started ") + profile_name[Settings::application.record.profile];
|
||||
|
||||
// start recording !!
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void VideoRecorder::terminate()
|
||||
{
|
||||
// stop the pipeline (again)
|
||||
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
|
||||
// statistics on expected number of frames
|
||||
guint64 N = MAX( (guint64) duration_ / (guint64) frame_duration_, frame_count_);
|
||||
float loss = 100.f * ((float) (N - frame_count_) ) / (float) N;
|
||||
Log::Info("Video Recording : %ld frames captured in %s (aming for %ld, %.0f%% lost)",
|
||||
frame_count_, GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str(), N, loss);
|
||||
|
||||
// warn user if more than 10% lost
|
||||
if (loss > 10.f) {
|
||||
if (timestamp_on_clock_)
|
||||
Log::Warning("Video Recording lost %.0f%% of frames: framerate could not be maintained at %ld FPS.", loss, GST_SECOND / frame_duration_);
|
||||
else
|
||||
Log::Warning("Video Recording lost %.0f%% of frames: video is only %s long.",
|
||||
loss, GstToolkit::time_to_string(timestamp_, GstToolkit::TIME_STRING_READABLE).c_str());
|
||||
Log::Info("Video Recording : try a lower resolution / a lower framerate / a larger buffer size / a faster codec.");
|
||||
}
|
||||
|
||||
// remember and inform
|
||||
Settings::application.recentRecordings.push(filename_);
|
||||
Log::Notify("Video Recording %s is ready.", filename_.c_str());
|
||||
}
|
||||
|
||||
std::string VideoRecorder::info() const
|
||||
{
|
||||
if (active_)
|
||||
return GstToolkit::time_to_string(timestamp_);
|
||||
else
|
||||
if (initialized_ && !active_ && !endofstream_)
|
||||
return "Saving file...";
|
||||
|
||||
return FrameGrabber::info();
|
||||
}
|
||||
|
||||
25
Recorder.h
25
Recorder.h
@@ -2,6 +2,8 @@
|
||||
#define RECORDER_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
@@ -10,26 +12,26 @@
|
||||
|
||||
class PNGRecorder : public FrameGrabber
|
||||
{
|
||||
std::string filename_;
|
||||
std::string filename_;
|
||||
|
||||
public:
|
||||
|
||||
PNGRecorder();
|
||||
std::string filename() const { return filename_; }
|
||||
|
||||
protected:
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
std::string init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
void addFrame(GstBuffer *buffer, GstCaps *caps, float dt) override;
|
||||
void addFrame(GstBuffer *buffer, GstCaps *caps) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class VideoRecorder : public FrameGrabber
|
||||
{
|
||||
std::string filename_;
|
||||
std::string filename_;
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
std::string init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
|
||||
public:
|
||||
@@ -45,12 +47,19 @@ public:
|
||||
JPEG_MULTI,
|
||||
DEFAULT
|
||||
} Profile;
|
||||
static const char* profile_name[DEFAULT];
|
||||
static const char* profile_name[DEFAULT];
|
||||
static const std::vector<std::string> profile_description;
|
||||
static std::vector<std::string> hardware_encoder;
|
||||
static std::vector<std::string> hardware_profile_description;
|
||||
|
||||
static const char* buffering_preset_name[6];
|
||||
static const guint64 buffering_preset_value[6];
|
||||
static const char* framerate_preset_name[3];
|
||||
static const int framerate_preset_value[3];
|
||||
|
||||
VideoRecorder();
|
||||
std::string info() const override;
|
||||
|
||||
std::string filename() const { return filename_; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
166
RenderView.cpp
Normal file
166
RenderView.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <thread>
|
||||
|
||||
// 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_;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderView::drawThumbnail()
|
||||
{
|
||||
if (frame_buffer_) {
|
||||
// if a thumbnailer is pending
|
||||
if (thumbnailer_.size() > 0) {
|
||||
|
||||
try {
|
||||
// new thumbnailing framebuffer
|
||||
FrameBuffer *frame_thumbnail = new FrameBuffer( glm::vec3(SESSION_THUMBNAIL_HEIGHT * frame_buffer_->aspectRatio(), SESSION_THUMBNAIL_HEIGHT, 1.f) );
|
||||
|
||||
// render
|
||||
if (Settings::application.render.blit) {
|
||||
if ( !frame_buffer_->blit(frame_thumbnail) )
|
||||
throw std::runtime_error("no blit");
|
||||
}
|
||||
else {
|
||||
FrameBufferSurface *thumb = new FrameBufferSurface(frame_buffer_);
|
||||
frame_thumbnail->begin();
|
||||
thumb->draw(glm::identity<glm::mat4>(), frame_thumbnail->projection());
|
||||
frame_thumbnail->end();
|
||||
delete thumb;
|
||||
}
|
||||
|
||||
// return valid thumbnail promise
|
||||
thumbnailer_.back().set_value( frame_thumbnail->image() );
|
||||
|
||||
// done with thumbnailing framebuffer
|
||||
delete frame_thumbnail;
|
||||
}
|
||||
catch(...) {
|
||||
// return failed thumbnail promise
|
||||
thumbnailer_.back().set_exception(std::current_exception());
|
||||
}
|
||||
|
||||
// done with this promise
|
||||
thumbnailer_.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FrameBufferImage *RenderView::thumbnail ()
|
||||
{
|
||||
// by default null image
|
||||
FrameBufferImage *img = nullptr;
|
||||
|
||||
// this function is always called from a parallel thread
|
||||
// So we wait for a few frames of rendering before trying to capture a thumbnail
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// create and store a promise for a FrameBufferImage
|
||||
thumbnailer_.emplace_back( std::promise<FrameBufferImage *>() );
|
||||
|
||||
// future will return the promised FrameBufferImage
|
||||
std::future<FrameBufferImage *> ft = thumbnailer_.back().get_future();
|
||||
|
||||
try {
|
||||
// wait for a valid return value from promise
|
||||
img = ft.get();
|
||||
}
|
||||
// catch any failed promise
|
||||
catch (const std::exception&){
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
45
RenderView.h
Normal file
45
RenderView.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef RENDERVIEW_H
|
||||
#define RENDERVIEW_H
|
||||
|
||||
#include <vector>
|
||||
#include <future>
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class RenderView : public View
|
||||
{
|
||||
friend class Session;
|
||||
|
||||
// rendering FBO
|
||||
FrameBuffer *frame_buffer_;
|
||||
Surface *fading_overlay_;
|
||||
|
||||
// promises of returning thumbnails after an update
|
||||
std::vector< std::promise<FrameBufferImage *> > thumbnailer_;
|
||||
|
||||
public:
|
||||
RenderView ();
|
||||
~RenderView ();
|
||||
|
||||
// render frame (in opengl context)
|
||||
void draw () override;
|
||||
bool canSelect(Source *) override { return false; }
|
||||
|
||||
void setResolution (glm::vec3 resolution = glm::vec3(0.f), bool useAlpha = false);
|
||||
glm::vec3 resolution() const { return frame_buffer_->resolution(); }
|
||||
|
||||
|
||||
// current frame
|
||||
inline FrameBuffer *frame () const { return frame_buffer_; }
|
||||
|
||||
protected:
|
||||
|
||||
void setFading(float f = 0.f);
|
||||
float fading() const;
|
||||
|
||||
// get a thumbnail outside of opengl context; wait for a promise to be fullfiled after draw
|
||||
void drawThumbnail();
|
||||
FrameBufferImage *thumbnail ();
|
||||
};
|
||||
|
||||
#endif // RENDERVIEW_H
|
||||
@@ -1,4 +1,23 @@
|
||||
#include <cstring>
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
@@ -38,7 +57,7 @@
|
||||
// standalone image loader
|
||||
#include <stb_image.h>
|
||||
|
||||
// vmix
|
||||
// vimix
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Resource.h"
|
||||
@@ -48,13 +67,60 @@
|
||||
#include "SystemToolkit.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
|
||||
#include "RenderingManager.h"
|
||||
|
||||
// local statics
|
||||
#ifdef USE_GST_OPENGL_SYNC_HANDLER
|
||||
//
|
||||
// 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 GstGLContext *global_gl_context = NULL;
|
||||
static GstGLDisplay *global_display = NULL;
|
||||
|
||||
static std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
|
||||
|
||||
static void glfw_error_callback(int error, const char* description)
|
||||
{
|
||||
@@ -95,10 +161,9 @@ static void WindowEscapeFullscreen( GLFWwindow *w, int key, int, int action, int
|
||||
|
||||
static void WindowToggleFullscreen( GLFWwindow *w, int button, int action, int)
|
||||
{
|
||||
static double seconds = 0.f;
|
||||
|
||||
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
|
||||
{
|
||||
static double seconds = 0.f;
|
||||
// detect double clic
|
||||
if ( glfwGetTime() - seconds < 0.2f ) {
|
||||
// toggle fullscreen
|
||||
@@ -154,27 +219,45 @@ bool Rendering::init()
|
||||
g_setenv ("GST_PLUGIN_SYSTEM_PATH", plugins_path.c_str(), TRUE);
|
||||
g_setenv ("GST_PLUGIN_SCANNER", plugins_scanner.c_str(), TRUE);
|
||||
}
|
||||
std::string frei0r_path = SystemToolkit::cwd_path() + "frei0r-1" ;
|
||||
if ( SystemToolkit::file_exists(frei0r_path)) {
|
||||
Log::Info("Found Frei0r plugins in %s", frei0r_path.c_str());
|
||||
g_setenv ("FREI0R_PATH", frei0r_path.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("Found the following GPU decoding plugin(s):");
|
||||
int i = 1;
|
||||
for(auto it = gpuplugins.rbegin(); it != gpuplugins.rend(); it++, ++i)
|
||||
Log::Info("%d. %s", i, (*it).c_str());
|
||||
}
|
||||
else {
|
||||
Log::Info("No GPU decoding plugin found.");
|
||||
}
|
||||
}
|
||||
#ifdef SYNC_GSTREAMER_OPENGL_CONTEXT
|
||||
#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 ());
|
||||
|
||||
//#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
|
||||
|
||||
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
|
||||
#endif
|
||||
|
||||
//
|
||||
// output window
|
||||
@@ -186,9 +269,6 @@ bool Rendering::init()
|
||||
glfwSetKeyCallback( output_.window(), WindowEscapeFullscreen);
|
||||
glfwSetMouseButtonCallback( output_.window(), WindowToggleFullscreen);
|
||||
|
||||
|
||||
// GstDeviceMonitor *dm = GstToolkit::setup_raw_video_source_device_monitor();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -223,22 +303,26 @@ void Rendering::pushBackDrawCallback(RenderingCallback function)
|
||||
|
||||
void Rendering::draw()
|
||||
{
|
||||
// change windows fullscreen mode if requested
|
||||
main_.toggleFullscreen_();
|
||||
output_.toggleFullscreen_();
|
||||
|
||||
// change main window title if requested
|
||||
if (!main_new_title_.empty()) {
|
||||
main_.setTitle(main_new_title_);
|
||||
main_new_title_.clear();
|
||||
}
|
||||
|
||||
// operate on main window context
|
||||
main_.makeCurrent();
|
||||
|
||||
// User Interface step 1
|
||||
UserInterface::manager().NewFrame();
|
||||
|
||||
// Custom draw
|
||||
// draw
|
||||
std::list<Rendering::RenderingCallback>::iterator iter;
|
||||
for (iter=draw_callbacks_.begin(); iter != draw_callbacks_.end(); 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_);
|
||||
@@ -246,12 +330,20 @@ void Rendering::draw()
|
||||
request_screenshot_ = false;
|
||||
}
|
||||
|
||||
// swap GL buffers
|
||||
glfwSwapBuffers(main_.window());
|
||||
|
||||
// draw output window (and swap buffer output)
|
||||
output_.draw( Mixer::manager().session()->frame() );
|
||||
|
||||
// swap GL buffers
|
||||
glfwSwapBuffers(main_.window());
|
||||
glfwSwapBuffers(output_.window());
|
||||
// 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.0) && (elapsed > 0.0) )
|
||||
g_usleep( 16000 - (gulong)elapsed );
|
||||
g_timer_start(timer);
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -260,10 +352,6 @@ void Rendering::draw()
|
||||
// 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);
|
||||
|
||||
@@ -275,7 +363,7 @@ void Rendering::terminate()
|
||||
// close window
|
||||
glfwDestroyWindow(output_.window());
|
||||
glfwDestroyWindow(main_.window());
|
||||
glfwTerminate();
|
||||
// glfwTerminate();
|
||||
}
|
||||
|
||||
|
||||
@@ -356,14 +444,15 @@ glm::vec2 Rendering::project(glm::vec3 scene_coordinate, glm::mat4 modelview, bo
|
||||
|
||||
void Rendering::FileDropped(GLFWwindow *, int path_count, const char* paths[])
|
||||
{
|
||||
for (int i = 0; i < path_count; ++i) {
|
||||
int i = 0;
|
||||
for (; 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) {
|
||||
if (i>0) {
|
||||
UserInterface::manager().showPannel();
|
||||
Rendering::manager().mainWindow().show();
|
||||
}
|
||||
@@ -416,9 +505,11 @@ RenderingWindow::~RenderingWindow()
|
||||
|
||||
void RenderingWindow::setTitle(const std::string &title)
|
||||
{
|
||||
std::string fulltitle = Settings::application.windows[index_].name;
|
||||
if ( !title.empty() )
|
||||
fulltitle += " -- " + title;
|
||||
std::string fulltitle;
|
||||
if ( title.empty() )
|
||||
fulltitle = Settings::application.windows[index_].name;
|
||||
else
|
||||
fulltitle = title + std::string(" - " APP_NAME);
|
||||
|
||||
glfwSetWindowTitle(window_, fulltitle.c_str());
|
||||
}
|
||||
@@ -514,7 +605,7 @@ void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
|
||||
// done request
|
||||
request_toggle_fullscreen_ = false;
|
||||
|
||||
// if in fullscreen mode
|
||||
// disable fullscreen mode
|
||||
if (mo == nullptr) {
|
||||
// store fullscreen mode
|
||||
Settings::application.windows[index_].fullscreen = false;
|
||||
@@ -526,7 +617,7 @@ void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
|
||||
Settings::application.windows[index_].w,
|
||||
Settings::application.windows[index_].h, 0 );
|
||||
}
|
||||
// not in fullscreen mode
|
||||
// set fullscreen mode
|
||||
else {
|
||||
// store fullscreen mode
|
||||
Settings::application.windows[index_].fullscreen = true;
|
||||
@@ -536,12 +627,11 @@ void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
}
|
||||
|
||||
@@ -620,23 +710,25 @@ bool RenderingWindow::init(int index, GLFWwindow *share)
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
|
||||
|
||||
// create the window normal
|
||||
// create the window
|
||||
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
|
||||
// ensure minimal window size
|
||||
glfwSetWindowSizeLimits(window_, 800, 500, GLFW_DONT_CARE, GLFW_DONT_CARE);
|
||||
|
||||
// set initial 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 );
|
||||
glfwSetWindowSizeCallback( window_, WindowResizeCallback );
|
||||
|
||||
// take opengl context ownership
|
||||
glfwMakeContextCurrent(window_);
|
||||
@@ -659,11 +751,15 @@ bool RenderingWindow::init(int index, GLFWwindow *share)
|
||||
// DPI scaling (retina)
|
||||
dpi_scale_ = float(window_attributes_.viewport.y) / float(winset.h);
|
||||
|
||||
// We decide for byte aligned textures all over
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
|
||||
// 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_GENERATE_MIPMAP_HINT, GL_NICEST);
|
||||
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
||||
|
||||
// if not main window
|
||||
if ( master_ != NULL ) {
|
||||
@@ -753,39 +849,45 @@ void RenderingWindow::draw(FrameBuffer *fb)
|
||||
|
||||
// attach the 2D texture to local FBO
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
|
||||
|
||||
#ifndef NDEBUG
|
||||
Log::Info("Blit to output window enabled.");
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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;
|
||||
// if not disabled
|
||||
if (!Settings::application.render.disabled) {
|
||||
|
||||
// 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);
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
else if (!Settings::application.render.disabled)
|
||||
{
|
||||
// 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
|
||||
@@ -815,64 +917,14 @@ void RenderingWindow::draw(FrameBuffer *fb)
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
// restore attribs
|
||||
Rendering::manager().popAttrib();
|
||||
|
||||
// swap buffer
|
||||
glfwSwapBuffers(window_);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "Screenshot.h"
|
||||
|
||||
//#define USE_GST_OPENGL_SYNC_HANDLER
|
||||
|
||||
typedef struct GLFWmonitor GLFWmonitor;
|
||||
typedef struct GLFWwindow GLFWwindow;
|
||||
@@ -28,6 +29,7 @@ class RenderingWindow
|
||||
|
||||
GLFWwindow *window_, *master_;
|
||||
RenderingAttrib window_attributes_;
|
||||
std::string title_changed_;
|
||||
int index_;
|
||||
float dpi_scale_;
|
||||
|
||||
@@ -91,8 +93,8 @@ class Rendering
|
||||
|
||||
// Private Constructor
|
||||
Rendering();
|
||||
Rendering(Rendering const& copy); // Not Implemented
|
||||
Rendering& operator=(Rendering const& copy); // Not Implemented
|
||||
Rendering(Rendering const& copy) = delete;
|
||||
Rendering& operator=(Rendering const& copy) = delete;
|
||||
|
||||
public:
|
||||
|
||||
@@ -105,9 +107,8 @@ public:
|
||||
|
||||
// Initialization OpenGL and GLFW window creation
|
||||
bool init();
|
||||
|
||||
// show windows and reset views
|
||||
void show();
|
||||
|
||||
// true if active rendering window
|
||||
bool isActive();
|
||||
// draw one frame
|
||||
@@ -130,6 +131,7 @@ public:
|
||||
// get hold on the windows
|
||||
inline RenderingWindow& mainWindow() { return main_; }
|
||||
inline RenderingWindow& outputWindow() { return output_; }
|
||||
inline void setMainWindowTitle(const std::string t) { main_new_title_ = t; }
|
||||
|
||||
// request screenshot
|
||||
void requestScreenshot();
|
||||
@@ -143,6 +145,11 @@ public:
|
||||
// project from scene coordinate to window
|
||||
glm::vec2 project(glm::vec3 scene_coordinate, glm::mat4 modelview = glm::mat4(1.f), bool to_framebuffer = true);
|
||||
|
||||
#ifdef USE_GST_OPENGL_SYNC_HANDLER
|
||||
// for opengl pipeline in gstreamer
|
||||
static void LinkPipeline( GstPipeline *pipeline );
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
std::string glsl_version;
|
||||
@@ -154,6 +161,7 @@ private:
|
||||
std::list<RenderingCallback> draw_callbacks_;
|
||||
|
||||
RenderingWindow main_;
|
||||
std::string main_new_title_;
|
||||
RenderingWindow output_;
|
||||
|
||||
// file drop callback
|
||||
@@ -162,8 +170,6 @@ private:
|
||||
Screenshot screenshot_;
|
||||
bool request_screenshot_;
|
||||
|
||||
// for opengl pipeline in gstreamer
|
||||
void LinkPipeline( GstPipeline *pipeline );
|
||||
};
|
||||
|
||||
|
||||
|
||||
62
Resource.cpp
62
Resource.cpp
@@ -1,6 +1,21 @@
|
||||
#include "defines.h"
|
||||
#include "Resource.h"
|
||||
#include "Log.h"
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -10,16 +25,17 @@
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// multiplatform message box
|
||||
#include <tinyfiledialogs.h>
|
||||
|
||||
// standalone image loader
|
||||
#include "stb_image.h"
|
||||
#include <stb_image.h>
|
||||
|
||||
// CMake Ressource Compiler
|
||||
#include <cmrc/cmrc.hpp>
|
||||
CMRC_DECLARE(vmix);
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Resource.h"
|
||||
|
||||
|
||||
std::map<std::string, uint> textureIndex;
|
||||
std::map<std::string, float> textureAspectRatio;
|
||||
@@ -41,6 +57,7 @@ uint Resource::getTextureBlack()
|
||||
// texture with one black pixel
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return tex_index_black;
|
||||
@@ -62,6 +79,7 @@ uint Resource::getTextureWhite()
|
||||
// texture with one black pixel
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return tex_index_white;
|
||||
@@ -83,6 +101,7 @@ uint Resource::getTextureTransparent()
|
||||
// texture with one black pixel
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return tex_index_transparent;
|
||||
@@ -102,7 +121,7 @@ const char *Resource::getData(const std::string& path, size_t* out_file_size){
|
||||
cmrc::file::iterator it = file.begin();
|
||||
data = static_cast<const char *>(it);
|
||||
}
|
||||
catch (std::system_error e) {
|
||||
catch (const std::system_error &e) {
|
||||
Log::Error("Could not access ressource %s", std::string(path).c_str());
|
||||
}
|
||||
|
||||
@@ -118,7 +137,7 @@ std::string Resource::getText(const std::string& path){
|
||||
file = fs.open(path.c_str());
|
||||
file_stream << std::string(file.begin(), file.end()) << std::endl;
|
||||
}
|
||||
catch (std::system_error e) {
|
||||
catch (const std::system_error &e) {
|
||||
Log::Error("Could not access ressource %s", std::string(path).c_str());
|
||||
}
|
||||
|
||||
@@ -162,9 +181,8 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
|
||||
uint mipMapCount = *(uint*)&(header[24]);
|
||||
uint fourCC = *(uint*)&(header[80]);
|
||||
|
||||
// how big is it going to be including all mipmaps?
|
||||
uint bufsize;
|
||||
bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
|
||||
// how big is it going to be including all mipmaps?
|
||||
uint bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
|
||||
|
||||
// get the buffer = bytes [128 - ]
|
||||
const char *buffer = fp + 128;
|
||||
@@ -188,7 +206,7 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
|
||||
}
|
||||
}
|
||||
|
||||
if (height == 0){
|
||||
if (height == 0 || bufsize == 0){
|
||||
Log::Error("Invalid image in ressource %s", std::string(path).c_str());
|
||||
return 0;
|
||||
}
|
||||
@@ -209,11 +227,11 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
|
||||
// load the mipmaps
|
||||
for (uint level = 0; level < mipMapCount && (width || height); ++level)
|
||||
{
|
||||
uint size = ((width+3)/4)*((height+3)/4)*blockSize;
|
||||
glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, size, buffer + offset);
|
||||
uint s = ((width+3)/4)*((height+3)/4)*blockSize;
|
||||
glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, s, buffer + offset);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
offset += size;
|
||||
offset += s;
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
|
||||
@@ -222,6 +240,7 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
|
||||
if(height < 1) height = 1;
|
||||
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
// remember to avoid openning the same resource twice
|
||||
textureIndex[path] = textureID;
|
||||
@@ -236,10 +255,9 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio)
|
||||
uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
|
||||
{
|
||||
std::string ext = path.substr(path.find_last_of(".") + 1);
|
||||
if (ext=="dds")
|
||||
if (ext=="dds"){
|
||||
return getTextureDDS(path, aspect_ratio);
|
||||
|
||||
GLuint textureID = 0;
|
||||
}
|
||||
|
||||
// return previously openned resource if already openned before
|
||||
if (textureIndex.count(path) > 0) {
|
||||
@@ -247,6 +265,7 @@ uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
|
||||
return textureIndex[path];
|
||||
}
|
||||
|
||||
GLuint textureID = 0;
|
||||
float ar = 1.0;
|
||||
int w, h, n;
|
||||
unsigned char* img = nullptr;
|
||||
@@ -266,19 +285,20 @@ uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
|
||||
}
|
||||
if (h == 0){
|
||||
Log::Error("Invalid image in ressource %s", std::string(path).c_str());
|
||||
stbi_image_free(img);
|
||||
return 0;
|
||||
}
|
||||
ar = static_cast<float>(w) / static_cast<float>(h);
|
||||
|
||||
glGenTextures(1, &textureID);
|
||||
glBindTexture( GL_TEXTURE_2D, textureID);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glBindTexture( GL_TEXTURE_2D, textureID);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, img);
|
||||
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);
|
||||
|
||||
// free memory
|
||||
stbi_image_free(img);
|
||||
|
||||
88
Scene.cpp
88
Scene.cpp
@@ -1,37 +1,70 @@
|
||||
#include "defines.h"
|
||||
#include "Scene.h"
|
||||
#include "Shader.h"
|
||||
#include "Primitives.h"
|
||||
#include "Visitor.h"
|
||||
#include "GarbageVisitor.h"
|
||||
#include "Log.h"
|
||||
#include "GlmToolkit.h"
|
||||
#include "SessionVisitor.h"
|
||||
|
||||
#include <glad/glad.h>
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/random.hpp>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Shader.h"
|
||||
#include "Primitives.h"
|
||||
#include "Visitor.h"
|
||||
#include "GarbageVisitor.h"
|
||||
#include "Log.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "GlmToolkit.h"
|
||||
#include "SessionVisitor.h"
|
||||
|
||||
#include "Scene.h"
|
||||
|
||||
#define DEBUG_SCENE 0
|
||||
#if DEBUG_SCENE
|
||||
static int num_nodes_ = 0;
|
||||
#endif
|
||||
|
||||
// Node
|
||||
Node::Node() : initialized_(false), visible_(true), refcount_(0)
|
||||
{
|
||||
// create unique id
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
id_ = BaseToolkit::uniqueId();
|
||||
|
||||
transform_ = glm::identity<glm::mat4>();
|
||||
scale_ = glm::vec3(1.f);
|
||||
rotation_ = glm::vec3(0.f);
|
||||
translation_ = glm::vec3(0.f);
|
||||
crop_ = glm::vec3(1.f);
|
||||
#if DEBUG_SCENE
|
||||
num_nodes_++;
|
||||
#endif
|
||||
}
|
||||
|
||||
Node::~Node ()
|
||||
{
|
||||
clearCallbacks();
|
||||
#if DEBUG_SCENE
|
||||
num_nodes_--;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Node::clearCallbacks()
|
||||
@@ -45,7 +78,7 @@ void Node::clearCallbacks()
|
||||
}
|
||||
}
|
||||
|
||||
void Node::copyTransform(Node *other)
|
||||
void Node::copyTransform(const Node *other)
|
||||
{
|
||||
if (!other)
|
||||
return;
|
||||
@@ -53,25 +86,23 @@ void Node::copyTransform(Node *other)
|
||||
scale_ = other->scale_;
|
||||
rotation_ = other->rotation_;
|
||||
translation_ = other->translation_;
|
||||
crop_ = other->crop_;
|
||||
}
|
||||
|
||||
void Node::update( float dt)
|
||||
{
|
||||
std::list<UpdateCallback *>::iterator iter;
|
||||
for (iter=update_callbacks_.begin(); iter != update_callbacks_.end(); )
|
||||
for (auto iter=update_callbacks_.begin(); iter != update_callbacks_.end(); )
|
||||
{
|
||||
UpdateCallback *callback = *iter;
|
||||
|
||||
if (callback->enabled())
|
||||
callback->update(this, dt);
|
||||
callback->update(this, dt);
|
||||
|
||||
if (callback->finished()) {
|
||||
iter = update_callbacks_.erase(iter);
|
||||
delete callback;
|
||||
}
|
||||
else {
|
||||
iter++;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
|
||||
// update transform matrix from attributes
|
||||
@@ -109,14 +140,14 @@ void Primitive::init()
|
||||
glGenBuffers( 1, &elementBuffer_);
|
||||
glBindVertexArray( vao_ );
|
||||
|
||||
// compute the memory needs for points normals and indicies
|
||||
// compute the memory needs for points
|
||||
std::size_t sizeofPoints = sizeof(glm::vec3) * points_.size();
|
||||
std::size_t sizeofColors = sizeof(glm::vec4) * colors_.size();
|
||||
std::size_t sizeofTexCoords = sizeof(glm::vec2) * texCoords_.size();
|
||||
|
||||
// setup the array buffers for vertices
|
||||
glBindBuffer( GL_ARRAY_BUFFER, arrayBuffer_ );
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeofPoints + sizeofColors + sizeofTexCoords, NULL, GL_STATIC_DRAW);
|
||||
glBufferData( GL_ARRAY_BUFFER, sizeofPoints + sizeofColors + sizeofTexCoords, NULL, GL_STATIC_DRAW);
|
||||
glBufferSubData( GL_ARRAY_BUFFER, 0, sizeofPoints, &points_[0] );
|
||||
glBufferSubData( GL_ARRAY_BUFFER, sizeofPoints, sizeofColors, &colors_[0] );
|
||||
if ( sizeofTexCoords )
|
||||
@@ -124,7 +155,7 @@ void Primitive::init()
|
||||
|
||||
// setup the element array for the triangle indices
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer_);
|
||||
int sizeofIndices = indices_.size()*sizeof(uint);
|
||||
std::size_t sizeofIndices = indices_.size() * sizeof(uint);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeofIndices, &(indices_[0]), GL_STATIC_DRAW);
|
||||
|
||||
// explain how to read attributes 0, 1 and 2 (for point, color and textcoord respectively)
|
||||
@@ -271,7 +302,7 @@ void Group::update( float dt )
|
||||
|
||||
// update every child node
|
||||
for (NodeSet::iterator node = children_.begin();
|
||||
node != children_.end(); node++) {
|
||||
node != children_.end(); ++node) {
|
||||
(*node)->update ( dt );
|
||||
}
|
||||
}
|
||||
@@ -288,7 +319,7 @@ void Group::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
|
||||
// draw every child node
|
||||
for (NodeSet::iterator node = children_.begin();
|
||||
node != children_.end(); node++) {
|
||||
node != children_.end(); ++node) {
|
||||
(*node)->draw ( ctm, projection );
|
||||
}
|
||||
}
|
||||
@@ -382,13 +413,13 @@ void Switch::accept(Visitor& v)
|
||||
|
||||
void Switch::setActive (uint index)
|
||||
{
|
||||
active_ = CLAMP(index, 0, children_.size() - 1);
|
||||
active_ = MINI(index, children_.size() - 1);
|
||||
}
|
||||
|
||||
Node *Switch::child(uint index) const
|
||||
{
|
||||
if (!children_.empty()) {
|
||||
uint i = CLAMP(index, 0, children_.size() - 1);
|
||||
uint i = MINI(index, children_.size() - 1);
|
||||
return children_.at(i);
|
||||
}
|
||||
return nullptr;
|
||||
@@ -445,6 +476,9 @@ Scene::~Scene()
|
||||
clear();
|
||||
// bg and fg are deleted as children of root
|
||||
delete root_;
|
||||
#if DEBUG_SCENE
|
||||
Log::Info("Total scene nodes %d", num_nodes_);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
8
Scene.h
8
Scene.h
@@ -13,6 +13,7 @@
|
||||
#include <map>
|
||||
|
||||
#include "UpdateCallback.h"
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
// Forward declare classes referenced
|
||||
class Shader;
|
||||
@@ -64,13 +65,13 @@ public:
|
||||
// accept all kind of visitors
|
||||
virtual void accept (Visitor& v);
|
||||
|
||||
void copyTransform (Node *other);
|
||||
void copyTransform (const Node *other);
|
||||
|
||||
// public members, to manipulate with care
|
||||
bool visible_;
|
||||
uint refcount_;
|
||||
glm::mat4 transform_;
|
||||
glm::vec3 scale_, rotation_, translation_;
|
||||
glm::vec3 scale_, rotation_, translation_, crop_;
|
||||
|
||||
// animation update callbacks
|
||||
// list of callbacks to call at each update
|
||||
@@ -239,6 +240,9 @@ class Scene {
|
||||
|
||||
public:
|
||||
Scene();
|
||||
// non assignable class
|
||||
Scene(Scene const&) = delete;
|
||||
Scene& operator=(Scene const&) = delete;
|
||||
~Scene();
|
||||
|
||||
void accept (Visitor& v);
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
#include "Screenshot.h"
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <memory.h>
|
||||
#include <assert.h>
|
||||
@@ -11,6 +28,7 @@
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
#include "Screenshot.h"
|
||||
|
||||
|
||||
Screenshot::Screenshot()
|
||||
@@ -39,7 +57,7 @@ void Screenshot::captureGL(int x, int y, int w, int h)
|
||||
{
|
||||
Width = w - x;
|
||||
Height = h - y;
|
||||
unsigned int size = Width * Height * 4;
|
||||
unsigned int size = Width * Height * 3;
|
||||
|
||||
// create BPO
|
||||
if (Pbo == 0)
|
||||
@@ -58,7 +76,7 @@ void Screenshot::captureGL(int x, int y, int w, int h)
|
||||
|
||||
// screenshot to PBO (fast)
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
Pbo_full = true;
|
||||
|
||||
// done
|
||||
@@ -85,6 +103,7 @@ void Screenshot::save(std::string filename)
|
||||
|
||||
// ready for next
|
||||
Pbo_full = false;
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -128,11 +147,9 @@ void Screenshot::storeToFile(Screenshot *s, std::string filename)
|
||||
ScreenshotSavePending_ = true;
|
||||
// got data to save ?
|
||||
if (s && s->Data) {
|
||||
// make it usable
|
||||
s->RemoveAlpha();
|
||||
s->FlipVertical();
|
||||
// save file
|
||||
stbi_write_png(filename.c_str(), s->Width, s->Height, 4, s->Data, s->Width * 4);
|
||||
stbi_flip_vertically_on_write(true);
|
||||
stbi_write_png(filename.c_str(), s->Width, s->Height, 3, s->Data, s->Width * 3);
|
||||
}
|
||||
ScreenshotSavePending_ = false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
#include "SearchVisitor.h"
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Scene.h"
|
||||
#include "MediaSource.h"
|
||||
#include "Session.h"
|
||||
#include "SessionSource.h"
|
||||
|
||||
#include "SearchVisitor.h"
|
||||
|
||||
SearchVisitor::SearchVisitor(Node *node) : Visitor(), node_(node), found_(false)
|
||||
{
|
||||
@@ -20,7 +44,7 @@ void SearchVisitor::visit(Group &n)
|
||||
if (found_)
|
||||
return;
|
||||
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
|
||||
(*node)->accept(*this);
|
||||
if (found_)
|
||||
break;
|
||||
@@ -39,3 +63,65 @@ void SearchVisitor::visit(Scene &n)
|
||||
// search only in workspace
|
||||
n.ws()->accept(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
SearchFileVisitor::SearchFileVisitor() : Visitor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SearchFileVisitor::visit(Node &)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SearchFileVisitor::visit(Group &n)
|
||||
{
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
|
||||
(*node)->accept(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void SearchFileVisitor::visit(Switch &n)
|
||||
{
|
||||
if (n.numChildren()>0)
|
||||
n.activeChild()->accept(*this);
|
||||
}
|
||||
|
||||
|
||||
void SearchFileVisitor::visit(Scene &n)
|
||||
{
|
||||
// search only in workspace
|
||||
n.ws()->accept(*this);
|
||||
}
|
||||
|
||||
|
||||
void SearchFileVisitor::visit (MediaSource& s)
|
||||
{
|
||||
filenames_.push_back( s.path() );
|
||||
}
|
||||
|
||||
void SearchFileVisitor::visit (SessionFileSource& s)
|
||||
{
|
||||
|
||||
filenames_.push_back( s.path() );
|
||||
}
|
||||
|
||||
std::list<std::string> SearchFileVisitor::parse (Session *se)
|
||||
{
|
||||
SearchFileVisitor sv;
|
||||
|
||||
for (auto iter = se->begin(); iter != se->end(); iter++){
|
||||
(*iter)->accept(sv);
|
||||
}
|
||||
|
||||
return sv.filenames();
|
||||
}
|
||||
|
||||
bool SearchFileVisitor::find (Session *se, std::string path)
|
||||
{
|
||||
std::list<std::string> filenames = parse (se);
|
||||
return std::find(filenames.begin(), filenames.end(), path) != filenames.end();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#ifndef SEARCHVISITOR_H
|
||||
#define SEARCHVISITOR_H
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include "Visitor.h"
|
||||
|
||||
class Session;
|
||||
|
||||
class SearchVisitor: public Visitor
|
||||
{
|
||||
Node *node_;
|
||||
@@ -14,12 +18,35 @@ public:
|
||||
inline Node *node() const { return found_ ? node_ : nullptr; }
|
||||
|
||||
// Elements of Scene
|
||||
void visit(Scene& n);
|
||||
void visit(Node& n);
|
||||
void visit(Primitive&) {}
|
||||
void visit(Group& n);
|
||||
void visit(Switch& n);
|
||||
void visit (Scene& n) override;
|
||||
void visit (Node& n) override;
|
||||
void visit (Primitive&) override {}
|
||||
void visit (Group& n) override;
|
||||
void visit (Switch& n) override;
|
||||
|
||||
};
|
||||
|
||||
class SearchFileVisitor: public Visitor
|
||||
{
|
||||
std::list<std::string> filenames_;
|
||||
|
||||
public:
|
||||
SearchFileVisitor();
|
||||
inline std::list<std::string> filenames() const { return filenames_; }
|
||||
|
||||
// Elements of Scene
|
||||
void visit (Scene& n) override;
|
||||
void visit (Node& n) override;
|
||||
void visit (Primitive&) override {}
|
||||
void visit (Group& n) override;
|
||||
void visit (Switch& n) override;
|
||||
|
||||
// Sources
|
||||
void visit (MediaSource& s) override;
|
||||
void visit (SessionFileSource& s) override;
|
||||
|
||||
static std::list<std::string> parse (Session *se);
|
||||
static bool find (Session *se, std::string path);
|
||||
};
|
||||
|
||||
#endif // SEARCHVISITOR_H
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "defines.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include <tinyxml2.h>
|
||||
#include "Source.h"
|
||||
|
||||
#include "Selection.h"
|
||||
|
||||
@@ -61,7 +80,7 @@ void Selection::set(SourceList l)
|
||||
{
|
||||
clear();
|
||||
|
||||
for(auto it = l.begin(); it != l.end(); it++)
|
||||
for(auto it = l.begin(); it != l.end(); ++it)
|
||||
(*it)->setMode(Source::SELECTED);
|
||||
|
||||
l.sort();
|
||||
@@ -71,7 +90,7 @@ void Selection::set(SourceList l)
|
||||
|
||||
void Selection::add(SourceList l)
|
||||
{
|
||||
for(auto it = l.begin(); it != l.end(); it++)
|
||||
for(auto it = l.begin(); it != l.end(); ++it)
|
||||
(*it)->setMode(Source::SELECTED);
|
||||
|
||||
// generate new set as union of current selection and give list
|
||||
@@ -86,7 +105,7 @@ void Selection::add(SourceList l)
|
||||
|
||||
void Selection::remove(SourceList l)
|
||||
{
|
||||
for(auto it = l.begin(); it != l.end(); it++)
|
||||
for(auto it = l.begin(); it != l.end(); ++it)
|
||||
(*it)->setMode(Source::VISIBLE);
|
||||
|
||||
// generate new set as difference of current selection and give list
|
||||
@@ -98,13 +117,13 @@ void Selection::remove(SourceList l)
|
||||
|
||||
void Selection::clear()
|
||||
{
|
||||
for(auto it = selection_.begin(); it != selection_.end(); it++)
|
||||
for(auto it = selection_.begin(); it != selection_.end(); ++it)
|
||||
(*it)->setMode(Source::VISIBLE);
|
||||
|
||||
selection_.clear();
|
||||
}
|
||||
|
||||
uint Selection::size()
|
||||
uint Selection::size() const
|
||||
{
|
||||
return selection_.size();
|
||||
}
|
||||
@@ -117,7 +136,21 @@ Source *Selection::front()
|
||||
return selection_.front();
|
||||
}
|
||||
|
||||
bool Selection::empty()
|
||||
Source *Selection::back()
|
||||
{
|
||||
if (selection_.empty())
|
||||
return nullptr;
|
||||
|
||||
return selection_.back();
|
||||
}
|
||||
|
||||
void Selection::pop_front()
|
||||
{
|
||||
if (!selection_.empty()) // TODO set mode ?
|
||||
selection_.pop_front();
|
||||
}
|
||||
|
||||
bool Selection::empty() const
|
||||
{
|
||||
return selection_.empty();
|
||||
}
|
||||
@@ -143,29 +176,14 @@ SourceList::iterator Selection::end()
|
||||
return selection_.end();
|
||||
}
|
||||
|
||||
std::string Selection::xml()
|
||||
std::string Selection::clipboard() const
|
||||
{
|
||||
std::string x = "";
|
||||
|
||||
if (!selection_.empty()) {
|
||||
|
||||
// create xml doc and root node
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
|
||||
selectionNode->SetAttribute("size", (int) selection_.size());
|
||||
xmlDoc.InsertEndChild(selectionNode);
|
||||
|
||||
// fill doc
|
||||
SessionVisitor sv(&xmlDoc, selectionNode);
|
||||
for (auto iter = selection_.begin(); iter != selection_.end(); iter++, sv.setRoot(selectionNode) )
|
||||
(*iter)->accept(sv);
|
||||
|
||||
// get compact string
|
||||
tinyxml2::XMLPrinter xmlPrint(0, true);
|
||||
xmlDoc.Print( &xmlPrint );
|
||||
x = xmlPrint.CStr();
|
||||
}
|
||||
|
||||
return x;
|
||||
return SessionVisitor::getClipboard(selection_);
|
||||
}
|
||||
|
||||
SourceList Selection::getCopy() const
|
||||
{
|
||||
SourceList dsl = selection_;
|
||||
return dsl;
|
||||
}
|
||||
|
||||
|
||||
25
Selection.h
25
Selection.h
@@ -1,7 +1,8 @@
|
||||
#ifndef SELECTION_H
|
||||
#define SELECTION_H
|
||||
|
||||
#include "Source.h"
|
||||
#include <string>
|
||||
#include "SourceList.h"
|
||||
|
||||
class Selection
|
||||
{
|
||||
@@ -9,24 +10,32 @@ class Selection
|
||||
public:
|
||||
Selection();
|
||||
|
||||
// construct list
|
||||
void add (Source *s);
|
||||
void add (SourceList l);
|
||||
void remove (Source *s);
|
||||
void remove (SourceList l);
|
||||
void set (Source *s);
|
||||
void set (SourceList l);
|
||||
void toggle (Source *s);
|
||||
|
||||
void add (SourceList l);
|
||||
void remove (SourceList l);
|
||||
void set (SourceList l);
|
||||
void clear ();
|
||||
void pop_front();
|
||||
|
||||
// access elements
|
||||
SourceList::iterator begin ();
|
||||
SourceList::iterator end ();
|
||||
Source *front();
|
||||
bool contains (Source *s);
|
||||
bool empty();
|
||||
uint size ();
|
||||
Source *back();
|
||||
|
||||
std::string xml();
|
||||
// properties
|
||||
bool contains (Source *s);
|
||||
bool empty() const;
|
||||
uint size () const;
|
||||
|
||||
// extract
|
||||
std::string clipboard() const;
|
||||
SourceList getCopy() const;
|
||||
|
||||
protected:
|
||||
SourceList::iterator find (Source *s);
|
||||
|
||||
439
Session.cpp
439
Session.cpp
@@ -1,21 +1,47 @@
|
||||
/*
|
||||
* This file is part of vimix - video live mixer
|
||||
*
|
||||
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
**/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "defines.h"
|
||||
#include "BaseToolkit.h"
|
||||
#include "Source.h"
|
||||
#include "Settings.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Session.h"
|
||||
#include "GarbageVisitor.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_ = "";
|
||||
#include "Session.h"
|
||||
|
||||
SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_string(BaseToolkit::uniqueId())),
|
||||
text(t), large(l), stick(s), pos(glm::vec2(520.f, 30.f)), size(glm::vec2(220.f, 220.f))
|
||||
{
|
||||
}
|
||||
|
||||
Session::Session() : active_(true), activation_threshold_(MIXING_MIN_THRESHOLD),
|
||||
filename_(""), failedSource_(nullptr), thumbnail_(nullptr)
|
||||
{
|
||||
config_[View::RENDERING] = new Group;
|
||||
config_[View::RENDERING]->scale_ = FrameBuffer::getResolutionFromParameters(Settings::application.render.ratio, Settings::application.render.res);
|
||||
config_[View::RENDERING]->scale_ = glm::vec3(0.f);
|
||||
|
||||
config_[View::GEOMETRY] = new Group;
|
||||
config_[View::GEOMETRY]->scale_ = Settings::application.views[View::GEOMETRY].default_scale;
|
||||
@@ -29,14 +55,24 @@ Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f)
|
||||
config_[View::MIXING]->scale_ = Settings::application.views[View::MIXING].default_scale;
|
||||
config_[View::MIXING]->translation_ = Settings::application.views[View::MIXING].default_translation;
|
||||
|
||||
config_[View::APPEARANCE] = new Group;
|
||||
config_[View::APPEARANCE]->scale_ = Settings::application.views[View::APPEARANCE].default_scale;
|
||||
config_[View::APPEARANCE]->translation_ = Settings::application.views[View::APPEARANCE].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;
|
||||
|
||||
snapshots_.xmlDoc_ = new tinyxml2::XMLDocument;
|
||||
start_time_ = gst_util_get_timestamp ();
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@@ -47,14 +83,22 @@ Session::~Session()
|
||||
delete config_[View::GEOMETRY];
|
||||
delete config_[View::LAYER];
|
||||
delete config_[View::MIXING];
|
||||
delete config_[View::APPEARANCE];
|
||||
delete config_[View::TEXTURE];
|
||||
|
||||
snapshots_.keys_.clear();
|
||||
delete snapshots_.xmlDoc_;
|
||||
}
|
||||
|
||||
uint64_t Session::runtime() const
|
||||
{
|
||||
return gst_util_get_timestamp () - start_time_;
|
||||
}
|
||||
|
||||
void Session::setActive (bool on)
|
||||
{
|
||||
if (active_ != on) {
|
||||
active_ = on;
|
||||
for(auto it = sources_.begin(); it != sources_.end(); it++) {
|
||||
for(auto it = sources_.begin(); it != sources_.end(); ++it) {
|
||||
(*it)->setActive(active_);
|
||||
}
|
||||
}
|
||||
@@ -63,26 +107,71 @@ void Session::setActive (bool on)
|
||||
// update all sources
|
||||
void Session::update(float dt)
|
||||
{
|
||||
// no update until render view is initialized
|
||||
if ( render_.frame() == nullptr )
|
||||
return;
|
||||
|
||||
// pre-render all sources
|
||||
failedSource_ = nullptr;
|
||||
bool ready = true;
|
||||
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); ++it){
|
||||
|
||||
// pre-render of all sources
|
||||
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); it++){
|
||||
// ensure the RenderSource is rendering *this* session
|
||||
RenderSource *rs = dynamic_cast<RenderSource *>( *it );
|
||||
if ( rs!= nullptr && rs->session() != this )
|
||||
rs->setSession(this);
|
||||
|
||||
// discard failed source
|
||||
if ( (*it)->failed() ) {
|
||||
failedSource_ = (*it);
|
||||
}
|
||||
// render normally
|
||||
else {
|
||||
if ( !(*it)->ready() )
|
||||
ready = false;
|
||||
// render the source
|
||||
(*it)->render();
|
||||
// update the source
|
||||
(*it)->setActive(activation_threshold_);
|
||||
(*it)->update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
|
||||
// update fading requested
|
||||
if (fading_.active) {
|
||||
|
||||
// animate
|
||||
fading_.progress += dt;
|
||||
|
||||
// update animation
|
||||
if ( fading_.duration > 0.f && fading_.progress < fading_.duration ) {
|
||||
// interpolation
|
||||
float f = fading_.progress / fading_.duration;
|
||||
f = ( 1.f - f ) * fading_.start + f * fading_.target;
|
||||
render_.setFading( f );
|
||||
}
|
||||
// arrived at target
|
||||
else {
|
||||
// set precise value
|
||||
render_.setFading( fading_.target );
|
||||
// fading finished
|
||||
fading_.active = false;
|
||||
fading_.start = fading_.target;
|
||||
fading_.duration = fading_.progress = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
// update the scene tree
|
||||
@@ -91,12 +180,11 @@ void Session::update(float dt)
|
||||
// draw render view in Frame Buffer
|
||||
render_.draw();
|
||||
|
||||
// grab frames to recorders & streamers
|
||||
FrameGrabbing::manager().grabFrame(render_.frame(), dt);
|
||||
|
||||
// draw the thumbnail only after all sources are ready
|
||||
if (ready)
|
||||
render_.drawThumbnail();
|
||||
}
|
||||
|
||||
|
||||
SourceList::iterator Session::addSource(Source *s)
|
||||
{
|
||||
// lock before change
|
||||
@@ -104,21 +192,21 @@ SourceList::iterator Session::addSource(Source *s)
|
||||
|
||||
// find the source
|
||||
SourceList::iterator 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);
|
||||
sources_.push_back(s);
|
||||
// return the iterator to the source created at the beginning
|
||||
its = sources_.end()--;
|
||||
}
|
||||
|
||||
// unlock access
|
||||
access_.unlock();
|
||||
|
||||
// return the iterator to the source created at the beginning
|
||||
return sources_.begin();
|
||||
return its;
|
||||
}
|
||||
|
||||
SourceList::iterator Session::deleteSource(Source *s)
|
||||
@@ -130,13 +218,13 @@ SourceList::iterator Session::deleteSource(Source *s)
|
||||
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;
|
||||
}
|
||||
@@ -148,7 +236,6 @@ SourceList::iterator Session::deleteSource(Source *s)
|
||||
return its;
|
||||
}
|
||||
|
||||
|
||||
void Session::removeSource(Source *s)
|
||||
{
|
||||
// lock before change
|
||||
@@ -158,10 +245,11 @@ void Session::removeSource(Source *s)
|
||||
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);
|
||||
}
|
||||
@@ -170,7 +258,6 @@ void Session::removeSource(Source *s)
|
||||
access_.unlock();
|
||||
}
|
||||
|
||||
|
||||
Source *Session::popSource()
|
||||
{
|
||||
Source *s = nullptr;
|
||||
@@ -179,10 +266,8 @@ Source *Session::popSource()
|
||||
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);
|
||||
}
|
||||
@@ -190,18 +275,52 @@ Source *Session::popSource()
|
||||
return s;
|
||||
}
|
||||
|
||||
void Session::setResolution(glm::vec3 resolution)
|
||||
static void replaceThumbnail(Session *s)
|
||||
{
|
||||
render_.setResolution(resolution);
|
||||
config_[View::RENDERING]->scale_ = resolution;
|
||||
if (s != nullptr) {
|
||||
FrameBufferImage *t = s->renderThumbnail();
|
||||
if (t != nullptr) // avoid recursive infinite loop
|
||||
s->setThumbnail(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Session::setFading(float f, bool forcenow)
|
||||
void Session::setThumbnail(FrameBufferImage *t)
|
||||
{
|
||||
if (forcenow)
|
||||
render_.setFading( f );
|
||||
resetThumbnail();
|
||||
// replace with given image
|
||||
if (t != nullptr)
|
||||
thumbnail_ = t;
|
||||
// no thumbnail image given: capture from rendering in a parallel thread
|
||||
else
|
||||
std::thread( replaceThumbnail, this ).detach();
|
||||
}
|
||||
|
||||
fading_target_ = CLAMP(f, 0.f, 1.f);
|
||||
void Session::resetThumbnail()
|
||||
{
|
||||
if (thumbnail_ != nullptr)
|
||||
delete thumbnail_;
|
||||
thumbnail_ = nullptr;
|
||||
}
|
||||
|
||||
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::setFadingTarget(float f, float duration)
|
||||
{
|
||||
// targetted fading value
|
||||
fading_.target = CLAMP(f, 0.f, 1.f);
|
||||
// starting point for interpolation
|
||||
fading_.start = fading();
|
||||
// initiate animation
|
||||
fading_.progress = 0.f;
|
||||
fading_.duration = duration;
|
||||
// activate update
|
||||
fading_.active = true;
|
||||
}
|
||||
|
||||
SourceList::iterator Session::begin()
|
||||
@@ -234,24 +353,39 @@ 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();
|
||||
}
|
||||
|
||||
std::list<uint64_t> Session::getIdList() const
|
||||
SourceIdList Session::getIdList() const
|
||||
{
|
||||
std::list<uint64_t> idlist;
|
||||
|
||||
for( auto sit = sources_.begin(); sit != sources_.end(); sit++)
|
||||
idlist.push_back( (*sit)->id() );
|
||||
|
||||
// make sure no duplicate
|
||||
idlist.unique();
|
||||
|
||||
return idlist;
|
||||
return ids(sources_);
|
||||
}
|
||||
|
||||
std::list<std::string> Session::getNameList(uint64_t exceptid) const
|
||||
{
|
||||
std::list<std::string> namelist;
|
||||
|
||||
for( SourceList::const_iterator it = sources_.cbegin(); it != sources_.cend(); ++it) {
|
||||
if ( (*it)->id() != exceptid )
|
||||
namelist.push_back( (*it)->name() );
|
||||
}
|
||||
|
||||
return namelist;
|
||||
}
|
||||
|
||||
|
||||
bool Session::empty() const
|
||||
{
|
||||
return sources_.empty();
|
||||
@@ -259,14 +393,14 @@ bool Session::empty() const
|
||||
|
||||
SourceList::iterator Session::at(int index)
|
||||
{
|
||||
if (index<0)
|
||||
if ( index < 0 || index > (int) sources_.size())
|
||||
return sources_.end();
|
||||
|
||||
int i = 0;
|
||||
SourceList::iterator it = sources_.begin();
|
||||
while ( i < index && it != sources_.end() ){
|
||||
i++;
|
||||
it++;
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
@@ -275,7 +409,7 @@ int Session::index(SourceList::iterator it) const
|
||||
{
|
||||
int index = -1;
|
||||
int count = 0;
|
||||
for(auto i = sources_.begin(); i != sources_.end(); i++, count++) {
|
||||
for(auto i = sources_.begin(); i != sources_.end(); ++i, ++count) {
|
||||
if ( i == it ) {
|
||||
index = count;
|
||||
break;
|
||||
@@ -284,6 +418,179 @@ int Session::index(SourceList::iterator it) const
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Session::addNote(SessionNote note)
|
||||
{
|
||||
notes_.push_back( note );
|
||||
}
|
||||
|
||||
std::list<SessionNote>::iterator Session::beginNotes ()
|
||||
{
|
||||
return notes_.begin();
|
||||
}
|
||||
|
||||
std::list<SessionNote>::iterator Session::endNotes ()
|
||||
{
|
||||
return notes_.end();
|
||||
}
|
||||
|
||||
std::list<SessionNote>::iterator Session::deleteNote (std::list<SessionNote>::iterator n)
|
||||
{
|
||||
if (n != notes_.end())
|
||||
return notes_.erase(n);
|
||||
|
||||
return notes_.end();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
size_t Session::numPlayGroups() const
|
||||
{
|
||||
return play_groups_.size();
|
||||
}
|
||||
|
||||
void Session::addPlayGroup(const SourceIdList &ids)
|
||||
{
|
||||
play_groups_.push_back( ids );
|
||||
}
|
||||
|
||||
void Session::addToPlayGroup(size_t i, Source *s)
|
||||
{
|
||||
if (i < play_groups_.size() )
|
||||
{
|
||||
if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) == play_groups_[i].end() )
|
||||
play_groups_[i].push_back(s->id());
|
||||
}
|
||||
}
|
||||
|
||||
void Session::removeFromPlayGroup(size_t i, Source *s)
|
||||
{
|
||||
if (i < play_groups_.size() )
|
||||
{
|
||||
if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) != play_groups_[i].end() )
|
||||
play_groups_[i].remove( s->id() );
|
||||
}
|
||||
}
|
||||
|
||||
void Session::deletePlayGroup(size_t i)
|
||||
{
|
||||
if (i < play_groups_.size() )
|
||||
play_groups_.erase( play_groups_.begin() + i);
|
||||
}
|
||||
|
||||
SourceList Session::playGroup(size_t i) const
|
||||
{
|
||||
SourceList list;
|
||||
|
||||
if (i < play_groups_.size() )
|
||||
{
|
||||
for (auto sid = play_groups_[i].begin(); sid != play_groups_[i].end(); ++sid){
|
||||
|
||||
SourceList::const_iterator it = std::find_if(sources_.begin(), sources_.end(), Source::hasId( *sid));;
|
||||
if ( it != sources_.end())
|
||||
list.push_back( *it);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void Session::lock()
|
||||
{
|
||||
access_.lock();
|
||||
@@ -294,12 +601,26 @@ void Session::unlock()
|
||||
access_.unlock();
|
||||
}
|
||||
|
||||
|
||||
Session *Session::load(const std::string& filename)
|
||||
void Session::validate (SourceList &sources)
|
||||
{
|
||||
SessionCreator creator;
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user