mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-07 08:20:01 +01:00
Compare commits
530 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
78f4b80b84 | ||
|
|
bb8f536f0a | ||
|
|
ca51c5348e | ||
|
|
cc4d9ede97 | ||
|
|
053c2a3f1f | ||
|
|
1538e7b85b | ||
|
|
61d2a4dcb9 | ||
|
|
3c55e25432 | ||
|
|
93ad971fc0 | ||
|
|
5200de2e3e | ||
|
|
d92736b38f | ||
|
|
20f1320e2d | ||
|
|
b6af17f283 | ||
|
|
4a6a110e3d | ||
|
|
7f161a0a49 | ||
|
|
30301b51d4 | ||
|
|
c33796e97c | ||
|
|
61ee23455b | ||
|
|
1cd36b6134 | ||
|
|
59087f9198 | ||
|
|
bb231868b4 | ||
|
|
c841c0e342 | ||
|
|
4630d39663 | ||
|
|
3b529222d8 | ||
|
|
1ab2ae0df0 | ||
|
|
8c9b753544 | ||
|
|
196ce3df1b | ||
|
|
15a0bab925 | ||
|
|
95ed47934d | ||
|
|
8c2c2302d1 | ||
|
|
187a6132fc | ||
|
|
b3fd29056e | ||
|
|
11a58b5adf | ||
|
|
7c5374552d | ||
|
|
d33ff427b5 | ||
|
|
32a4607673 | ||
|
|
6f37ca8a84 | ||
|
|
f9dcd7348e | ||
|
|
f4baa67089 | ||
|
|
39f8b56c99 | ||
|
|
ea1192c334 | ||
|
|
56dfbc737d | ||
|
|
1c1a0e9b33 | ||
|
|
fcc014e5d1 | ||
|
|
bfb0576e26 | ||
|
|
71140a8c6c | ||
|
|
6d80c798f5 | ||
|
|
d3f6f2f87d | ||
|
|
d1050e9fdf | ||
|
|
105294fdaf | ||
|
|
4755f47286 | ||
|
|
77da91efa5 | ||
|
|
e679f18d93 | ||
|
|
c2531cf035 | ||
|
|
2124dfc718 | ||
|
|
ce5369a0ef | ||
|
|
ec797f8d67 | ||
|
|
ce7f30fa63 | ||
|
|
79482d3d1b | ||
|
|
93e7027f48 | ||
|
|
34580ab5ea | ||
|
|
bab0e9b710 | ||
|
|
88d4e3d9d5 | ||
|
|
47c338341d | ||
|
|
3cae0cd66f | ||
|
|
0738c25fb4 | ||
|
|
b8ebab5766 | ||
|
|
954b35032a | ||
|
|
46b9a8f663 | ||
|
|
41f87aa927 | ||
|
|
05a4ac164e | ||
|
|
44901b1e78 | ||
|
|
8ef79a6dbd | ||
|
|
940dd0f2a5 | ||
|
|
4fa7e06e19 | ||
|
|
7f2c3d531c | ||
|
|
a4621f31e3 | ||
|
|
7438b257ae | ||
|
|
cb6a0aefa4 | ||
|
|
7fba62bc49 | ||
|
|
01410a59cf | ||
|
|
e60c7a4cad | ||
|
|
8fa14bda1a | ||
|
|
469ee4c26a | ||
|
|
2627174fc0 | ||
|
|
7246dfa08e | ||
|
|
db0892d25b | ||
|
|
509416d5a0 | ||
|
|
43f444f07b | ||
|
|
bbeb99056a | ||
|
|
65aefc9fb8 | ||
|
|
27239b7513 | ||
|
|
15285ec151 | ||
|
|
d7893be541 | ||
|
|
59c07ceb96 | ||
|
|
007d876dbc | ||
|
|
3a41e59f00 | ||
|
|
3a34da9322 | ||
|
|
b3ee400b1a | ||
|
|
102413c7f4 | ||
|
|
c674fa0897 | ||
|
|
bd922f5bcc | ||
|
|
1390eff646 | ||
|
|
34b508a8dd | ||
|
|
8297c85220 | ||
|
|
795c0ed30f | ||
|
|
babbddcf28 | ||
|
|
650017c8f3 | ||
|
|
2c1eaff476 | ||
|
|
c0e135993c | ||
|
|
22011ffd54 | ||
|
|
31ebccd248 | ||
|
|
af11408ee9 | ||
|
|
99f5236959 | ||
|
|
67463d2214 | ||
|
|
3aabb83ccf | ||
|
|
82b755db84 | ||
|
|
10dc426119 | ||
|
|
233fc64c4e | ||
|
|
977ae76f9b | ||
|
|
77d9b17ac8 | ||
|
|
6f4b75ec1c | ||
|
|
2faa499ace | ||
|
|
2493d8d9f9 | ||
|
|
616c6c8bdf | ||
|
|
5421b5e926 | ||
|
|
d563ee14a9 | ||
|
|
3e5b1e74e8 | ||
|
|
f32b85a656 | ||
|
|
61e5c046c0 | ||
|
|
0eaffe213a | ||
|
|
b2a316b813 | ||
|
|
fac798df93 | ||
|
|
83a2da6b2b | ||
|
|
467ed23b37 | ||
|
|
41e0a6f0be | ||
|
|
00ebacc9db | ||
|
|
e0d44d4db1 | ||
|
|
e8a88fcbb9 | ||
|
|
d4b014188e | ||
|
|
b05207b27d | ||
|
|
c777a3d153 | ||
|
|
1bada746dc | ||
|
|
0d53afb6fd | ||
|
|
3b31d33c90 | ||
|
|
dcffb1cbaa | ||
|
|
bbd105983d | ||
|
|
1d7e0838fa | ||
|
|
d9a205d9ab | ||
|
|
39ceea9690 | ||
|
|
89891a18e5 | ||
|
|
2b59a0e6ed | ||
|
|
688aee8831 | ||
|
|
047163a38c | ||
|
|
df2a66484b | ||
|
|
69c74aa103 | ||
|
|
a4ff2a325f | ||
|
|
7109b94484 | ||
|
|
202db9eaa2 | ||
|
|
84caf2da9a | ||
|
|
9e160fec51 | ||
|
|
b7d54dfadf | ||
|
|
04e03456bf | ||
|
|
e84b16c9ce | ||
|
|
9251aff19f | ||
|
|
519baf7a3b | ||
|
|
59db2cf57c | ||
|
|
db6d3a6fa0 | ||
|
|
1209d337bc | ||
|
|
b0e54c6ff5 | ||
|
|
76f067de55 | ||
|
|
2127c53d50 | ||
|
|
bf5913fb3d | ||
|
|
9e32e4f5b2 | ||
|
|
76926f433c | ||
|
|
e03db22092 | ||
|
|
34659c4d8a | ||
|
|
caa39237ac | ||
|
|
68b219eef2 | ||
|
|
5ebf80b0cd | ||
|
|
fb2d43b022 | ||
|
|
67fa3c9ec8 | ||
|
|
041551535e | ||
|
|
5895e203ba | ||
|
|
3152e420dc | ||
|
|
fa7257fe92 | ||
|
|
077bf3430b | ||
|
|
bcfbf184c6 | ||
|
|
b9f0c259e3 | ||
|
|
6ef5642e63 | ||
|
|
29a9b1daf8 | ||
|
|
526e0f29cb | ||
|
|
d290b058eb | ||
|
|
68c7262aac | ||
|
|
68d3c1aee1 | ||
|
|
a5545147f0 | ||
|
|
32234c4d7c | ||
|
|
2c52530a92 | ||
|
|
69b1f792ba | ||
|
|
e80b174db3 | ||
|
|
90715173f7 | ||
|
|
4e1611aa07 | ||
|
|
1b4d49e80e | ||
|
|
e546214018 | ||
|
|
764259f93a | ||
|
|
cc3f824bfa | ||
|
|
3ca6bfa396 | ||
|
|
c8ac4b2d95 | ||
|
|
5f86afac0c | ||
|
|
6213f3da59 | ||
|
|
f90964bac8 | ||
|
|
e41868d405 | ||
|
|
d49bea5723 | ||
|
|
240f1fde0a | ||
|
|
86fd5f21f3 | ||
|
|
0d934c3590 | ||
|
|
e44832a419 | ||
|
|
9f954d258f | ||
|
|
710514b478 | ||
|
|
13867e2192 | ||
|
|
d7f6461415 | ||
|
|
182f204d80 | ||
|
|
a4b61927bc | ||
|
|
857274c2f3 | ||
|
|
3469e50f0a | ||
|
|
23800b17b4 | ||
|
|
f6588de023 | ||
|
|
c6d8c7189f | ||
|
|
36b57c1499 | ||
|
|
f3487d2074 | ||
|
|
35cd5c6a21 | ||
|
|
ab031cf340 | ||
|
|
0e2af5b04f | ||
|
|
44b9169cdc | ||
|
|
5763a9e756 | ||
|
|
e73ebeab93 | ||
|
|
ce6f198f08 | ||
|
|
2f6f67bdd3 | ||
|
|
a7ba118562 | ||
|
|
bb1682768a | ||
|
|
3c5c6ef8ed | ||
|
|
3d77642d3b | ||
|
|
675856d57c | ||
|
|
bb8dcf088e | ||
|
|
c829e5a40c | ||
|
|
98f9f4a225 | ||
|
|
3f568f714a | ||
|
|
2863a1f3c9 | ||
|
|
6f844f722d | ||
|
|
763d8ac423 | ||
|
|
fa652d74dc | ||
|
|
96fb3ab951 | ||
|
|
929bf7981a | ||
|
|
37eb845fe4 | ||
|
|
e9440c2326 | ||
|
|
f19958d744 | ||
|
|
66977453e1 | ||
|
|
ed596f0ba5 | ||
|
|
96d71387dc | ||
|
|
cfd94317cd | ||
|
|
ec2a65cbba | ||
|
|
6941d9e999 | ||
|
|
7eb5ffaa0d | ||
|
|
a3f5ae9e71 | ||
|
|
2fb3c2baf2 | ||
|
|
25b8d59570 | ||
|
|
1e67720c2a | ||
|
|
531c053db4 | ||
|
|
d591e24a46 | ||
|
|
1acf409b58 | ||
|
|
676b69cf1b | ||
|
|
e3563190d3 | ||
|
|
6e002a8451 | ||
|
|
cb62706791 | ||
|
|
93e3242aba |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,3 +14,7 @@ libTINYXML2.a
|
||||
libvmix-resources.a
|
||||
rules.ninja
|
||||
/vmix
|
||||
/vimix_*.snap
|
||||
/CMakeLists.txt.user.*
|
||||
|
||||
rsc/shaders/paint.fs
|
||||
|
||||
247
ActionManager.cpp
Normal file
247
ActionManager.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Log.h"
|
||||
#include "View.h"
|
||||
#include "Mixer.h"
|
||||
#include "MixingGroup.h"
|
||||
#include "tinyxml2Toolkit.h"
|
||||
#include "SessionSource.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include "ActionManager.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define ACTION_DEBUG
|
||||
#endif
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
Action::Action(): step_(0), max_step_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Action::clear()
|
||||
{
|
||||
// clean the history
|
||||
xmlDoc_.Clear();
|
||||
step_ = 0;
|
||||
max_step_ = 0;
|
||||
|
||||
// start fresh
|
||||
store("Session start");
|
||||
}
|
||||
|
||||
void Action::store(const std::string &label)
|
||||
{
|
||||
// ignore if locked or if no label is given
|
||||
if (locked_ || label.empty())
|
||||
return;
|
||||
|
||||
// incremental naming of history nodes
|
||||
step_++;
|
||||
std::string nodename = "H" + std::to_string(step_);
|
||||
|
||||
// erase future
|
||||
for (uint e = step_; e <= max_step_; e++) {
|
||||
std::string name = "H" + std::to_string(e);
|
||||
XMLElement *node = xmlDoc_.FirstChildElement( name.c_str() );
|
||||
if ( node )
|
||||
xmlDoc_.DeleteChild(node);
|
||||
}
|
||||
max_step_ = step_;
|
||||
|
||||
// create history node
|
||||
XMLElement *sessionNode = xmlDoc_.NewElement( nodename.c_str() );
|
||||
xmlDoc_.InsertEndChild(sessionNode);
|
||||
// label describes the action
|
||||
sessionNode->SetAttribute("label", label.c_str());
|
||||
// view indicates the view when this action occured
|
||||
sessionNode->SetAttribute("view", (int) Mixer::manager().view()->mode());
|
||||
|
||||
// get session to operate on
|
||||
Session *se = Mixer::manager().session();
|
||||
|
||||
// save all sources using source visitor
|
||||
SessionVisitor sv(&xmlDoc_, sessionNode);
|
||||
for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) )
|
||||
(*iter)->accept(sv);
|
||||
|
||||
// debug
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Action stored %s '%s'", nodename.c_str(), label.c_str());
|
||||
// XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Action::undo()
|
||||
{
|
||||
// not possible to go to 1 -1 = 0
|
||||
if (step_ <= 1)
|
||||
return;
|
||||
|
||||
// restore always changes step_ to step_ - 1
|
||||
restore( step_ - 1);
|
||||
}
|
||||
|
||||
void Action::redo()
|
||||
{
|
||||
// not possible to go to max_step_ + 1
|
||||
if (step_ >= max_step_)
|
||||
return;
|
||||
|
||||
// restore always changes step_ to step_ + 1
|
||||
restore( step_ + 1);
|
||||
}
|
||||
|
||||
|
||||
void Action::stepTo(uint target)
|
||||
{
|
||||
// get reasonable target
|
||||
uint t = CLAMP(target, 1, max_step_);
|
||||
|
||||
// ignore t == step_
|
||||
if (t != step_)
|
||||
restore(t);
|
||||
}
|
||||
|
||||
std::string Action::label(uint s) const
|
||||
{
|
||||
std::string l = "";
|
||||
|
||||
if (s > 0 && s <= max_step_) {
|
||||
std::string nodename = "H" + std::to_string(s);
|
||||
const XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
|
||||
l = sessionNode->Attribute("label");
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
void Action::restore(uint target)
|
||||
{
|
||||
// lock
|
||||
locked_ = true;
|
||||
|
||||
// get history node of target step
|
||||
step_ = CLAMP(target, 1, max_step_);
|
||||
std::string nodename = "H" + std::to_string(step_);
|
||||
XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() );
|
||||
|
||||
// ask view to refresh, and switch to action view if user prefers
|
||||
int view = Settings::application.current_view ;
|
||||
if (Settings::application.action_history_follow_view)
|
||||
sessionNode->QueryIntAttribute("view", &view);
|
||||
Mixer::manager().setView( (View::Mode) view);
|
||||
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Restore %s '%s' ", nodename.c_str(), sessionNode->Attribute("label"));
|
||||
#endif
|
||||
|
||||
//
|
||||
// compare source lists
|
||||
//
|
||||
|
||||
// we operate on the current session
|
||||
Session *se = Mixer::manager().session();
|
||||
if (se == nullptr)
|
||||
return;
|
||||
|
||||
// sessionsources contains list of ids of all sources currently in the session (before loading)
|
||||
SourceIdList session_sources = se->getIdList();
|
||||
// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++)
|
||||
// Log::Info("sessionsources id %s", std::to_string(*it).c_str());
|
||||
|
||||
// load history status:
|
||||
// - if a source exists, its attributes are updated, and that's all
|
||||
// - if a source does not exists (in current session), it is created inside the session
|
||||
SessionLoader loader( se );
|
||||
loader.load( sessionNode );
|
||||
|
||||
// loaded_sources contains map of xml ids of all sources treated by loader
|
||||
std::map< uint64_t, Source* > loaded_sources = loader.getSources();
|
||||
|
||||
// remove intersect of both lists (sources were updated by SessionLoader)
|
||||
for( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); ){
|
||||
auto ssit = std::find(session_sources.begin(), session_sources.end(), (*lsit).first);
|
||||
if ( ssit != session_sources.end() ) {
|
||||
lsit = loaded_sources.erase(lsit);
|
||||
session_sources.erase(ssit);
|
||||
}
|
||||
else
|
||||
lsit++;
|
||||
}
|
||||
|
||||
// remaining ids in list sessionsources : to remove
|
||||
while ( !session_sources.empty() ){
|
||||
Source *s = Mixer::manager().findSource( session_sources.front() );
|
||||
if (s!=nullptr) {
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Delete id %s\n", std::to_string(session_sources.front() ).c_str());
|
||||
#endif
|
||||
// remove the source from the mixer
|
||||
Mixer::manager().detach( s );
|
||||
// delete source from session
|
||||
se->deleteSource( s );
|
||||
}
|
||||
session_sources.pop_front();
|
||||
}
|
||||
|
||||
// remaining sources in list loaded_sources : to add
|
||||
for ( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); lsit++)
|
||||
{
|
||||
#ifdef ACTION_DEBUG
|
||||
Log::Info("Recreate id %s to %s\n", std::to_string((*lsit).first).c_str(), std::to_string((*lsit).second->id()).c_str());
|
||||
#endif
|
||||
// attach created source
|
||||
Mixer::manager().attach( (*lsit).second );
|
||||
|
||||
// change the history to match the new id
|
||||
replaceSourceId( (*lsit).first, (*lsit).second->id());
|
||||
}
|
||||
|
||||
//
|
||||
// compare mixing groups
|
||||
//
|
||||
|
||||
// Get the list of mixing groups in the xml loader
|
||||
std::list< SourceList > loadergroups = loader.getMixingGroups();
|
||||
|
||||
// clear all session groups
|
||||
auto group_iter = se->beginMixingGroup();
|
||||
while ( group_iter != se->endMixingGroup() )
|
||||
group_iter = se->deleteMixingGroup(group_iter);
|
||||
|
||||
// apply all changes creating or modifying groups in the session
|
||||
// (after this, new groups are created and existing groups are adjusted)
|
||||
for (auto group_loader_it = loadergroups.begin(); group_loader_it != loadergroups.end(); group_loader_it++) {
|
||||
se->link( *group_loader_it, Mixer::manager().view(View::MIXING)->scene.fg() );
|
||||
}
|
||||
|
||||
// free
|
||||
locked_ = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Action::replaceSourceId(uint64_t previousid, uint64_t newid)
|
||||
{
|
||||
// loop over every session history step
|
||||
XMLElement* historyNode = xmlDoc_.FirstChildElement("H1");
|
||||
for( ; historyNode ; historyNode = historyNode->NextSiblingElement())
|
||||
{
|
||||
// loop over every source in session history
|
||||
XMLElement* sourceNode = historyNode->FirstChildElement("Source");
|
||||
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
|
||||
{
|
||||
// check if this source node has this id
|
||||
uint64_t id_source_ = 0;
|
||||
sourceNode->QueryUnsigned64Attribute("id", &id_source_);
|
||||
if ( id_source_ == previousid )
|
||||
// change to new id
|
||||
sourceNode->SetAttribute("id", newid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
49
ActionManager.h
Normal file
49
ActionManager.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef ACTIONMANAGER_H
|
||||
#define ACTIONMANAGER_H
|
||||
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
class Action
|
||||
{
|
||||
// Private Constructor
|
||||
Action();
|
||||
Action(Action const& copy); // Not Implemented
|
||||
Action& operator=(Action const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
|
||||
static Action& manager()
|
||||
{
|
||||
// The only instance
|
||||
static Action _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
void store(const std::string &label);
|
||||
|
||||
void clear();
|
||||
void undo();
|
||||
void redo();
|
||||
void stepTo(uint target);
|
||||
|
||||
inline uint current() const { return step_; }
|
||||
inline uint max() const { return max_step_; }
|
||||
|
||||
std::string label(uint s) const;
|
||||
|
||||
private:
|
||||
|
||||
void restore(uint target);
|
||||
void replaceSourceId(uint64_t previousid, uint64_t newid);
|
||||
// void replaceSourceId(tinyxml2::XMLElement* parentnode, uint64_t previousid, uint64_t newid);
|
||||
|
||||
tinyxml2::XMLDocument xmlDoc_;
|
||||
uint step_;
|
||||
uint max_step_;
|
||||
std::atomic<bool> locked_;
|
||||
};
|
||||
|
||||
#endif // ACTIONMANAGER_H
|
||||
@@ -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
|
||||
|
||||
279
CMakeLists.txt
279
CMakeLists.txt
@@ -1,6 +1,29 @@
|
||||
|
||||
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})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "Compiling vimix version ${VIMIX_VERSION_MAJOR}.${VIMIX_VERSION_MINOR}.${VIMIX_VERSION_PATCH}")
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "ON")
|
||||
set(CMAKE_INCLUDE_CURRENTDIR ON)
|
||||
@@ -11,25 +34,44 @@ if(UNIX)
|
||||
if (APPLE)
|
||||
add_definitions(-DAPPLE)
|
||||
|
||||
# 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")
|
||||
|
||||
else()
|
||||
add_definitions(-DLINUX)
|
||||
|
||||
set(OpenGL_GL_PREFERENCE "GLVND")
|
||||
|
||||
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)
|
||||
add_definitions(-DWIN32)
|
||||
add_definitions(-DMINGW32)
|
||||
endif(UNIX)
|
||||
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)
|
||||
|
||||
find_package(ICU REQUIRED COMPONENTS i18n io uc)
|
||||
macro_log_feature(ICU_FOUND "ICU" "International Components for Unicode" "http://site.icu-project.org" TRUE)
|
||||
|
||||
#
|
||||
# GSTREAMER
|
||||
@@ -64,7 +106,6 @@ macro_log_feature(GSTREAMER_GL_LIBRARY_FOUND "GStreamer opengl library"
|
||||
"${GSTREAMER_GL_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}"
|
||||
@@ -74,42 +115,33 @@ macro_log_feature(GSTREAMER_GL_LIBRARY_FOUND "GStreamer opengl library"
|
||||
# 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)
|
||||
|
||||
#
|
||||
# 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)
|
||||
macro_log_feature(glfw3_FOUND "GLFW3" "Open Source multi-platform library for OpenGL" "http://www.glfw.org" TRUE)
|
||||
set(GLFW_LIBRARY glfw)
|
||||
|
||||
find_package(OpenGL REQUIRED)
|
||||
macro_display_feature_log()
|
||||
|
||||
# static sub packages in ext
|
||||
set(BUILD_STATIC_LIBS ON)
|
||||
|
||||
#
|
||||
# GLM
|
||||
#
|
||||
set(BUILD_STATIC_LIBS ON)
|
||||
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")
|
||||
|
||||
#
|
||||
# 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' generated at 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
|
||||
@@ -143,6 +175,27 @@ 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}.")
|
||||
|
||||
#
|
||||
# OSCPack
|
||||
#
|
||||
if(UNIX)
|
||||
set(OSCPACK_PLATFORM_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/ip/posix/)
|
||||
elseif(WIN32)
|
||||
set(OSCPACK_PLATFORM_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/ip/win32/)
|
||||
endif()
|
||||
set(OSCPACK_SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/osc/OscTypes.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/osc/OscReceivedElements.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/osc/OscPrintReceivedElements.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/osc/OscOutboundPacketStream.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ext/OSCPack/ip/IpEndpointName.cpp
|
||||
${OSCPACK_PLATFORM_DIR}/NetworkingUtils.cpp
|
||||
${OSCPACK_PLATFORM_DIR}/UdpSocket.cpp
|
||||
)
|
||||
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}.")
|
||||
|
||||
#
|
||||
# STB
|
||||
#
|
||||
@@ -159,22 +212,29 @@ if(WIN32)
|
||||
endif( WIN32 )
|
||||
|
||||
#
|
||||
# TINY FILE DIALOG
|
||||
# FILE DIALOG: use tinyfiledialog for all except Linux
|
||||
#
|
||||
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}.")
|
||||
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()
|
||||
|
||||
#
|
||||
# OBJ LOADER
|
||||
# OBJ LOADER - not used
|
||||
#
|
||||
#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)
|
||||
|
||||
#
|
||||
# Application
|
||||
#
|
||||
@@ -198,7 +258,8 @@ include_directories(
|
||||
${TINYFD_INCLUDE_DIR}
|
||||
${STB_INCLUDE_DIR}
|
||||
${DIRENT_INCLUDE_DIR}
|
||||
${OBJLOADER_INCLUDE_DIR}
|
||||
${OSCPACK_INCLUDE_DIR}
|
||||
${ICU_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
||||
@@ -215,7 +276,15 @@ 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
|
||||
SourceList.cpp
|
||||
Session.cpp
|
||||
Selection.cpp
|
||||
SessionSource.cpp
|
||||
@@ -223,13 +292,22 @@ set(VMIX_SRCS
|
||||
GarbageVisitor.cpp
|
||||
SessionCreator.cpp
|
||||
Mixer.cpp
|
||||
FrameGrabber.cpp
|
||||
Recorder.cpp
|
||||
Streamer.cpp
|
||||
Loopback.cpp
|
||||
Settings.cpp
|
||||
Screenshot.cpp
|
||||
Resource.cpp
|
||||
FileDialog.cpp
|
||||
Timeline.cpp
|
||||
Stream.cpp
|
||||
MediaPlayer.cpp
|
||||
MediaSource.cpp
|
||||
StreamSource.cpp
|
||||
PatternSource.cpp
|
||||
DeviceSource.cpp
|
||||
NetworkSource.cpp
|
||||
FrameBuffer.cpp
|
||||
RenderingManager.cpp
|
||||
UserInterfaceManager.cpp
|
||||
@@ -242,15 +320,27 @@ set(VMIX_SRCS
|
||||
GstToolkit.cpp
|
||||
GlmToolkit.cpp
|
||||
SystemToolkit.cpp
|
||||
DialogToolkit.cpp
|
||||
tinyxml2Toolkit.cpp
|
||||
NetworkToolkit.cpp
|
||||
Connection.cpp
|
||||
ActionManager.cpp
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
@@ -275,37 +365,77 @@ set(VMIX_RSC_FILES
|
||||
./rsc/images/transparencygrid.png
|
||||
./rsc/images/shadow.dds
|
||||
./rsc/images/glow.dds
|
||||
./rsc/images/checker.dds
|
||||
./rsc/images/shadow_perspective.dds
|
||||
./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/target.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
|
||||
./rsc/mesh/border_handles_scale.ply
|
||||
./rsc/mesh/border_handles_overlay.ply
|
||||
./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/circle.ply
|
||||
./rsc/mesh/square_point.ply
|
||||
./rsc/mesh/icon_video.ply
|
||||
./rsc/mesh/icon_image.ply
|
||||
./rsc/mesh/icon_render.ply
|
||||
./rsc/mesh/icon_gear.ply
|
||||
./rsc/mesh/icon_camera.ply
|
||||
./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
|
||||
./rsc/mesh/icon_lock.ply
|
||||
./rsc/mesh/icon_unlock.ply
|
||||
./rsc/mesh/icon_circle.ply
|
||||
./rsc/mesh/icon_square.ply
|
||||
./rsc/mesh/icon_cross.ply
|
||||
./rsc/mesh/icon_clock.ply
|
||||
./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/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)
|
||||
@@ -319,6 +449,7 @@ IF(APPLE)
|
||||
# create the application
|
||||
add_executable(${VMIX_BINARY} MACOSX_BUNDLE
|
||||
${VMIX_SRCS}
|
||||
./osx/CustomDelegate.m
|
||||
${IMGUITEXTEDIT_SRC}
|
||||
${MACOSX_BUNDLE_ICON_FILE}
|
||||
)
|
||||
@@ -327,15 +458,25 @@ IF(APPLE)
|
||||
set(MACOSX_BUNDLE_PLIST_FILE ${CMAKE_SOURCE_DIR}/osx/Info.plist)
|
||||
set_target_properties(${VMIX_BINARY} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${MACOSX_BUNDLE_PLIST_FILE})
|
||||
|
||||
set(PLATFORM_LIBS
|
||||
"-framework CoreFoundation"
|
||||
"-framework Appkit"
|
||||
)
|
||||
|
||||
ELSE(APPLE)
|
||||
|
||||
link_directories (${GTK3_LIBRARY_DIRS})
|
||||
|
||||
add_executable(${VMIX_BINARY}
|
||||
${VMIX_SRCS}
|
||||
${IMGUITEXTEDIT_SRC}
|
||||
)
|
||||
|
||||
ENDIF(APPLE)
|
||||
set(PLATFORM_LIBS
|
||||
GTK::GTK
|
||||
)
|
||||
|
||||
ENDIF(APPLE)
|
||||
|
||||
### COMPILE THE TARGET (all OS)
|
||||
|
||||
@@ -344,14 +485,12 @@ 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}
|
||||
${OPENGL_LIBRARY}
|
||||
target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
|
||||
GLAD
|
||||
TINYXML2
|
||||
IMGUI
|
||||
OSCPACK
|
||||
${GLFW_LIBRARY}
|
||||
${CMAKE_DL_LIBS}
|
||||
${GOBJECT_LIBRARIES}
|
||||
${GSTREAMER_LIBRARY}
|
||||
@@ -362,28 +501,29 @@ target_link_libraries(${VMIX_BINARY} LINK_PRIVATE
|
||||
${GSTREAMER_PBUTILS_LIBRARY}
|
||||
${GSTREAMER_GL_LIBRARY}
|
||||
${GSTREAMER_PLAYER_LIBRARY}
|
||||
${NFD_LIBRARY}
|
||||
${PNG_LIBRARY}
|
||||
${THREAD_LIBRARY}
|
||||
TINYXML2
|
||||
TINYFD
|
||||
IMGUI
|
||||
${TINYFD_LIBRARY}
|
||||
Threads::Threads
|
||||
PNG::PNG
|
||||
glm::glm
|
||||
ICU::i18n
|
||||
ICU::io
|
||||
ICU::uc
|
||||
vmix::rc
|
||||
${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 "2")
|
||||
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/"
|
||||
@@ -396,6 +536,8 @@ SET(CPACK_SOURCE_IGNORE_FILES
|
||||
|
||||
IF(APPLE)
|
||||
|
||||
# include( InstallRequiredSystemLibraries )
|
||||
|
||||
# Bundle target
|
||||
set(CPACK_GENERATOR "DragNDrop")
|
||||
|
||||
@@ -414,25 +556,42 @@ IF(APPLE)
|
||||
RUNTIME DESTINATION bin COMPONENT Runtime
|
||||
)
|
||||
|
||||
# create GST plugins directory in Bundle Resources subfolder
|
||||
set(plugin_dest_dir vimix.app/Contents/Resources/)
|
||||
install(FILES "/usr/local/Cellar/gstreamer/1.16.2/libexec/gstreamer-1.0/gst-plugin-scanner"
|
||||
|
||||
### TODO configure auto to find installation dir of gst
|
||||
|
||||
# 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"
|
||||
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.16.2/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-good/1.16.2/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-bad/1.16.2_3/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-plugins-ugly/1.16.2_1/lib/gstreamer-1.0" DESTINATION "${plugin_dest_dir}" COMPONENT Runtime)
|
||||
install(DIRECTORY "/usr/local/Cellar/gst-libav/1.16.2/lib/gstreamer-1.0" 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 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)
|
||||
|
||||
# package runtime fixup bundle
|
||||
install(CODE "
|
||||
file(GLOB_RECURSE GSTPLUGINS \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/gstreamer-1.0/*.so\")
|
||||
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\")
|
||||
include(BundleUtilities)
|
||||
set(BU_CHMOD_BUNDLE_ITEMS TRUE)
|
||||
fixup_bundle(\"${APPS}\" \"\${GSTPLUGINS}\" \"\")
|
||||
fixup_bundle(\"${APPS}\" \"\${GSTPLUGINS}\" \"${LIBS_PATH}\")
|
||||
"
|
||||
COMPONENT Runtime
|
||||
)
|
||||
@@ -452,7 +611,7 @@ ELSE(APPLE)
|
||||
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)
|
||||
|
||||
292
Connection.cpp
Normal file
292
Connection.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
|
||||
#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"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define CONNECTION_DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
Connection::Connection()
|
||||
{
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
if (receiver_!=nullptr) {
|
||||
receiver_->Break();
|
||||
delete receiver_;
|
||||
}
|
||||
}
|
||||
|
||||
bool Connection::init()
|
||||
{
|
||||
// add default info for myself
|
||||
connections_.push_back(ConnectionInfo());
|
||||
|
||||
// try to open a socket at base handshake port
|
||||
int trial = 0;
|
||||
while (trial < MAX_HANDSHAKE) {
|
||||
try {
|
||||
// increment the port to have unique ports
|
||||
connections_[0].port_handshake = HANDSHAKE_PORT + trial;
|
||||
connections_[0].port_stream_request = STREAM_REQUEST_PORT + trial;
|
||||
connections_[0].port_osc = OSC_DIALOG_PORT + trial;
|
||||
|
||||
// try to create listenning socket
|
||||
// through exception runtime if fails
|
||||
receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS,
|
||||
connections_[0].port_handshake ), &listener_ );
|
||||
// validate hostname
|
||||
connections_[0].name = APP_NAME "@" + NetworkToolkit::hostname() +
|
||||
"." + std::to_string(connections_[0].port_handshake-HANDSHAKE_PORT);
|
||||
// all good
|
||||
trial = MAX_HANDSHAKE;
|
||||
}
|
||||
catch (const std::runtime_error&) {
|
||||
// arg, the receiver could not be initialized
|
||||
// because the port was not available
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
// try again
|
||||
trial++;
|
||||
}
|
||||
|
||||
// perfect, we could initialize the receiver
|
||||
if (receiver_!=nullptr) {
|
||||
// listen for answers
|
||||
std::thread(listen).detach();
|
||||
// regularly check for available streaming hosts
|
||||
std::thread(ask).detach();
|
||||
|
||||
// inform the application settings of our id
|
||||
Settings::application.instance_id = connections_[0].port_handshake - HANDSHAKE_PORT;
|
||||
// use or replace instance name from settings
|
||||
// if (Settings::application.instance_names.count(Settings::application.instance_id))
|
||||
// connections_[0].name = Settings::application.instance_names[Settings::application.instance_id];
|
||||
// else
|
||||
// Settings::application.instance_names[Settings::application.instance_id] = connections_[0].name;
|
||||
// restore state of Streamer
|
||||
Streaming::manager().enable( Settings::application.accept_connections );
|
||||
|
||||
}
|
||||
|
||||
return receiver_ != nullptr;
|
||||
}
|
||||
|
||||
void Connection::terminate()
|
||||
{
|
||||
if (receiver_!=nullptr)
|
||||
receiver_->AsynchronousBreak();
|
||||
|
||||
// restore state of Streamer
|
||||
Streaming::manager().enable( false );
|
||||
}
|
||||
|
||||
int Connection::numHosts () const
|
||||
{
|
||||
return connections_.size();
|
||||
}
|
||||
|
||||
ConnectionInfo Connection::info(int index)
|
||||
{
|
||||
if (connections_.empty()) {
|
||||
connections_.push_back(ConnectionInfo());
|
||||
}
|
||||
|
||||
index = CLAMP(index, 0, (int) connections_.size());
|
||||
|
||||
return connections_[index];
|
||||
}
|
||||
|
||||
|
||||
struct hasName: public std::unary_function<ConnectionInfo, bool>
|
||||
{
|
||||
inline bool operator()(const ConnectionInfo &elem) const {
|
||||
return (elem.name.compare(_a) == 0);
|
||||
}
|
||||
explicit hasName(const std::string &a) : _a(a) { }
|
||||
private:
|
||||
std::string _a;
|
||||
};
|
||||
|
||||
|
||||
int Connection::index(const std::string &name) const
|
||||
{
|
||||
int id = -1;
|
||||
|
||||
std::vector<ConnectionInfo>::const_iterator p = std::find_if(connections_.begin(), connections_.end(), hasName(name));
|
||||
if (p != connections_.end())
|
||||
id = std::distance(connections_.begin(), p);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
int Connection::index(ConnectionInfo i) const
|
||||
{
|
||||
int id = -1;
|
||||
|
||||
std::vector<ConnectionInfo>::const_iterator p = std::find(connections_.begin(), connections_.end(), i);
|
||||
if (p != connections_.end())
|
||||
id = std::distance(connections_.begin(), p);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Connection::print()
|
||||
{
|
||||
for(int 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);
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::listen()
|
||||
{
|
||||
#ifdef CONNECTION_DEBUG
|
||||
Log::Info("Accepting handshake on port %d", Connection::manager().connections_[0].port_handshake);
|
||||
#endif
|
||||
Connection::manager().receiver_->Run();
|
||||
}
|
||||
|
||||
void Connection::ask()
|
||||
{
|
||||
// prepare OSC PING message
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
p.Clear();
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_PING );
|
||||
p << Connection::manager().connections_[0].port_handshake;
|
||||
p << osc::EndMessage;
|
||||
|
||||
UdpSocket socket;
|
||||
socket.SetEnableBroadcast(true);
|
||||
|
||||
// loop infinitely
|
||||
while(true)
|
||||
{
|
||||
// broadcast on several ports
|
||||
for(int i=HANDSHAKE_PORT; i<HANDSHAKE_PORT+MAX_HANDSHAKE; i++)
|
||||
socket.SendTo( IpEndpointName( i ), p.Data(), p.Size() );
|
||||
|
||||
// wait a bit
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
// 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(); ) {
|
||||
// decrease life score
|
||||
(*it).alive--;
|
||||
// erase connection if its life score is negative (not responding too many times)
|
||||
if ( (*it).alive < 0 ) {
|
||||
// inform streamer to cancel streaming to this client
|
||||
Streaming::manager().removeStreams( (*it).name );
|
||||
// remove from list
|
||||
it = Connection::manager().connections_.erase(it);
|
||||
#ifdef CONNECTION_DEBUG
|
||||
Log::Info("List of connection updated:");
|
||||
Connection::manager().print();
|
||||
#endif
|
||||
}
|
||||
// loop
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ConnectionRequestListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint )
|
||||
{
|
||||
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
|
||||
remoteEndpoint.AddressAndPortAsString(sender);
|
||||
|
||||
// get ip of connection (without port)
|
||||
std::string remote_ip(sender);
|
||||
remote_ip = remote_ip.substr(0, remote_ip.find_last_of(":"));
|
||||
|
||||
|
||||
try{
|
||||
// ping request : reply with pong
|
||||
if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_PING) == 0 ){
|
||||
|
||||
// PING message has parameter : port where to reply
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
int remote_port = (arg++)->AsInt32();
|
||||
|
||||
// ignore requests from myself
|
||||
if ( !NetworkToolkit::is_host_ip(remote_ip)
|
||||
|| Connection::manager().connections_[0].port_handshake != remote_port) {
|
||||
|
||||
// build message
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
p.Clear();
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_PONG );
|
||||
p << Connection::manager().connections_[0].name.c_str();
|
||||
p << Connection::manager().connections_[0].port_handshake;
|
||||
p << Connection::manager().connections_[0].port_stream_request;
|
||||
p << Connection::manager().connections_[0].port_osc;
|
||||
p << osc::EndMessage;
|
||||
|
||||
// send OSC message to port indicated by remote
|
||||
IpEndpointName host( remote_ip.c_str(), remote_port );
|
||||
UdpTransmitSocket socket( host );
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
|
||||
}
|
||||
}
|
||||
// pong response: add info
|
||||
else if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_PONG) == 0 ){
|
||||
|
||||
// create info struct
|
||||
ConnectionInfo info;
|
||||
info.address = remote_ip;
|
||||
|
||||
// add all ports info
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
info.name = std::string( (arg++)->AsString() );
|
||||
info.port_handshake = (arg++)->AsInt32();
|
||||
info.port_stream_request = (arg++)->AsInt32();
|
||||
info.port_osc = (arg++)->AsInt32();
|
||||
|
||||
// do we know this connection ?
|
||||
int i = Connection::manager().index(info);
|
||||
if ( i < 0) {
|
||||
// a new connection! Add to list
|
||||
Connection::manager().connections_.push_back(info);
|
||||
// replace instance name in settings
|
||||
// int id = info.port_handshake - HANDSHAKE_PORT;
|
||||
// Settings::application.instance_names[id] = info.name;
|
||||
|
||||
#ifdef CONNECTION_DEBUG
|
||||
Log::Info("List of connection updated:");
|
||||
Connection::manager().print();
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
// we know this connection: keep its status to ALIVE
|
||||
Connection::manager().connections_[i].alive = ALIVE;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch( osc::Exception& e ){
|
||||
// any parsing errors such as unexpected argument types, or
|
||||
// missing arguments get thrown as exceptions.
|
||||
Log::Info("error while parsing message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
97
Connection.h
Normal file
97
Connection.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "osc/OscReceivedElements.h"
|
||||
#include "osc/OscPacketListener.h"
|
||||
#include "ip/UdpSocket.h"
|
||||
|
||||
#include "NetworkToolkit.h"
|
||||
|
||||
#define ALIVE 3
|
||||
|
||||
class ConnectionRequestListener : public osc::OscPacketListener {
|
||||
|
||||
protected:
|
||||
virtual void ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint );
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
|
||||
std::string address;
|
||||
int port_handshake;
|
||||
int port_stream_request;
|
||||
int port_osc;
|
||||
std::string name;
|
||||
int alive;
|
||||
|
||||
ConnectionInfo () {
|
||||
address = "127.0.0.1";
|
||||
port_handshake = HANDSHAKE_PORT;
|
||||
port_stream_request = STREAM_REQUEST_PORT;
|
||||
port_osc = OSC_DIALOG_PORT;
|
||||
name = "";
|
||||
alive = ALIVE;
|
||||
}
|
||||
|
||||
inline ConnectionInfo& operator = (const ConnectionInfo& o)
|
||||
{
|
||||
if (this != &o) {
|
||||
this->address = o.address;
|
||||
this->port_handshake = o.port_handshake;
|
||||
this->port_stream_request = o.port_stream_request;
|
||||
this->port_osc = o.port_osc;
|
||||
this->name = o.name;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool operator == (const ConnectionInfo& o) const
|
||||
{
|
||||
return this->address.compare(o.address) == 0
|
||||
&& this->port_handshake == o.port_handshake;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class Connection
|
||||
{
|
||||
friend class ConnectionRequestListener;
|
||||
|
||||
// Private Constructor
|
||||
Connection();
|
||||
Connection(Connection const& copy); // Not Implemented
|
||||
Connection& operator=(Connection const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
static Connection& manager()
|
||||
{
|
||||
// The only instance
|
||||
static Connection _instance;
|
||||
return _instance;
|
||||
}
|
||||
~Connection();
|
||||
|
||||
bool init();
|
||||
void terminate();
|
||||
|
||||
int numHosts () const;
|
||||
int index(ConnectionInfo i) const;
|
||||
int index(const std::string &name) const;
|
||||
ConnectionInfo info(int index = 0); // index 0 for self
|
||||
|
||||
private:
|
||||
|
||||
static void ask();
|
||||
static void listen();
|
||||
ConnectionRequestListener listener_;
|
||||
UdpListeningReceiveSocket *receiver_;
|
||||
|
||||
std::vector< ConnectionInfo > connections_;
|
||||
|
||||
void print();
|
||||
};
|
||||
|
||||
#endif // CONNECTION_H
|
||||
385
Decorations.cpp
385
Decorations.cpp
@@ -8,10 +8,12 @@
|
||||
#include "BoundingBoxVisitor.h"
|
||||
#include "ImageShader.h"
|
||||
#include "GlmToolkit.h"
|
||||
#include "Resource.h"
|
||||
#include "Log.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) {
|
||||
@@ -19,33 +21,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:
|
||||
@@ -62,32 +85,6 @@ Frame::Frame(CornerType corner, BorderType border, ShadowType shadow) : Node(),
|
||||
break;
|
||||
}
|
||||
|
||||
// switch (type) {
|
||||
// case SHARP_LARGE:
|
||||
// square_ = sharpframe;
|
||||
// shadow_ = shadows[0];
|
||||
// break;
|
||||
// case SHARP_THIN:
|
||||
// square_ = sharpframe;
|
||||
// break;
|
||||
// case ROUND_LARGE:
|
||||
// side_ = frames[2];
|
||||
// top_ = frames[3];
|
||||
// shadow_ = shadows[1];
|
||||
// break;
|
||||
// default:
|
||||
// case ROUND_THIN:
|
||||
// side_ = frames[0];
|
||||
// top_ = frames[1];
|
||||
// shadow_ = shadows[1];
|
||||
// break;
|
||||
// case ROUND_THIN_PERSPECTIVE:
|
||||
// side_ = frames[0];
|
||||
// top_ = frames[1];
|
||||
// shadow_ = shadows[2];
|
||||
// break;
|
||||
// }
|
||||
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
@@ -101,8 +98,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_)
|
||||
@@ -112,9 +111,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();
|
||||
@@ -127,27 +128,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);
|
||||
@@ -157,17 +159,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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,17 +192,40 @@ void Frame::accept(Visitor& v)
|
||||
|
||||
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_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_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_ == ROTATE ) {
|
||||
handle_ = handle_rotation_;
|
||||
if ( type_ == Handles::ROTATE ) {
|
||||
handle_ = handle_rotation;
|
||||
}
|
||||
else if ( type_ == Handles::SCALE ) {
|
||||
handle_ = handle_scale;
|
||||
}
|
||||
else if ( type_ == Handles::MENU ) {
|
||||
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;
|
||||
}
|
||||
|
||||
Handles::~Handles()
|
||||
@@ -206,28 +240,31 @@ void Handles::update( float dt )
|
||||
|
||||
void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
|
||||
if ( !initialized() ) {
|
||||
if(handle_ && !handle_->initialized())
|
||||
handle_->init();
|
||||
if(shadow_ && !shadow_->initialized())
|
||||
shadow_->init();
|
||||
init();
|
||||
}
|
||||
|
||||
if ( visible_ ) {
|
||||
static Mesh *handle_active = new Mesh("mesh/border_handles_overlay_filled.ply");
|
||||
|
||||
// set color
|
||||
handle_->shader()->color = color;
|
||||
handle_active->shader()->color = color;
|
||||
|
||||
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) );
|
||||
// vec = modelview * glm::vec4(0.f, 1.f, 0.f, 1.f);
|
||||
// glm::vec3 scale( vec.x > 0.f ? 1.f : -1.f, vec.y > 0.f ? 1.f : -1.f, 1.f);
|
||||
// glm::vec3 scale(1.f, 1.f, 1.f);
|
||||
glm::vec4 vec;
|
||||
glm::vec3 tra, rot, sca;
|
||||
|
||||
// Log::Info(" (0,1) becomes (%f, %f)", scale.x, scale.y);
|
||||
// get rotation and mirroring from the modelview
|
||||
GlmToolkit::inverse_transform(modelview, tra, rot, sca);
|
||||
glm::vec3 mirror = glm::sign(sca);
|
||||
|
||||
if ( type_ == RESIZE ) {
|
||||
if ( type_ == Handles::RESIZE ) {
|
||||
|
||||
// 4 corners
|
||||
vec = modelview * glm::vec4(1.f, -1.f, 0.f, 1.f);
|
||||
@@ -245,8 +282,14 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
vec = modelview * glm::vec4(-1.f, 1.f, 0.f, 1.f);
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
handle_->draw( ctm, projection );
|
||||
|
||||
if ( glm::length(corner_) > 0.f ) {
|
||||
vec = modelview * glm::vec4(corner_.x, corner_.y, 0.f, 1.f);
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
handle_active->draw( ctm, projection );
|
||||
}
|
||||
}
|
||||
else if ( type_ == RESIZE_H ){
|
||||
else if ( type_ == Handles::RESIZE_H ){
|
||||
// left and right
|
||||
vec = modelview * glm::vec4(1.f, 0.f, 0.f, 1.f);
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
@@ -255,8 +298,14 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
vec = modelview * glm::vec4(-1.f, 0.f, 0.f, 1.f);
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
handle_->draw( ctm, projection );
|
||||
|
||||
if ( glm::length(corner_) > 0.f ) {
|
||||
vec = modelview * glm::vec4(corner_.x, corner_.y, 0.f, 1.f);
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
handle_active->draw( ctm, projection );
|
||||
}
|
||||
}
|
||||
else if ( type_ == RESIZE_V ){
|
||||
else if ( type_ == Handles::RESIZE_V ){
|
||||
// top and bottom
|
||||
vec = modelview * glm::vec4(0.f, 1.f, 0.f, 1.f);
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
@@ -265,29 +314,71 @@ void Handles::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
vec = modelview * glm::vec4(0.f, -1.f, 0.f, 1.f);
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
handle_->draw( ctm, projection );
|
||||
|
||||
if ( glm::length(corner_) > 0.f ) {
|
||||
vec = modelview * glm::vec4(corner_.x, corner_.y, 0.f, 1.f);
|
||||
ctm = GlmToolkit::transform(vec, rot, glm::vec3(1.f));
|
||||
handle_active->draw( ctm, projection );
|
||||
}
|
||||
}
|
||||
else if ( type_ == ROTATE ){
|
||||
else if ( type_ == Handles::ROTATE ){
|
||||
// one icon in top right corner
|
||||
// 1. Fixed displacement by (0.12,0.12) along the rotation..
|
||||
ctm = GlmToolkit::transform(glm::vec4(0.f), rot, glm::vec3(1.f));
|
||||
glm::vec4 pos = ctm * glm::vec4(0.12f, 0.12f, 0.f, 1.f);
|
||||
// Log::Info(" (0.12,0.12) becomes (%f, %f)", pos.x, pos.y);
|
||||
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 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));
|
||||
|
||||
// TODO fix problem with negative scale
|
||||
// glm::vec4 target = modelview * glm::vec4(1.2f, 1.2f, 0.f, 1.f);
|
||||
|
||||
// vec = modelview * glm::vec4(1.f, 1.f, 0.f, 1.f);
|
||||
// glm::vec4 dv = target - vec;
|
||||
|
||||
// Log::Info("dv (%f, %f)", dv.x, dv.y);
|
||||
// float m = dv.x < dv.y ? dv.x : dv.y;
|
||||
// Log::Info("min %f", m);
|
||||
|
||||
// ctm = GlmToolkit::transform( glm::vec3(target), rot, glm::vec3(1.f));
|
||||
|
||||
// 3. draw
|
||||
shadow_->draw( ctm, projection );
|
||||
handle_->draw( ctm, projection );
|
||||
}
|
||||
else if ( type_ == Handles::SCALE ){
|
||||
// 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 );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
else if ( type_ == Handles::MENU ){
|
||||
// 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 top 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 );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -303,20 +394,71 @@ void Handles::accept(Visitor& v)
|
||||
|
||||
Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
|
||||
{
|
||||
static Mesh *icons[9] = {nullptr};
|
||||
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[POINT] = new Mesh("mesh/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[DOTS] = new Mesh("mesh/icon_dots.ply");
|
||||
icons[CIRCLES] = new Mesh("mesh/icon_circles.ply");
|
||||
icons[EMPTY] = new Mesh("mesh/icon_empty.ply");
|
||||
icons[CIRCLE_POINT] = new Mesh("mesh/point.ply");
|
||||
shadows[CIRCLE_POINT] = nullptr;
|
||||
icons[SQUARE_POINT] = new Mesh("mesh/square_point.ply");
|
||||
shadows[SQUARE_POINT] = nullptr;
|
||||
icons[IMAGE] = new Mesh("mesh/icon_image.ply");
|
||||
shadows[IMAGE] = 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;
|
||||
}
|
||||
|
||||
|
||||
symbol_ = icons[type_];
|
||||
shadow_ = shadows[type_];
|
||||
translation_ = pos;
|
||||
color = glm::vec4( 1.f, 1.f, 1.f, 1.f);
|
||||
}
|
||||
@@ -331,6 +473,8 @@ void Symbol::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
if ( !initialized() ) {
|
||||
if(symbol_ && !symbol_->initialized())
|
||||
symbol_->init();
|
||||
if(shadow_ && !shadow_->initialized())
|
||||
shadow_->init();
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -339,11 +483,26 @@ void Symbol::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
// set color
|
||||
symbol_->shader()->color = color;
|
||||
|
||||
glm::mat4 ctm = modelview * transform_;
|
||||
// correct for aspect ratio
|
||||
glm::vec4 vec = ctm * glm::vec4(1.f, 1.0f, 0.f, 0.f);
|
||||
ctm *= glm::scale(glm::identity<glm::mat4>(), glm::vec3( vec.y / vec.x, 1.f, 1.f));
|
||||
// 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::vec3 sca = glm::vec3(vec.y , vec.y, 1.f) * glm::vec3(scale_.y, scale_.y, 1.f);
|
||||
// extract translation
|
||||
glm::vec3 tran = glm::vec3(modelview[3][0], modelview[3][1], modelview[3][2]) ;
|
||||
tran += translation_ * glm::vec3(vec);
|
||||
// apply local rotation
|
||||
rot.z += rotation_.z;
|
||||
// generate matrix
|
||||
ctm = GlmToolkit::transform(tran, rot, sca);
|
||||
|
||||
if (shadow_)
|
||||
shadow_->draw( ctm, projection );
|
||||
symbol_->draw( ctm, projection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 } Type;
|
||||
typedef enum { RESIZE = 0, RESIZE_H, RESIZE_V, ROTATE, SCALE, CROP, MENU, LOCKED, UNLOCKED } Type;
|
||||
Handles(Type type);
|
||||
~Handles();
|
||||
|
||||
@@ -46,29 +46,36 @@ public:
|
||||
|
||||
Type type() const { return type_; }
|
||||
Primitive *handle() const { return handle_; }
|
||||
void overlayActiveCorner(glm::vec2 v) {corner_ = v;}
|
||||
|
||||
glm::vec4 color;
|
||||
|
||||
protected:
|
||||
Primitive *handle_;
|
||||
Mesh *handle_;
|
||||
Mesh *shadow_;
|
||||
glm::vec2 corner_;
|
||||
Type type_;
|
||||
|
||||
};
|
||||
|
||||
class Symbol : public Node
|
||||
{
|
||||
public:
|
||||
typedef enum { POINT = 0, IMAGE, VIDEO, SESSION, CLONE, RENDER, DOTS, CIRCLES, EMPTY } Type;
|
||||
Symbol(Type t = POINT, glm::vec3 pos = glm::vec3(0.f));
|
||||
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, CUBE, SHARE,
|
||||
DOTS, BUSY, LOCK, UNLOCK, EYE, EYESLASH, VECTORSLASH, ARROWS, ROTATION, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
|
||||
Symbol(Type t = CIRCLE_POINT, glm::vec3 pos = glm::vec3(0.f));
|
||||
~Symbol();
|
||||
|
||||
void draw (glm::mat4 modelview, glm::mat4 projection) override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox bbox() const { return symbol_->bbox(); }
|
||||
|
||||
Type type() const { return type_; }
|
||||
glm::vec4 color;
|
||||
|
||||
protected:
|
||||
Mesh *symbol_;
|
||||
Mesh *shadow_;
|
||||
Type type_;
|
||||
};
|
||||
|
||||
|
||||
573
DeviceSource.cpp
Normal file
573
DeviceSource.cpp
Normal file
@@ -0,0 +1,573 @@
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <gst/pbutils/gstdiscoverer.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "DeviceSource.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Decorations.h"
|
||||
#include "Stream.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define DEVICE_DEBUG
|
||||
//#define GST_DEVICE_DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if defined(APPLE)
|
||||
std::string gst_plugin_device = "avfvideosrc";
|
||||
std::string gst_plugin_vidcap = "avfvideosrc capture-screen=true";
|
||||
#else
|
||||
std::string gst_plugin_device = "v4l2src";
|
||||
std::string gst_plugin_vidcap = "ximagesrc";
|
||||
#endif
|
||||
|
||||
////EXAMPLE :
|
||||
///
|
||||
//v4l2deviceprovider, udev-probed=(boolean)true,
|
||||
//device.bus_path=(string)pci-0000:00:14.0-usb-0:2:1.0,
|
||||
//sysfs.path=(string)/sys/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/video4linux/video0,
|
||||
//device.bus=(string)usb,
|
||||
//device.subsystem=(string)video4linux,
|
||||
//device.vendor.id=(string)1bcf,
|
||||
//device.vendor.name=(string)"Sunplus\\x20IT\\x20Co\\x20",
|
||||
//device.product.id=(string)2286,
|
||||
//device.product.name=(string)"AUSDOM\ FHD\ Camera:\ AUSDOM\ FHD\ C",
|
||||
//device.serial=(string)Sunplus_IT_Co_AUSDOM_FHD_Camera,
|
||||
//device.capabilities=(string):capture:,
|
||||
//device.api=(string)v4l2,
|
||||
//device.path=(string)/dev/video0,
|
||||
//v4l2.device.driver=(string)uvcvideo,
|
||||
//v4l2.device.card=(string)"AUSDOM\ FHD\ Camera:\ AUSDOM\ FHD\ C",
|
||||
//v4l2.device.bus_info=(string)usb-0000:00:14.0-2,
|
||||
//v4l2.device.version=(uint)328748,
|
||||
//v4l2.device.capabilities=(uint)2225078273,
|
||||
//v4l2.device.device_caps=(uint)69206017;
|
||||
//Device added: AUSDOM FHD Camera: AUSDOM FHD C - v4l2src device=/dev/video0
|
||||
|
||||
//v4l2deviceprovider, udev-probed=(boolean)true,
|
||||
//device.bus_path=(string)pci-0000:00:14.0-usb-0:4:1.0,
|
||||
//sysfs.path=(string)/sys/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0/video4linux/video2,
|
||||
//device.bus=(string)usb,
|
||||
//device.subsystem=(string)video4linux,
|
||||
//device.vendor.id=(string)046d,
|
||||
//device.vendor.name=(string)046d,
|
||||
//device.product.id=(string)080f,
|
||||
//device.product.name=(string)"UVC\ Camera\ \(046d:080f\)",
|
||||
//device.serial=(string)046d_080f_3EA77580,
|
||||
//device.capabilities=(string):capture:,
|
||||
//device.api=(string)v4l2,
|
||||
//device.path=(string)/dev/video2,
|
||||
//v4l2.device.driver=(string)uvcvideo,
|
||||
//v4l2.device.card=(string)"UVC\ Camera\ \(046d:080f\)",
|
||||
//v4l2.device.bus_info=(string)usb-0000:00:14.0-4,
|
||||
//v4l2.device.version=(uint)328748,
|
||||
//v4l2.device.capabilities=(uint)2225078273,
|
||||
//v4l2.device.device_caps=(uint)69206017; // decimal of hexadecimal v4l code Device Caps : 0x04200001
|
||||
//Device added: UVC Camera (046d:080f) - v4l2src device=/dev/video2
|
||||
|
||||
std::string pipelineForDevice(GstDevice *device, uint index)
|
||||
{
|
||||
std::ostringstream pipe;
|
||||
const gchar *str = gst_structure_get_string(gst_device_get_properties(device), "device.api");
|
||||
|
||||
if (str && gst_plugin_device.find(str) != std::string::npos)
|
||||
{
|
||||
pipe << gst_plugin_device;
|
||||
|
||||
#if defined(APPLE)
|
||||
pipe << " device-index=" << index;
|
||||
#else
|
||||
str = gst_structure_get_string(gst_device_get_properties(device), "device.path");
|
||||
if (str)
|
||||
pipe << " device=" << str;
|
||||
#endif
|
||||
}
|
||||
|
||||
return pipe.str();
|
||||
}
|
||||
|
||||
gboolean
|
||||
Device::callback_device_monitor (GstBus *, GstMessage * message, gpointer )
|
||||
{
|
||||
GstDevice *device;
|
||||
gchar *name;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_DEVICE_ADDED: {
|
||||
gst_message_parse_device_added (message, &device);
|
||||
name = gst_device_get_display_name (device);
|
||||
|
||||
// ignore if already in the list
|
||||
if ( std::find(manager().src_name_.begin(), manager().src_name_.end(), name) != manager().src_name_.end())
|
||||
break;
|
||||
|
||||
manager().src_name_.push_back(name);
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
|
||||
g_print("\nDevice %s plugged : %s\n", name, stru);
|
||||
g_free (stru);
|
||||
#endif
|
||||
g_free (name);
|
||||
|
||||
std::string p = pipelineForDevice(device, manager().src_description_.size());
|
||||
manager().src_description_.push_back(p);
|
||||
|
||||
DeviceConfigSet confs = getDeviceConfigs(p);
|
||||
manager().src_config_.push_back(confs);
|
||||
|
||||
manager().list_uptodate_ = false;
|
||||
|
||||
gst_object_unref (device);
|
||||
}
|
||||
break;
|
||||
case GST_MESSAGE_DEVICE_REMOVED: {
|
||||
gst_message_parse_device_removed (message, &device);
|
||||
name = gst_device_get_display_name (device);
|
||||
manager().remove(name);
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
g_print("\nDevice %s unplugged\n", name);
|
||||
#endif
|
||||
g_free (name);
|
||||
|
||||
manager().list_uptodate_ = false;
|
||||
|
||||
gst_object_unref (device);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
void Device::remove(const char *device)
|
||||
{
|
||||
std::vector< std::string >::iterator nameit = src_name_.begin();
|
||||
std::vector< std::string >::iterator descit = src_description_.begin();
|
||||
std::vector< DeviceConfigSet >::iterator coit = src_config_.begin();
|
||||
while (nameit != src_name_.end()){
|
||||
|
||||
if ( (*nameit).compare(device) == 0 )
|
||||
{
|
||||
src_name_.erase(nameit);
|
||||
src_description_.erase(descit);
|
||||
src_config_.erase(coit);
|
||||
break;
|
||||
}
|
||||
|
||||
++nameit;
|
||||
++descit;
|
||||
++coit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Device::Device()
|
||||
{
|
||||
GstBus *bus;
|
||||
GstCaps *caps;
|
||||
|
||||
// create GStreamer device monitor to capture
|
||||
// when a device is plugged in or out
|
||||
monitor_ = gst_device_monitor_new ();
|
||||
|
||||
bus = gst_device_monitor_get_bus (monitor_);
|
||||
gst_bus_add_watch (bus, callback_device_monitor, NULL);
|
||||
gst_object_unref (bus);
|
||||
|
||||
caps = gst_caps_new_empty_simple ("video/x-raw");
|
||||
gst_device_monitor_add_filter (monitor_, "Video/Source", caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
gst_device_monitor_set_show_all_devices(monitor_, true);
|
||||
gst_device_monitor_start (monitor_);
|
||||
|
||||
// initial fill of the list
|
||||
GList *devices = gst_device_monitor_get_devices(monitor_);
|
||||
GList *tmp;
|
||||
for (tmp = devices; tmp ; tmp = tmp->next ) {
|
||||
|
||||
GstDevice *device = (GstDevice *) tmp->data;
|
||||
|
||||
gchar *name = gst_device_get_display_name (device);
|
||||
src_name_.push_back(name);
|
||||
g_free (name);
|
||||
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
|
||||
g_print("\nDevice %s already plugged : %s", name, stru);
|
||||
g_free (stru);
|
||||
#endif
|
||||
|
||||
std::string p = pipelineForDevice(device, src_description_.size());
|
||||
src_description_.push_back(p);
|
||||
|
||||
DeviceConfigSet confs = getDeviceConfigs(p);
|
||||
src_config_.push_back(confs);
|
||||
}
|
||||
g_list_free(devices);
|
||||
|
||||
// Add config for plugged screen
|
||||
src_name_.push_back("Screen capture");
|
||||
src_description_.push_back(gst_plugin_vidcap);
|
||||
|
||||
// Try to auto find resolution
|
||||
DeviceConfigSet confs = getDeviceConfigs(gst_plugin_vidcap);
|
||||
if (!confs.empty()) {
|
||||
// fix the framerate (otherwise at 1 FPS
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
DeviceConfigSet confscreen;
|
||||
best.fps_numerator = 15;
|
||||
confscreen.insert(best);
|
||||
src_config_.push_back(confscreen);
|
||||
}
|
||||
|
||||
// TODO Use lib glfw to get monitors
|
||||
// TODO Detect auto removal of monitors
|
||||
|
||||
list_uptodate_ = true;
|
||||
}
|
||||
|
||||
|
||||
int Device::numDevices() const
|
||||
{
|
||||
return src_name_.size();
|
||||
}
|
||||
|
||||
bool Device::exists(const std::string &device) const
|
||||
{
|
||||
std::vector< std::string >::const_iterator d = std::find(src_name_.begin(), src_name_.end(), device);
|
||||
return d != src_name_.end();
|
||||
}
|
||||
|
||||
struct hasDevice: public std::unary_function<DeviceSource*, bool>
|
||||
{
|
||||
inline bool operator()(const DeviceSource* elem) const {
|
||||
return (elem && elem->device() == _d);
|
||||
}
|
||||
explicit hasDevice(const std::string &d) : _d(d) { }
|
||||
private:
|
||||
std::string _d;
|
||||
};
|
||||
|
||||
Source *Device::createSource(const std::string &device) const
|
||||
{
|
||||
Source *s = nullptr;
|
||||
|
||||
// find if a DeviceSource with this device is already registered
|
||||
std::list< DeviceSource *>::const_iterator d = std::find_if(device_sources_.begin(), device_sources_.end(), hasDevice(device));
|
||||
|
||||
// if already registered, clone the device source
|
||||
if ( d != device_sources_.end()) {
|
||||
CloneSource *cs = (*d)->clone();
|
||||
s = cs;
|
||||
}
|
||||
// otherwise, we are free to create a new device source
|
||||
else {
|
||||
DeviceSource *ds = new DeviceSource();
|
||||
ds->setDevice(device);
|
||||
s = ds;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
bool Device::unplugged(const std::string &device) const
|
||||
{
|
||||
if (list_uptodate_)
|
||||
return false;
|
||||
return !exists(device);
|
||||
}
|
||||
|
||||
std::string Device::name(int index) const
|
||||
{
|
||||
if (index > -1 && index < (int) src_name_.size())
|
||||
return src_name_[index];
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string Device::description(int index) const
|
||||
{
|
||||
if (index > -1 && index < (int) src_description_.size())
|
||||
return src_description_[index];
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
DeviceConfigSet Device::config(int index) const
|
||||
{
|
||||
if (index > -1 && index < (int) src_config_.size())
|
||||
return src_config_[index];
|
||||
else
|
||||
return DeviceConfigSet();
|
||||
}
|
||||
|
||||
int Device::index(const std::string &device) const
|
||||
{
|
||||
int i = -1;
|
||||
std::vector< std::string >::const_iterator p = std::find(src_name_.begin(), src_name_.end(), device);
|
||||
if (p != src_name_.end())
|
||||
i = std::distance(src_name_.begin(), p);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
DeviceSource::DeviceSource() : StreamSource()
|
||||
{
|
||||
// create stream
|
||||
stream_ = new Stream;
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::CAMERA, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
DeviceSource::~DeviceSource()
|
||||
{
|
||||
// unregister this device source
|
||||
Device::manager().device_sources_.remove(this);
|
||||
}
|
||||
|
||||
void DeviceSource::setDevice(const std::string &devicename)
|
||||
{
|
||||
device_ = devicename;
|
||||
Log::Notify("Creating Source with device '%s'", device_.c_str());
|
||||
|
||||
int index = Device::manager().index(device_);
|
||||
if (index > -1) {
|
||||
|
||||
// register this device source
|
||||
Device::manager().device_sources_.push_back(this);
|
||||
|
||||
// start filling in the gstreamer pipeline
|
||||
std::ostringstream pipeline;
|
||||
pipeline << Device::manager().description(index);
|
||||
|
||||
// test the device and get config
|
||||
DeviceConfigSet confs = Device::manager().config(index);
|
||||
#ifdef DEVICE_DEBUG
|
||||
Log::Info("Device %s supported configs:", devicename.c_str());
|
||||
for( DeviceConfigSet::iterator it = confs.begin(); it != confs.end(); ++it ){
|
||||
float fps = static_cast<float>((*it).fps_numerator) / static_cast<float>((*it).fps_denominator);
|
||||
Log::Info(" - %s %s %d x %d %.1f fps", (*it).stream.c_str(), (*it).format.c_str(), (*it).width, (*it).height, fps);
|
||||
}
|
||||
#endif
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
|
||||
Log::Info("Device %s selected its optimal config: %s %s %dx%d@%.1ffps", device_.c_str(), best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
|
||||
|
||||
pipeline << " ! " << best.stream;
|
||||
if (!best.format.empty())
|
||||
pipeline << ",format=" << best.format;
|
||||
pipeline << ",framerate=" << best.fps_numerator << "/" << best.fps_denominator;
|
||||
pipeline << ",width=" << best.width;
|
||||
pipeline << ",height=" << best.height;
|
||||
|
||||
if ( best.stream.find("jpeg") != std::string::npos )
|
||||
pipeline << " ! jpegdec";
|
||||
|
||||
if ( device_.find("Screen") != std::string::npos )
|
||||
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue max-size-buffers=3";
|
||||
|
||||
pipeline << " ! videoconvert";
|
||||
|
||||
stream_->open( pipeline.str(), best.width, best.height);
|
||||
stream_->play(true);
|
||||
}
|
||||
else
|
||||
Log::Warning("No such device '%s'", device_.c_str());
|
||||
}
|
||||
|
||||
void DeviceSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
bool DeviceSource::failed() const
|
||||
{
|
||||
return stream_->failed() || Device::manager().unplugged(device_);
|
||||
}
|
||||
|
||||
DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
|
||||
{
|
||||
DeviceConfigSet configs;
|
||||
|
||||
// create dummy pipeline to be tested
|
||||
std::string description = src_description;
|
||||
description += " name=devsrc ! fakesink name=sink";
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
GstElement *pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("DeviceSource Could not construct test pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
return configs;
|
||||
}
|
||||
|
||||
// get the pipeline element named "devsrc" from the Device class
|
||||
GstElement *elem = gst_bin_get_by_name (GST_BIN (pipeline_), "devsrc");
|
||||
if (elem) {
|
||||
|
||||
// initialize the pipeline
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PAUSED);
|
||||
if (ret != GST_STATE_CHANGE_FAILURE) {
|
||||
|
||||
// get the first pad and its content
|
||||
GstIterator *iter = gst_element_iterate_src_pads(elem);
|
||||
GValue vPad = G_VALUE_INIT;
|
||||
GstPad* pad_ret = NULL;
|
||||
if (gst_iterator_next(iter, &vPad) == GST_ITERATOR_OK)
|
||||
{
|
||||
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);
|
||||
for (int c = 0; c < C; ++c) {
|
||||
// get GST cap
|
||||
GstStructure *decice_cap_struct = gst_caps_get_structure (device_caps, c);
|
||||
#ifdef GST_DEVICE_DEBUG
|
||||
gchar *capstext = gst_structure_to_string (decice_cap_struct);
|
||||
g_print("\nDevice caps: %s", capstext);
|
||||
g_free(capstext);
|
||||
#endif
|
||||
|
||||
// fill our config
|
||||
DeviceConfig config;
|
||||
|
||||
// not managing opengl texture-target types
|
||||
// TODO: support input devices texture-target video/x-raw(memory:GLMemory) for improved pipeline
|
||||
if ( gst_structure_has_field (decice_cap_struct, "texture-target"))
|
||||
continue;
|
||||
|
||||
// NAME : typically video/x-raw or image/jpeg
|
||||
config.stream = gst_structure_get_name (decice_cap_struct);
|
||||
|
||||
// FORMAT : typically BGRA or YUVY
|
||||
if ( gst_structure_has_field (decice_cap_struct, "format")) {
|
||||
// get generic value
|
||||
const GValue *val = gst_structure_get_value(decice_cap_struct, "format");
|
||||
|
||||
// if its a list of format string
|
||||
if ( GST_VALUE_HOLDS_LIST(val)) {
|
||||
int N = gst_value_list_get_size(val);
|
||||
for (int n = 0; n < N; n++ ){
|
||||
std::string f = gst_value_serialize( gst_value_list_get_value(val, n) );
|
||||
|
||||
// preference order : 1) RGBx, 2) JPEG, 3) ALL OTHER
|
||||
// select f if it contains R (e.g. for RGBx) and not already RGB in config
|
||||
if ( (f.find("R") != std::string::npos) && (config.format.find("R") == std::string::npos ) ) {
|
||||
config.format = f;
|
||||
break;
|
||||
}
|
||||
// default, take at least one if nothing yet in config
|
||||
else if ( config.format.empty() )
|
||||
config.format = f;
|
||||
}
|
||||
|
||||
}
|
||||
// single format
|
||||
else {
|
||||
config.format = gst_value_serialize(val);
|
||||
}
|
||||
}
|
||||
|
||||
// FRAMERATE : can be a fraction of a list of fractions
|
||||
if ( gst_structure_has_field (decice_cap_struct, "framerate")) {
|
||||
|
||||
// get generic value
|
||||
const GValue *val = gst_structure_get_value(decice_cap_struct, "framerate");
|
||||
// if its a single fraction
|
||||
if ( GST_VALUE_HOLDS_FRACTION(val)) {
|
||||
config.fps_numerator = gst_value_get_fraction_numerator(val);
|
||||
config.fps_denominator= gst_value_get_fraction_denominator(val);
|
||||
}
|
||||
// if its a range of fraction; take the max
|
||||
else if ( GST_VALUE_HOLDS_FRACTION_RANGE(val)) {
|
||||
config.fps_numerator = gst_value_get_fraction_numerator(gst_value_get_fraction_range_max(val));
|
||||
config.fps_denominator= gst_value_get_fraction_denominator(gst_value_get_fraction_range_max(val));
|
||||
}
|
||||
// deal otherwise with a list of fractions; find the max
|
||||
else if ( GST_VALUE_HOLDS_LIST(val)) {
|
||||
gdouble fps_max = 1.0;
|
||||
// loop over all fractions
|
||||
int N = gst_value_list_get_size(val);
|
||||
for (int 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);
|
||||
int d = gst_value_get_fraction_denominator(frac);
|
||||
// keep only the higher FPS
|
||||
gdouble f = 1.0;
|
||||
gst_util_fraction_to_double( n, d, &f );
|
||||
if ( f > fps_max ) {
|
||||
config.fps_numerator = n;
|
||||
config.fps_denominator = d;
|
||||
fps_max = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WIDTH and HEIGHT
|
||||
if ( gst_structure_has_field (decice_cap_struct, "width"))
|
||||
gst_structure_get_int (decice_cap_struct, "width", &config.width);
|
||||
if ( gst_structure_has_field (decice_cap_struct, "height"))
|
||||
gst_structure_get_int (decice_cap_struct, "height", &config.height);
|
||||
|
||||
|
||||
// add this config
|
||||
configs.insert(config);
|
||||
}
|
||||
|
||||
}
|
||||
gst_iterator_free(iter);
|
||||
|
||||
// terminate pipeline
|
||||
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
}
|
||||
|
||||
g_object_unref (elem);
|
||||
}
|
||||
|
||||
gst_object_unref (pipeline_);
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
|
||||
glm::ivec2 DeviceSource::icon() const
|
||||
{
|
||||
if ( device_.find("Screen") != std::string::npos )
|
||||
return glm::ivec2(19, 1);
|
||||
else
|
||||
return glm::ivec2(2, 14);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
130
DeviceSource.h
Normal file
130
DeviceSource.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#ifndef DEVICESOURCE_H
|
||||
#define DEVICESOURCE_H
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "GstToolkit.h"
|
||||
#include "StreamSource.h"
|
||||
|
||||
class DeviceSource : public StreamSource
|
||||
{
|
||||
public:
|
||||
DeviceSource();
|
||||
~DeviceSource();
|
||||
|
||||
// Source interface
|
||||
bool failed() const override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
// StreamSource interface
|
||||
Stream *stream() const override { return stream_; }
|
||||
|
||||
// specific interface
|
||||
void setDevice(const std::string &devicename);
|
||||
inline std::string device() const { return device_; }
|
||||
|
||||
glm::ivec2 icon() const override;
|
||||
|
||||
private:
|
||||
std::string device_;
|
||||
|
||||
};
|
||||
|
||||
struct DeviceConfig {
|
||||
gint width;
|
||||
gint height;
|
||||
gint fps_numerator;
|
||||
gint fps_denominator;
|
||||
std::string stream;
|
||||
std::string format;
|
||||
|
||||
DeviceConfig() {
|
||||
width = 0;
|
||||
height = 0;
|
||||
fps_numerator = 1;
|
||||
fps_denominator = 1;
|
||||
stream = "";
|
||||
format = "";
|
||||
}
|
||||
|
||||
inline DeviceConfig& operator = (const DeviceConfig& b)
|
||||
{
|
||||
if (this != &b) {
|
||||
this->width = b.width;
|
||||
this->height = b.height;
|
||||
this->fps_numerator = b.fps_numerator;
|
||||
this->fps_denominator = b.fps_denominator;
|
||||
this->stream = b.stream;
|
||||
this->format = b.format;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool operator < (const DeviceConfig b) const
|
||||
{
|
||||
int formatscore = this->format.find("R") != std::string::npos ? 2 : 1; // best score for RGBx
|
||||
int b_formatscore = b.format.find("R") != std::string::npos ? 2 : 1;
|
||||
float fps = static_cast<float>(this->fps_numerator) / static_cast<float>(this->fps_denominator);
|
||||
float b_fps = static_cast<float>(b.fps_numerator) / static_cast<float>(b.fps_denominator);
|
||||
return ( fps * static_cast<float>(this->height * formatscore) < b_fps * static_cast<float>(b.height * b_formatscore));
|
||||
}
|
||||
};
|
||||
|
||||
struct better_device_comparator
|
||||
{
|
||||
inline bool operator () (const DeviceConfig a, const DeviceConfig b) const
|
||||
{
|
||||
return (a < b);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::set<DeviceConfig, better_device_comparator> DeviceConfigSet;
|
||||
|
||||
|
||||
class Device
|
||||
{
|
||||
friend class DeviceSource;
|
||||
|
||||
Device();
|
||||
Device(Device const& copy); // Not Implemented
|
||||
Device& operator=(Device const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
|
||||
static Device& manager()
|
||||
{
|
||||
// The only instance
|
||||
static Device _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
int numDevices () const;
|
||||
std::string name (int index) const;
|
||||
std::string description (int index) const;
|
||||
DeviceConfigSet config (int index) const;
|
||||
|
||||
int index (const std::string &device) const;
|
||||
bool exists (const std::string &device) const;
|
||||
bool unplugged (const std::string &device) const;
|
||||
|
||||
Source *createSource(const std::string &device) const;
|
||||
|
||||
static gboolean callback_device_monitor (GstBus *, GstMessage *, gpointer);
|
||||
static DeviceConfigSet getDeviceConfigs(const std::string &src_description);
|
||||
|
||||
private:
|
||||
|
||||
void remove(const char *device);
|
||||
|
||||
std::vector< std::string > src_name_;
|
||||
std::vector< std::string > src_description_;
|
||||
std::vector< DeviceConfigSet > src_config_;
|
||||
bool list_uptodate_;
|
||||
GstDeviceMonitor *monitor_;
|
||||
|
||||
std::list< DeviceSource * > device_sources_;
|
||||
};
|
||||
|
||||
|
||||
#endif // DEVICESOURCE_H
|
||||
323
DialogToolkit.cpp
Normal file
323
DialogToolkit.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
|
||||
// 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
|
||||
|
||||
#if defined(LINUX)
|
||||
#define USE_TINYFILEDIALOG 0
|
||||
#else
|
||||
#define USE_TINYFILEDIALOG 1
|
||||
#endif
|
||||
|
||||
#include "defines.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "DialogToolkit.h"
|
||||
|
||||
#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, filterPatterns[i] );
|
||||
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
|
||||
|
||||
std::string DialogToolkit::saveSessionFileDialog(const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
char const * save_pattern[1] = { "*.mix" };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * save_file_name;
|
||||
|
||||
save_file_name = tinyfd_saveFileDialog( "Save a session file", path.c_str(), 1, save_pattern, "vimix session");
|
||||
|
||||
if (save_file_name)
|
||||
filename = std::string(save_file_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( "Save Session File", 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 (extension != "mix")
|
||||
filename += ".mix";
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
std::string DialogToolkit::openSessionFileDialog(const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[1] = { "*.mix" };
|
||||
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( "Import a file", startpath.c_str(), 1, open_pattern, "vimix session", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( "Open Session File", 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 DialogToolkit::ImportFileDialog(const std::string &path)
|
||||
{
|
||||
std::string filename = "";
|
||||
std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path();
|
||||
char const * open_pattern[18] = { "*.mix", "*.mp4", "*.mpg",
|
||||
"*.avi", "*.mov", "*.mkv",
|
||||
"*.webm", "*.mod", "*.wmv",
|
||||
"*.mxf", "*.ogg", "*.flv",
|
||||
"*.asf", "*.jpg", "*.png",
|
||||
"*.gif", "*.tif", "*.svg" };
|
||||
#if USE_TINYFILEDIALOG
|
||||
char const * open_file_name;
|
||||
open_file_name = tinyfd_openFileDialog( "Import a file", startpath.c_str(), 18, open_pattern, "All supported formats", 0);
|
||||
|
||||
if (open_file_name)
|
||||
filename = std::string(open_file_name);
|
||||
#else
|
||||
|
||||
if (!gtk_init()) {
|
||||
ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return filename;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( "Import Media File", NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT, NULL );
|
||||
|
||||
// set file filters
|
||||
add_filter_file_dialog(dialog, 18, open_pattern, "All supported formats");
|
||||
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 DialogToolkit::FolderDialog(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("Select folder", startpath.c_str());
|
||||
|
||||
if (open_folder_name)
|
||||
foldername = std::string(open_folder_name);
|
||||
#else
|
||||
if (!gtk_init()) {
|
||||
ErrorDialog("Could not initialize GTK+ for dialog");
|
||||
return foldername;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_file_chooser_dialog_new( "Select folder", 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;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
24
DialogToolkit.h
Normal file
24
DialogToolkit.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef DIALOGTOOLKIT_H
|
||||
#define DIALOGTOOLKIT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace DialogToolkit
|
||||
{
|
||||
|
||||
std::string saveSessionFileDialog(const std::string &path);
|
||||
|
||||
std::string openSessionFileDialog(const std::string &path);
|
||||
|
||||
std::string ImportFileDialog(const std::string &path);
|
||||
|
||||
std::string FolderDialog(const std::string &path);
|
||||
|
||||
void ErrorDialog(const char* message);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // DIALOGTOOLKIT_H
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
@@ -6,16 +7,21 @@
|
||||
#include "Scene.h"
|
||||
|
||||
|
||||
DrawVisitor::DrawVisitor(Node *nodetodraw, glm::mat4 projection)
|
||||
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):
|
||||
force_(force), targets_(nodestodraw), modelview_(glm::identity<glm::mat4>()),
|
||||
projection_(projection), num_duplicat_(1), transform_duplicat_(glm::identity<glm::mat4>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void DrawVisitor::loop(int num, glm::mat4 transform)
|
||||
{
|
||||
@@ -25,19 +31,30 @@ void DrawVisitor::loop(int num, glm::mat4 transform)
|
||||
|
||||
void DrawVisitor::visit(Node &n)
|
||||
{
|
||||
// draw the target
|
||||
if ( 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,12 +64,12 @@ 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++) {
|
||||
if ( (*node)->visible_ )
|
||||
for (NodeSet::iterator node = n.begin(); !targets_.empty() && node != n.end(); ++node) {
|
||||
if ( (*node)->visible_ || force_)
|
||||
(*node)->accept(*this);
|
||||
modelview_ = mv;
|
||||
}
|
||||
@@ -60,17 +77,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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
#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);
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
#include "FileDialog.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
|
||||
#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 +23,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;
|
||||
|
||||
11
FileDialog.h
11
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&) {} // Prevent construction by copying
|
||||
FileDialog& operator =(const FileDialog&) { return *this; } // Prevent assignment
|
||||
~FileDialog(); // Prevent unwanted destruction
|
||||
|
||||
public:
|
||||
|
||||
203
FrameBuffer.cpp
203
FrameBuffer.cpp
@@ -1,15 +1,20 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "FrameBuffer.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Settings.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
const char* FrameBuffer::aspect_ratio_name[4] = { "4:3", "3:2", "16:10", "16:9" };
|
||||
glm::vec2 FrameBuffer::aspect_ratio_size[4] = { glm::vec2(4.f,3.f), glm::vec2(3.f,2.f), glm::vec2(16.f,10.f), glm::vec2(16.f,9.f) };
|
||||
const char* FrameBuffer::resolution_name[4] = { "720p", "1080p", "1440", "4K" };
|
||||
const char* FrameBuffer::aspect_ratio_name[5] = { "4:3", "3:2", "16:10", "16:9", "21:9" };
|
||||
glm::vec2 FrameBuffer::aspect_ratio_size[5] = { glm::vec2(4.f,3.f), glm::vec2(3.f,2.f), glm::vec2(16.f,10.f), glm::vec2(16.f,9.f) , glm::vec2(21.f,9.f) };
|
||||
const char* FrameBuffer::resolution_name[4] = { "720", "1080", "1440", "2160" };
|
||||
float FrameBuffer::resolution_height[4] = { 720.f, 1080.f, 1440.f, 2160.f };
|
||||
|
||||
|
||||
@@ -21,16 +26,47 @@ glm::vec3 FrameBuffer::getResolutionFromParameters(int ar, int h)
|
||||
return res;
|
||||
}
|
||||
|
||||
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)
|
||||
glm::ivec2 FrameBuffer::getParametersFromResolution(glm::vec3 res)
|
||||
{
|
||||
attrib_.viewport = glm::ivec2(resolution);
|
||||
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, use_alpha_ ? 0.f : 1.f);
|
||||
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(uint width, uint height, bool useAlpha, bool multiSampling): textureid_(0), intermediate_textureid_(0), framebufferid_(0), intermediate_framebufferid_(0), use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
|
||||
FrameBuffer::FrameBuffer(glm::vec3 resolution, bool useAlpha, bool multiSampling):
|
||||
textureid_(0), intermediate_textureid_(0), framebufferid_(0), intermediate_framebufferid_(0),
|
||||
use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
|
||||
{
|
||||
attrib_.viewport = glm::ivec2(resolution);
|
||||
setProjectionArea(glm::vec2(1.f, 1.f));
|
||||
attrib_.clear_color = glm::vec4(0.f, 0.f, 0.f, 0.f);
|
||||
}
|
||||
|
||||
FrameBuffer::FrameBuffer(uint width, uint height, bool useAlpha, bool multiSampling):
|
||||
textureid_(0), intermediate_textureid_(0), framebufferid_(0), intermediate_framebufferid_(0),
|
||||
use_alpha_(useAlpha), use_multi_sampling_(multiSampling)
|
||||
{
|
||||
attrib_.viewport = glm::ivec2(width, height);
|
||||
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()
|
||||
@@ -61,16 +97,18 @@ void FrameBuffer::init()
|
||||
use_alpha_ ? GL_RGBA8 : GL_RGB8, attrib_.viewport.x, attrib_.viewport.y, GL_TRUE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
|
||||
|
||||
// attach the multisampled texture to FBO (currently binded)
|
||||
// attach the multisampled texture to FBO (framebufferid_ currently binded)
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, intermediate_textureid_, 0);
|
||||
|
||||
// create an intermediate FBO
|
||||
// create an intermediate FBO : this is the FBO to use for reading
|
||||
glGenFramebuffers(1, &intermediate_framebufferid_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, intermediate_framebufferid_);
|
||||
|
||||
// attach the 2D texture to intermediate FBO
|
||||
// attach the 2D texture to intermediate FBO (intermediate_framebufferid_)
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
|
||||
|
||||
// Log::Info("New FBO %d Multi Sampling ", framebufferid_);
|
||||
@@ -93,6 +131,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_);
|
||||
}
|
||||
|
||||
|
||||
@@ -110,12 +154,24 @@ float FrameBuffer::aspectRatio() const
|
||||
}
|
||||
|
||||
|
||||
std::string FrameBuffer::info() const
|
||||
{
|
||||
glm::ivec2 p = FrameBuffer::getParametersFromResolution(resolution());
|
||||
std::ostringstream info;
|
||||
|
||||
info << attrib_.viewport.x << "x" << attrib_.viewport.y;
|
||||
if (p.x > -1)
|
||||
info << "px, " << FrameBuffer::aspect_ratio_name[p.x];
|
||||
|
||||
return info.str();
|
||||
}
|
||||
|
||||
glm::vec3 FrameBuffer::resolution() const
|
||||
{
|
||||
return glm::vec3(attrib_.viewport.x, attrib_.viewport.y, 0.f);
|
||||
}
|
||||
|
||||
void FrameBuffer::begin()
|
||||
void FrameBuffer::begin(bool clear)
|
||||
{
|
||||
if (!framebufferid_)
|
||||
init();
|
||||
@@ -124,7 +180,8 @@ void FrameBuffer::begin()
|
||||
|
||||
Rendering::manager().pushAttrib(attrib_);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
if (clear)
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void FrameBuffer::end()
|
||||
@@ -149,7 +206,7 @@ void FrameBuffer::release()
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void FrameBuffer::readPixels()
|
||||
void FrameBuffer::readPixels(uint8_t *target_data)
|
||||
{
|
||||
if (!framebufferid_)
|
||||
return;
|
||||
@@ -164,21 +221,24 @@ 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)
|
||||
return false;
|
||||
|
||||
if (!destination->framebufferid_)
|
||||
destination->init();
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, other->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;
|
||||
}
|
||||
@@ -219,3 +279,106 @@ void FrameBuffer::checkFramebufferStatus()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
glm::mat4 FrameBuffer::projection() const
|
||||
{
|
||||
return projection_;
|
||||
}
|
||||
|
||||
glm::vec2 FrameBuffer::projectionArea() const
|
||||
{
|
||||
return projection_area_;
|
||||
}
|
||||
|
||||
void FrameBuffer::setProjectionArea(glm::vec2 c)
|
||||
{
|
||||
projection_area_.x = CLAMP(c.x, 0.1f, 1.f);
|
||||
projection_area_.y = CLAMP(c.y, 0.1f, 1.f);
|
||||
projection_ = glm::ortho(-projection_area_.x, projection_area_.x, projection_area_.y, -projection_area_.y, -1.f, 1.f);
|
||||
}
|
||||
|
||||
|
||||
FrameBufferImage::FrameBufferImage(int w, int h) :
|
||||
rgb(nullptr), width(w), height(h)
|
||||
{
|
||||
if (width>0 && height>0)
|
||||
rgb = new uint8_t[width*height*3];
|
||||
}
|
||||
|
||||
FrameBufferImage::FrameBufferImage(jpegBuffer jpgimg) :
|
||||
rgb(nullptr), width(0), height(0)
|
||||
{
|
||||
int c = 0;
|
||||
if (jpgimg.buffer != nullptr && jpgimg.len >0)
|
||||
rgb = stbi_load_from_memory(jpgimg.buffer, jpgimg.len, &width, &height, &c, 3);
|
||||
}
|
||||
|
||||
FrameBufferImage::~FrameBufferImage() {
|
||||
if (rgb!=nullptr)
|
||||
delete rgb;
|
||||
}
|
||||
|
||||
FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg()
|
||||
{
|
||||
jpegBuffer jpgimg;
|
||||
|
||||
// if we hold a valid image
|
||||
if (rgb!=nullptr && width>0 && height>0) {
|
||||
|
||||
// allocate JPEG buffer
|
||||
// (NB: JPEG will need less than this but we can't know before...)
|
||||
jpgimg.buffer = (unsigned char *) malloc( width * height * 3 * sizeof(unsigned char));
|
||||
|
||||
stbi_write_jpg_to_func( [](void *context, void *data, int size)
|
||||
{
|
||||
memcpy(((FrameBufferImage::jpegBuffer*)context)->buffer + ((FrameBufferImage::jpegBuffer*)context)->len, data, size);
|
||||
((FrameBufferImage::jpegBuffer*)context)->len += size;
|
||||
}
|
||||
,&jpgimg, width, height, 3, rgb, FBI_JPEG_QUALITY);
|
||||
}
|
||||
|
||||
return jpgimg;
|
||||
}
|
||||
|
||||
FrameBufferImage *FrameBuffer::image(){
|
||||
|
||||
FrameBufferImage *img = nullptr;
|
||||
|
||||
// not ready
|
||||
if (!framebufferid_)
|
||||
return img;
|
||||
|
||||
// allocate image
|
||||
img = new FrameBufferImage(attrib_.viewport.x, attrib_.viewport.y);
|
||||
|
||||
// get pixels into image
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // set buffer target readpixel
|
||||
readPixels(img->rgb);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
bool FrameBuffer::fill(FrameBufferImage *image)
|
||||
{
|
||||
if (!framebufferid_)
|
||||
init();
|
||||
|
||||
// not compatible for RGB
|
||||
if (use_alpha_ || use_multi_sampling_)
|
||||
return false;
|
||||
|
||||
// invalid image
|
||||
if ( image == nullptr ||
|
||||
image->rgb==nullptr ||
|
||||
image->width !=attrib_.viewport.x ||
|
||||
image->height!=attrib_.viewport.y )
|
||||
return false;
|
||||
|
||||
// fill texture with image
|
||||
glBindTexture(GL_TEXTURE_2D, textureid_);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height,
|
||||
GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,45 @@
|
||||
#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 = nullptr;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
struct jpegBuffer {
|
||||
unsigned char *buffer = nullptr;
|
||||
uint len = 0;
|
||||
};
|
||||
jpegBuffer getJpeg();
|
||||
|
||||
FrameBufferImage(int w, int h);
|
||||
FrameBufferImage(jpegBuffer jpgimg);
|
||||
~FrameBufferImage();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The FrameBuffer class holds an OpenGL Frame Buffer Object.
|
||||
*/
|
||||
class FrameBuffer {
|
||||
|
||||
public:
|
||||
// size descriptions
|
||||
static const char* aspect_ratio_name[4];
|
||||
static glm::vec2 aspect_ratio_size[4];
|
||||
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 glm::vec3 getResolutionFromParameters(int ar, int h);
|
||||
static glm::ivec2 getParametersFromResolution(glm::vec3 res);
|
||||
// unbind any framebuffer object
|
||||
static void release();
|
||||
|
||||
@@ -21,15 +48,14 @@ public:
|
||||
~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; }
|
||||
@@ -40,6 +66,12 @@ public:
|
||||
inline uint height() const { return attrib_.viewport.y; }
|
||||
glm::vec3 resolution() const;
|
||||
float aspectRatio() const;
|
||||
std::string info() const;
|
||||
|
||||
// projection area (crop)
|
||||
glm::mat4 projection() const;
|
||||
glm::vec2 projectionArea() const;
|
||||
void setProjectionArea(glm::vec2 c);
|
||||
|
||||
// internal pixel format
|
||||
inline bool use_alpha() const { return use_alpha_; }
|
||||
@@ -48,14 +80,21 @@ public:
|
||||
// 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_area_;
|
||||
uint textureid_, intermediate_textureid_;
|
||||
uint framebufferid_, intermediate_framebufferid_;
|
||||
bool use_alpha_, use_multi_sampling_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
357
FrameGrabber.cpp
Normal file
357
FrameGrabber.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
#include <algorithm>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// gstreamer
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "FrameBuffer.h"
|
||||
|
||||
#include "FrameGrabber.h"
|
||||
|
||||
|
||||
|
||||
FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(NULL)
|
||||
{
|
||||
pbo_[0] = 0;
|
||||
pbo_[1] = 0;
|
||||
}
|
||||
|
||||
FrameGrabbing::~FrameGrabbing()
|
||||
{
|
||||
// stop and delete all frame grabbers
|
||||
clearAll();
|
||||
|
||||
// cleanup
|
||||
if (caps_)
|
||||
gst_caps_unref (caps_);
|
||||
if (pbo_[0])
|
||||
glDeleteBuffers(2, pbo_);
|
||||
}
|
||||
|
||||
void FrameGrabbing::add(FrameGrabber *rec)
|
||||
{
|
||||
if (rec != nullptr)
|
||||
grabbers_.push_back(rec);
|
||||
}
|
||||
|
||||
FrameGrabber *FrameGrabbing::front()
|
||||
{
|
||||
if (grabbers_.empty())
|
||||
return nullptr;
|
||||
else
|
||||
return grabbers_.front();
|
||||
}
|
||||
|
||||
struct fgId: public std::unary_function<FrameGrabber*, bool>
|
||||
{
|
||||
inline bool operator()(const FrameGrabber* elem) const {
|
||||
return (elem && elem->id() == _id);
|
||||
}
|
||||
explicit fgId(uint64_t id) : _id(id) { }
|
||||
private:
|
||||
uint64_t _id;
|
||||
};
|
||||
|
||||
FrameGrabber *FrameGrabbing::get(uint64_t id)
|
||||
{
|
||||
if (id > 0 && grabbers_.size() > 0 )
|
||||
{
|
||||
std::list<FrameGrabber *>::iterator iter = std::find_if(grabbers_.begin(), grabbers_.end(), fgId(id));
|
||||
if (iter != grabbers_.end())
|
||||
return (*iter);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FrameGrabbing::stopAll()
|
||||
{
|
||||
std::list<FrameGrabber *>::iterator iter;
|
||||
for (iter=grabbers_.begin(); iter != grabbers_.end(); ++iter )
|
||||
(*iter)->stop();
|
||||
}
|
||||
|
||||
void FrameGrabbing::clearAll()
|
||||
{
|
||||
std::list<FrameGrabber *>::iterator iter;
|
||||
for (iter=grabbers_.begin(); iter != grabbers_.end(); )
|
||||
{
|
||||
FrameGrabber *rec = *iter;
|
||||
rec->stop();
|
||||
iter = grabbers_.erase(iter);
|
||||
delete rec;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
|
||||
{
|
||||
if (frame_buffer == nullptr)
|
||||
return;
|
||||
|
||||
// if different frame buffer from previous frame
|
||||
if ( frame_buffer->width() != width_ ||
|
||||
frame_buffer->height() != height_ ||
|
||||
frame_buffer->use_alpha() != use_alpha_) {
|
||||
|
||||
// define stream properties
|
||||
width_ = frame_buffer->width();
|
||||
height_ = frame_buffer->height();
|
||||
use_alpha_ = frame_buffer->use_alpha();
|
||||
size_ = width_ * height_ * (use_alpha_ ? 4 : 3);
|
||||
|
||||
// first time initialization
|
||||
if ( pbo_[0] == 0 )
|
||||
glGenBuffers(2, pbo_);
|
||||
|
||||
// re-affect pixel buffer object
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[1]);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
||||
|
||||
// reset indices
|
||||
pbo_index_ = 0;
|
||||
pbo_next_index_ = 0;
|
||||
|
||||
// new caps
|
||||
if (caps_)
|
||||
gst_caps_unref (caps_);
|
||||
caps_ = gst_caps_new_simple ("video/x-raw",
|
||||
"format", G_TYPE_STRING, use_alpha_ ? "RGBA" : "RGB",
|
||||
"width", G_TYPE_INT, width_,
|
||||
"height", G_TYPE_INT, height_,
|
||||
"framerate", GST_TYPE_FRACTION, 30, 1,
|
||||
NULL);
|
||||
}
|
||||
|
||||
// fill a frame in buffer
|
||||
if (!grabbers_.empty() && size_ > 0) {
|
||||
|
||||
GstBuffer *buffer = nullptr;
|
||||
|
||||
// set buffer target for writing in a new frame
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_index_]);
|
||||
|
||||
#ifdef USE_GLREADPIXEL
|
||||
// get frame
|
||||
frame_buffer->readPixels();
|
||||
#else
|
||||
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
|
||||
// update case ; alternating indices
|
||||
if ( pbo_next_index_ != pbo_index_ ) {
|
||||
|
||||
// set buffer target for saving the frame
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_next_index_]);
|
||||
|
||||
// new buffer
|
||||
buffer = gst_buffer_new_and_alloc (size_);
|
||||
|
||||
// map gst buffer into a memory WRITE target
|
||||
GstMapInfo map;
|
||||
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
|
||||
|
||||
// map PBO pixels into a memory READ pointer
|
||||
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
|
||||
// transfer pixels from PBO memory to buffer memory
|
||||
if (NULL != ptr)
|
||||
memmove(map.data, ptr, size_);
|
||||
|
||||
// un-map
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
// alternate indices
|
||||
pbo_next_index_ = pbo_index_;
|
||||
pbo_index_ = (pbo_index_ + 1) % 2;
|
||||
|
||||
// a frame was successfully grabbed
|
||||
if (buffer != nullptr) {
|
||||
|
||||
// give the frame to all recorders
|
||||
std::list<FrameGrabber *>::iterator iter = grabbers_.begin();
|
||||
while (iter != grabbers_.end())
|
||||
{
|
||||
FrameGrabber *rec = *iter;
|
||||
rec->addFrame(buffer, caps_, dt);
|
||||
|
||||
if (rec->finished()) {
|
||||
iter = grabbers_.erase(iter);
|
||||
delete rec;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
|
||||
// unref / free the frame
|
||||
gst_buffer_unref(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
FrameGrabber::FrameGrabber(): finished_(false), active_(false), accept_buffer_(false),
|
||||
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timestamp_(0)
|
||||
{
|
||||
// unique id
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
// configure fix parameter
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
|
||||
timeframe_ = 2 * frame_duration_;
|
||||
}
|
||||
|
||||
FrameGrabber::~FrameGrabber()
|
||||
{
|
||||
if (src_ != nullptr)
|
||||
gst_object_unref (src_);
|
||||
if (caps_ != nullptr)
|
||||
gst_caps_unref (caps_);
|
||||
if (pipeline_ != nullptr) {
|
||||
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline_);
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameGrabber::finished() const
|
||||
{
|
||||
return finished_;
|
||||
}
|
||||
|
||||
bool FrameGrabber::busy() const
|
||||
{
|
||||
if (active_)
|
||||
return accept_buffer_ ? true : false;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
double FrameGrabber::duration() const
|
||||
{
|
||||
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
|
||||
}
|
||||
|
||||
void FrameGrabber::stop ()
|
||||
{
|
||||
// send end of stream
|
||||
gst_app_src_end_of_stream (src_);
|
||||
|
||||
// stop recording
|
||||
active_ = false;
|
||||
}
|
||||
|
||||
std::string FrameGrabber::info() const
|
||||
{
|
||||
if (active_)
|
||||
return GstToolkit::time_to_string(timestamp_);
|
||||
else
|
||||
return "Inactive";
|
||||
}
|
||||
|
||||
// appsrc needs data and we should start sending
|
||||
void FrameGrabber::callback_need_data (GstAppSrc *, guint , gpointer p)
|
||||
{
|
||||
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
|
||||
if (grabber)
|
||||
grabber->accept_buffer_ = true;
|
||||
}
|
||||
|
||||
// appsrc has enough data and we can stop sending
|
||||
void FrameGrabber::callback_enough_data (GstAppSrc *, gpointer p)
|
||||
{
|
||||
FrameGrabber *grabber = static_cast<FrameGrabber *>(p);
|
||||
if (grabber)
|
||||
grabber->accept_buffer_ = false;
|
||||
}
|
||||
|
||||
void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
|
||||
{
|
||||
// ignore
|
||||
if (buffer == nullptr)
|
||||
return;
|
||||
|
||||
// first time initialization
|
||||
if (pipeline_ == nullptr)
|
||||
init(caps);
|
||||
|
||||
// cancel if finished
|
||||
if (finished_)
|
||||
return;
|
||||
|
||||
// stop if an incompatilble frame buffer given
|
||||
if ( !gst_caps_is_equal( caps_, caps ))
|
||||
{
|
||||
stop();
|
||||
// Log::Warning("FrameGrabber interrupted: new session (%s)\nincompatible with recording (%s)", gst_caps_to_string(frame.caps), gst_caps_to_string(caps_));
|
||||
Log::Warning("FrameGrabber interrupted because the resolution changed.");
|
||||
}
|
||||
|
||||
// store a frame if recording is active
|
||||
if (active_)
|
||||
{
|
||||
// calculate dt in ns
|
||||
timeframe_ += gst_gdouble_to_guint64( dt * 1000000.f );
|
||||
|
||||
// if time is passed one frame duration (with 10% margin)
|
||||
// and if the encoder accepts data
|
||||
if ( timeframe_ > frame_duration_ - 3000000 && accept_buffer_) {
|
||||
|
||||
// set timing of buffer
|
||||
buffer->pts = timestamp_;
|
||||
buffer->duration = frame_duration_;
|
||||
|
||||
// increment ref counter to make sure the frame remains available
|
||||
gst_buffer_ref(buffer);
|
||||
|
||||
// push
|
||||
gst_app_src_push_buffer (src_, buffer);
|
||||
// NB: buffer will be unrefed by the appsrc
|
||||
|
||||
accept_buffer_ = false;
|
||||
|
||||
// next timestamp
|
||||
timestamp_ += frame_duration_;
|
||||
|
||||
// restart frame counter
|
||||
timeframe_ = 0;
|
||||
}
|
||||
}
|
||||
// did the recording terminate with sink receiving end-of-stream ?
|
||||
else {
|
||||
|
||||
if (!finished_)
|
||||
{
|
||||
// Wait for EOS message
|
||||
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
|
||||
GstMessage *msg = gst_bus_poll(bus, GST_MESSAGE_EOS, GST_TIME_AS_USECONDS(1));
|
||||
// received EOS
|
||||
if (msg) {
|
||||
// stop the pipeline
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
Log::Warning("FrameGrabber Could not stop.");
|
||||
|
||||
finished_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (finished_)
|
||||
terminate();
|
||||
|
||||
}
|
||||
128
FrameGrabber.h
Normal file
128
FrameGrabber.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#ifndef FRAMEGRABBER_H
|
||||
#define FRAMEGRABBER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
// use glReadPixel or glGetTextImage
|
||||
// read pixels & pbo should be the fastest
|
||||
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
|
||||
#define USE_GLREADPIXEL
|
||||
|
||||
class FrameBuffer;
|
||||
|
||||
|
||||
/**
|
||||
* @brief The FrameGrabber class defines the base class for all recorders
|
||||
* used to save images or videos from a frame buffer.
|
||||
*
|
||||
* Every subclass shall at least implement init() and terminate()
|
||||
*
|
||||
* The FrameGrabbing manager calls addFrame() for all its grabbers.
|
||||
*/
|
||||
class FrameGrabber
|
||||
{
|
||||
friend class FrameGrabbing;
|
||||
|
||||
uint64_t id_;
|
||||
|
||||
public:
|
||||
FrameGrabber();
|
||||
virtual ~FrameGrabber();
|
||||
|
||||
inline uint64_t id() const { return id_; }
|
||||
|
||||
virtual void stop();
|
||||
virtual std::string info() const;
|
||||
virtual double duration() const;
|
||||
virtual bool finished() const;
|
||||
virtual bool busy() const;
|
||||
|
||||
protected:
|
||||
|
||||
// only FrameGrabbing manager can add frame
|
||||
virtual void addFrame(GstBuffer *buffer, GstCaps *caps, float dt);
|
||||
|
||||
// only addFrame method shall call those
|
||||
virtual void init(GstCaps *caps) = 0;
|
||||
virtual void terminate() = 0;
|
||||
|
||||
// thread-safe testing termination
|
||||
std::atomic<bool> finished_;
|
||||
std::atomic<bool> active_;
|
||||
std::atomic<bool> accept_buffer_;
|
||||
|
||||
// gstreamer pipeline
|
||||
GstElement *pipeline_;
|
||||
GstAppSrc *src_;
|
||||
GstCaps *caps_;
|
||||
GstClockTime timeframe_;
|
||||
GstClockTime timestamp_;
|
||||
GstClockTime frame_duration_;
|
||||
|
||||
// gstreamer callbacks
|
||||
static void callback_need_data (GstAppSrc *, guint, gpointer user_data);
|
||||
static void callback_enough_data (GstAppSrc *, gpointer user_data);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The FrameGrabbing class manages all frame grabbers
|
||||
*
|
||||
* Session calls grabFrame after each render
|
||||
*
|
||||
*/
|
||||
class FrameGrabbing
|
||||
{
|
||||
friend class Mixer;
|
||||
|
||||
// Private Constructor
|
||||
FrameGrabbing();
|
||||
FrameGrabbing(FrameGrabbing const& copy); // Not Implemented
|
||||
FrameGrabbing& operator=(FrameGrabbing const& copy); // Not Implemented
|
||||
|
||||
public:
|
||||
|
||||
static FrameGrabbing& manager()
|
||||
{
|
||||
// The only instance
|
||||
static FrameGrabbing _instance;
|
||||
return _instance;
|
||||
}
|
||||
~FrameGrabbing();
|
||||
|
||||
inline uint width() const { return width_; }
|
||||
inline uint height() const { return height_; }
|
||||
|
||||
void add(FrameGrabber *rec);
|
||||
FrameGrabber *front();
|
||||
FrameGrabber *get(uint64_t id);
|
||||
void stopAll();
|
||||
void clearAll();
|
||||
|
||||
protected:
|
||||
|
||||
// only for friend Session
|
||||
void grabFrame(FrameBuffer *frame_buffer, float dt);
|
||||
|
||||
private:
|
||||
std::list<FrameGrabber *> grabbers_;
|
||||
guint pbo_[2];
|
||||
guint pbo_index_;
|
||||
guint pbo_next_index_;
|
||||
guint size_;
|
||||
guint width_;
|
||||
guint height_;
|
||||
bool use_alpha_;
|
||||
GstCaps *caps_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // FRAMEGRABBER_H
|
||||
@@ -34,7 +34,7 @@ void GarbageVisitor::visit(Node &n)
|
||||
|
||||
// take the node out of the Tree
|
||||
if (current_)
|
||||
current_->detatch(&n);
|
||||
current_->detach(&n);
|
||||
|
||||
}
|
||||
|
||||
@@ -58,7 +58,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
|
||||
@@ -132,11 +132,6 @@ void GarbageVisitor::visit(LineSquare &)
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(LineCircle &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GarbageVisitor::visit(Mesh &n)
|
||||
{
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ public:
|
||||
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;
|
||||
|
||||
|
||||
1125
GeometryView.cpp
Normal file
1125
GeometryView.cpp
Normal file
File diff suppressed because it is too large
Load Diff
46
GeometryView.h
Normal file
46
GeometryView.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef GEOMETRYVIEW_H
|
||||
#define GEOMETRYVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class GeometryView : public View
|
||||
{
|
||||
public:
|
||||
GeometryView();
|
||||
|
||||
void draw () override;
|
||||
void update (float dt) override;
|
||||
void resize (int) override;
|
||||
int size () override;
|
||||
bool canSelect(Source *) override;
|
||||
|
||||
std::pair<Node *, glm::vec2> pick(glm::vec2 P) override;
|
||||
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
|
||||
void terminate() override;
|
||||
void arrow (glm::vec2) override;
|
||||
|
||||
private:
|
||||
Surface *output_surface_;
|
||||
Node *overlay_position_;
|
||||
Node *overlay_position_cross_;
|
||||
Symbol *overlay_rotation_;
|
||||
Symbol *overlay_rotation_fix_;
|
||||
Group *overlay_rotation_clock_;
|
||||
Symbol *overlay_rotation_clock_tic_;
|
||||
Node *overlay_rotation_clock_hand_;
|
||||
Symbol *overlay_scaling_;
|
||||
Symbol *overlay_scaling_cross_;
|
||||
Node *overlay_scaling_grid_;
|
||||
Node *overlay_crop_;
|
||||
|
||||
void updateSelectionOverlay() override;
|
||||
bool overlay_selection_active_;
|
||||
Group *overlay_selection_stored_status_;
|
||||
Handles *overlay_selection_scale_;
|
||||
Handles *overlay_selection_rotate_;
|
||||
|
||||
void applySelectionTransform(glm::mat4 M);
|
||||
};
|
||||
|
||||
|
||||
#endif // GEOMETRYVIEW_H
|
||||
113
GlmToolkit.cpp
113
GlmToolkit.cpp
@@ -4,8 +4,20 @@
|
||||
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <glm/gtc/random.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
glm::mat4 View = glm::translate(glm::identity<glm::mat4>(), translation);
|
||||
@@ -16,13 +28,43 @@ 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);
|
||||
}
|
||||
|
||||
void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point) // TODO why ref to point?
|
||||
//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;
|
||||
//}
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() :
|
||||
mMin(glm::vec3(1.f)), mMax(glm::vec3(-1.f))
|
||||
{
|
||||
}
|
||||
|
||||
void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point)
|
||||
{
|
||||
if (isNull()) {
|
||||
mMin = point;
|
||||
@@ -110,9 +152,10 @@ bool GlmToolkit::AxisAlignedBoundingBox::contains(const AxisAlignedBoundingBox&
|
||||
if ( !intersect(bb, ignore_z))
|
||||
return false;
|
||||
|
||||
if ( (mMin.x <= bb.mMin.x) && (mMax.x >= bb.mMax.x) &&
|
||||
(mMin.y <= bb.mMin.y) && (mMax.y >= bb.mMax.y) &&
|
||||
( ignore_z || ((mMin.z <= bb.mMin.z) && (mMax.z >= bb.mMax.z)) ) )
|
||||
if ( (mMin.x < bb.mMin.x) && (mMax.x > bb.mMax.x) &&
|
||||
(mMin.y < bb.mMin.y) && (mMax.y > bb.mMax.y)
|
||||
&& ( ignore_z || ((mMin.z < bb.mMin.z) && (mMax.z > bb.mMax.z)) )
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -131,7 +174,7 @@ bool GlmToolkit::AxisAlignedBoundingBox::contains(glm::vec3 point, bool ignore_z
|
||||
}
|
||||
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::translated(glm::vec3 t)
|
||||
GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::translated(glm::vec3 t) const
|
||||
{
|
||||
GlmToolkit::AxisAlignedBoundingBox bb;
|
||||
bb = *this;
|
||||
@@ -142,27 +185,61 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::translate
|
||||
return bb;
|
||||
}
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::scaled(glm::vec3 s)
|
||||
GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::scaled(glm::vec3 s) const
|
||||
{
|
||||
GlmToolkit::AxisAlignedBoundingBox bb;
|
||||
bb = *this;
|
||||
GlmToolkit::AxisAlignedBoundingBox bb;
|
||||
glm::vec3 vec;
|
||||
|
||||
bb.mMin *= s;
|
||||
bb.mMax *= s;
|
||||
// Apply scaling to min & max corners (can be inverted) and update bbox accordingly
|
||||
vec = mMin * s;
|
||||
bb.extend(vec);
|
||||
|
||||
vec = mMax * s;
|
||||
bb.extend(vec);
|
||||
|
||||
return bb;
|
||||
}
|
||||
|
||||
GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transformed(glm::mat4 m)
|
||||
GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transformed(glm::mat4 m) const
|
||||
{
|
||||
GlmToolkit::AxisAlignedBoundingBox bb;
|
||||
glm::vec4 vec;
|
||||
vec = m * glm::vec4(mMin, 1.f);
|
||||
bb.mMin = glm::vec3(vec);
|
||||
|
||||
vec = m * glm::vec4(mMax, 1.f);
|
||||
bb.mMax = glm::vec3(vec);
|
||||
// Apply transform to all four corners (can be rotated) and update bbox accordingly
|
||||
vec = m * glm::vec4(mMin.x, mMin.y, 0.f, 1.f);
|
||||
bb.extend(glm::vec3(vec));
|
||||
|
||||
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);
|
||||
bb.extend(glm::vec3(vec));
|
||||
|
||||
vec = m * glm::vec4(mMax.x, mMin.y, 0.f, 1.f);
|
||||
bb.extend(glm::vec3(vec));
|
||||
|
||||
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)
|
||||
{
|
||||
int ar = glm::clamp(aspectratio, 0, 5);
|
||||
int h = glm::clamp(height, 0, 8);
|
||||
|
||||
static glm::vec2 aspect_ratio_size[6] = { glm::vec2(1.f,1.f), 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) };
|
||||
static float resolution_height[10] = { 16.f, 64.f, 200.f, 320.f, 480.f, 576.f, 720.f, 1080.f, 1440.f, 2160.f };
|
||||
|
||||
float width = aspect_ratio_size[ar].x * resolution_height[h] / aspect_ratio_size[ar].y;
|
||||
glm::ivec2 res = glm::ivec2( width, resolution_height[h]);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
43
GlmToolkit.h
43
GlmToolkit.h
@@ -8,18 +8,19 @@
|
||||
namespace GlmToolkit
|
||||
{
|
||||
|
||||
glm::mat4 transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale);
|
||||
// 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 ) {
|
||||
inline void operator = (const AxisAlignedBoundingBox &D ) {
|
||||
mMin = D.mMin;
|
||||
mMax = D.mMax;
|
||||
}
|
||||
@@ -39,11 +40,37 @@ public:
|
||||
void extend(std::vector<glm::vec3> points);
|
||||
void extend(const AxisAlignedBoundingBox& bb);
|
||||
|
||||
AxisAlignedBoundingBox translated(glm::vec3 t);
|
||||
AxisAlignedBoundingBox scaled(glm::vec3 s);
|
||||
AxisAlignedBoundingBox transformed(glm::mat4 m);
|
||||
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" };
|
||||
|
||||
glm::ivec2 resolutionFromDescription(int aspectratio, int height);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
137
GstToolkit.cpp
137
GstToolkit.cpp
@@ -2,29 +2,70 @@
|
||||
#include <iomanip>
|
||||
using namespace std;
|
||||
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
#include "GstToolkit.h"
|
||||
|
||||
string GstToolkit::time_to_string(guint64 t)
|
||||
string GstToolkit::time_to_string(guint64 t, time_string_mode m)
|
||||
{
|
||||
if (t == GST_CLOCK_TIME_NONE)
|
||||
return "00:00:00.00";
|
||||
if (t == GST_CLOCK_TIME_NONE) {
|
||||
switch (m) {
|
||||
case TIME_STRING_FIXED:
|
||||
return "00:00:00.00";
|
||||
case TIME_STRING_MINIMAL:
|
||||
return "0.0";
|
||||
default:
|
||||
return "00.00";
|
||||
}
|
||||
}
|
||||
|
||||
guint ms = GST_TIME_AS_MSECONDS(t);
|
||||
guint s = ms / 1000;
|
||||
|
||||
ostringstream oss;
|
||||
if (s / 3600)
|
||||
oss << setw(2) << setfill('0') << s / 3600 << ':';
|
||||
if ((s % 3600) / 60)
|
||||
oss << setw(2) << setfill('0') << (s % 3600) / 60 << ':';
|
||||
oss << setw(2) << setfill('0') << (s % 3600) % 60 << '.';
|
||||
oss << setw(2) << setfill('0') << (ms % 1000) / 10;
|
||||
|
||||
// fixed length string (11 chars) HH:mm:ss.ii"
|
||||
// MINIMAL: keep only the 2 higher values (most significant)
|
||||
if (m == TIME_STRING_MINIMAL) {
|
||||
int count = 0;
|
||||
if (s / 3600) {
|
||||
oss << s / 3600 << ':';
|
||||
count++;
|
||||
}
|
||||
if ((s % 3600) / 60) {
|
||||
oss << (s % 3600) / 60 << ':';
|
||||
count++;
|
||||
}
|
||||
if (count < 2) {
|
||||
oss << setw(count > 0 ? 2 : 1) << setfill('0') << (s % 3600) % 60;
|
||||
count++;
|
||||
}
|
||||
if (count < 2 )
|
||||
oss << '.'<< setw(1) << setfill('0') << (ms % 1000) / 10;
|
||||
}
|
||||
else {
|
||||
// TIME_STRING_FIXED : fixed length string (11 chars) HH:mm:ss.ii"
|
||||
// TIME_STRING_RIGHT : always show the right part (seconds), not the min or hours if none
|
||||
if (m == TIME_STRING_FIXED || (s / 3600) )
|
||||
oss << setw(2) << setfill('0') << s / 3600 << ':';
|
||||
if (m == TIME_STRING_FIXED || ((s % 3600) / 60) )
|
||||
oss << setw(2) << setfill('0') << (s % 3600) / 60 << ':';
|
||||
oss << setw(2) << setfill('0') << (s % 3600) % 60 << '.';
|
||||
oss << setw(2) << setfill('0') << (ms % 1000) / 10;
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
std::string GstToolkit::filename_to_uri(std::string path)
|
||||
{
|
||||
// set uri to open
|
||||
gchar *uritmp = gst_filename_to_uri(path.c_str(), NULL);
|
||||
std::string uri( uritmp );
|
||||
g_free(uritmp);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
list<string> GstToolkit::all_plugins()
|
||||
{
|
||||
list<string> pluginlist;
|
||||
@@ -92,3 +133,77 @@ 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
|
||||
const char *plugins[10] = { "omxmpeg4videodec", "omxmpeg2dec", "omxh264dec", "vdpaumpegdec",
|
||||
"nvh264dec", "nvh265dec", "nvmpeg2videodec",
|
||||
"nvmpeg4videodec", "nvvp8dec", "nvvp9dec"
|
||||
};
|
||||
const int N = 10;
|
||||
#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 + 1 : 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) {
|
||||
gchar *name = gst_element_get_name(e);
|
||||
// g_print(" - %s", name);
|
||||
std::string e_name(name);
|
||||
g_free(name);
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (e_name.find(plugins[i]) != std::string::npos) {
|
||||
found = plugins[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
g_value_unset(&value);
|
||||
}
|
||||
gst_iterator_free(it);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
20
GstToolkit.h
20
GstToolkit.h
@@ -9,13 +9,23 @@
|
||||
namespace GstToolkit
|
||||
{
|
||||
|
||||
std::string time_to_string(guint64 t);
|
||||
typedef enum {
|
||||
TIME_STRING_FIXED = 0,
|
||||
TIME_STRING_ADJUSTED,
|
||||
TIME_STRING_MINIMAL
|
||||
} time_string_mode;
|
||||
|
||||
std::string gst_version();
|
||||
std::list<std::string> all_plugins();
|
||||
std::list<std::string> all_plugin_features(std::string pluginname);
|
||||
std::string time_to_string(guint64 t, time_string_mode m = TIME_STRING_ADJUSTED);
|
||||
std::string filename_to_uri(std::string filename);
|
||||
|
||||
bool enable_feature (std::string name, bool enable);
|
||||
std::string gst_version();
|
||||
|
||||
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::list<std::string> all_plugin_features(std::string pluginname);
|
||||
bool enable_feature (std::string name, bool enable);
|
||||
|
||||
}
|
||||
|
||||
|
||||
833
ImGuiToolkit.cpp
833
ImGuiToolkit.cpp
File diff suppressed because it is too large
Load Diff
@@ -11,26 +11,32 @@
|
||||
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 IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr);
|
||||
void ShowIconsWindow(bool* p_open);
|
||||
|
||||
// utility buttons
|
||||
bool ButtonIcon (int i, int j);
|
||||
// icon buttons
|
||||
bool ButtonIcon (int i, int j, const char* tooltip = nullptr);
|
||||
bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle);
|
||||
bool ButtonIconMultistate (std::vector<std::pair<int, int> > icons, int* state);
|
||||
void 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);
|
||||
bool ComboIcon (std::vector<std::pair<int, int> > icons, std::vector<std::string> labels, int* state);
|
||||
|
||||
// utility buttons
|
||||
bool ButtonToggle (const char* label, bool* toggle);
|
||||
void ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr);
|
||||
void ButtonOpenUrl (const char* url, const ImVec2& size_arg = ImVec2(0,0));
|
||||
|
||||
void HelpMarker (const char* desc);
|
||||
void ToolTip (const char* desc, const char* shortcut = nullptr);
|
||||
void HelpMarker (const char* desc, const char* icon = ICON_FA_QUESTION_CIRCLE, const char* shortcut = nullptr);
|
||||
void HelpIcon (const char* desc, int i = 19, int j = 5, const char* shortcut = nullptr);
|
||||
|
||||
// utility sliders
|
||||
void Bar (float value, float in, float out, float min, float max, const char* title, bool expand);
|
||||
bool TimelineSlider (const char* label, guint64 *time, guint64 duration, guint64 step);
|
||||
bool TimelineSliderEdit (const char* label, guint64 *time, guint64 duration, guint64 step,
|
||||
std::list<std::pair<guint64, guint64> >& segments);
|
||||
|
||||
bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width);
|
||||
bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size);
|
||||
bool EditPlotLines(const char* label, float *array, int values_count, float values_min, float values_max, const ImVec2 size);
|
||||
bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, bool *released, const ImVec2 size);
|
||||
|
||||
// fonts from ressources 'fonts/'
|
||||
typedef enum {
|
||||
FONT_DEFAULT =0,
|
||||
@@ -54,9 +60,9 @@ namespace ImGuiToolkit
|
||||
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);
|
||||
void ShowStats (bool* p_open, int* p_corner, bool* p_timer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
521
ImGuiVisitor.cpp
521
ImGuiVisitor.cpp
@@ -2,11 +2,16 @@
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Scene.h"
|
||||
@@ -16,8 +21,14 @@
|
||||
#include "MediaPlayer.h"
|
||||
#include "MediaSource.h"
|
||||
#include "SessionSource.h"
|
||||
#include "PatternSource.h"
|
||||
#include "DeviceSource.h"
|
||||
#include "NetworkSource.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "Settings.h"
|
||||
#include "Mixer.h"
|
||||
#include "ActionManager.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
@@ -37,10 +48,8 @@ void ImGuiVisitor::visit(Node &n)
|
||||
|
||||
void ImGuiVisitor::visit(Group &n)
|
||||
{
|
||||
// std::string id = std::to_string(n.id());
|
||||
// if (ImGui::TreeNode(id.c_str(), "Group %d", n.id()))
|
||||
// {
|
||||
// MODEL VIEW
|
||||
// MODEL VIEW
|
||||
ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(1, 16)) {
|
||||
n.translation_.x = 0.f;
|
||||
@@ -48,6 +57,7 @@ void ImGuiVisitor::visit(Group &n)
|
||||
n.rotation_.z = 0.f;
|
||||
n.scale_.x = 1.f;
|
||||
n.scale_.y = 1.f;
|
||||
Action::manager().store("Geometry Reset");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Geometry");
|
||||
@@ -55,6 +65,7 @@ void ImGuiVisitor::visit(Group &n)
|
||||
if (ImGuiToolkit::ButtonIcon(6, 15)) {
|
||||
n.translation_.x = 0.f;
|
||||
n.translation_.y = 0.f;
|
||||
Action::manager().store("Position 0.0, 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
float translation[2] = { n.translation_.x, n.translation_.y};
|
||||
@@ -64,16 +75,15 @@ void ImGuiVisitor::visit(Group &n)
|
||||
n.translation_.x = translation[0];
|
||||
n.translation_.y = translation[1];
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(18, 9))
|
||||
n.rotation_.z = 0.f;
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderAngle("Angle", &(n.rotation_.z), -180.f, 180.f) ;
|
||||
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Position " << std::setprecision(3) << n.translation_.x << ", " << n.translation_.y;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
if (ImGuiToolkit::ButtonIcon(3, 15)) {
|
||||
n.scale_.x = 1.f;
|
||||
n.scale_.y = 1.f;
|
||||
Action::manager().store("Scale 1.0 x 1.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
float scale[2] = { n.scale_.x, n.scale_.y} ;
|
||||
@@ -83,17 +93,30 @@ void ImGuiVisitor::visit(Group &n)
|
||||
n.scale_.x = CLAMP_SCALE(scale[0]);
|
||||
n.scale_.y = CLAMP_SCALE(scale[1]);
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Scale " << std::setprecision(3) << n.scale_.x << " x " << n.scale_.y;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
// // loop over members of a group
|
||||
// for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
|
||||
// (*node)->accept(*this);
|
||||
// }
|
||||
if (ImGuiToolkit::ButtonIcon(18, 9)){
|
||||
n.rotation_.z = 0.f;
|
||||
Action::manager().store("Angle 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderAngle("Angle", &(n.rotation_.z), -180.f, 180.f) ;
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
std::ostringstream oss;
|
||||
oss << "Angle " << std::setprecision(3) << n.rotation_.z * 180.f / M_PI;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
// ImGui::TreePop();
|
||||
// }
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
// spacing
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetTextLineHeight() / 2.f);
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(Switch &n)
|
||||
@@ -113,8 +136,8 @@ void ImGuiVisitor::visit(Scene &n)
|
||||
|
||||
void ImGuiVisitor::visit(Primitive &n)
|
||||
{
|
||||
ImGui::PushID(n.id());
|
||||
ImGui::Text("Primitive %d", n.id());
|
||||
ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
ImGui::Text("Primitive %d");
|
||||
|
||||
n.shader()->accept(*this);
|
||||
|
||||
@@ -141,8 +164,9 @@ void ImGuiVisitor::visit(MediaPlayer &n)
|
||||
|
||||
void ImGuiVisitor::visit(Shader &n)
|
||||
{
|
||||
ImGui::PushID(n.id());
|
||||
ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
|
||||
// Base color
|
||||
// if (ImGuiToolkit::ButtonIcon(10, 2)) {
|
||||
// n.blending = Shader::BLEND_OPACITY;
|
||||
// n.color = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
||||
@@ -152,59 +176,101 @@ void ImGuiVisitor::visit(Shader &n)
|
||||
// ImGui::SameLine(0, 5);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
int mode = n.blending;
|
||||
if (ImGui::Combo("Blending", &mode, "Normal\0Screen\0Inverse\0Addition\0Subtract\0") )
|
||||
if (ImGui::Combo("Blending", &mode, "Normal\0Screen\0Subtract\0Multiply\0Soft light"
|
||||
"\0Hard light\0Soft subtract\0Lighten only\0") ) {
|
||||
n.blending = Shader::BlendMode(mode);
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit(ImageShader &n)
|
||||
{
|
||||
ImGui::PushID(n.id());
|
||||
|
||||
// get index of the mask used in this ImageShader
|
||||
int item_current = n.mask;
|
||||
|
||||
// if (ImGuiToolkit::ButtonIcon(10, 3)) n.mask = 0;
|
||||
// ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
// combo list of masks
|
||||
if ( ImGui::Combo("Mask", &item_current, ImageShader::mask_names, IM_ARRAYSIZE(ImageShader::mask_names) ) )
|
||||
{
|
||||
if (item_current < (int) ImageShader::mask_presets.size())
|
||||
n.mask = item_current;
|
||||
else {
|
||||
// TODO ask for custom mask
|
||||
std::ostringstream oss;
|
||||
oss << "Blending ";
|
||||
switch(n.blending) {
|
||||
case Shader::BLEND_OPACITY:
|
||||
oss<<"Normal";
|
||||
break;
|
||||
case Shader::BLEND_SCREEN:
|
||||
oss<<"Screen";
|
||||
break;
|
||||
case Shader::BLEND_SUBTRACT:
|
||||
oss<<"Subtract";
|
||||
break;
|
||||
case Shader::BLEND_MULTIPLY:
|
||||
oss<<"Multiply";
|
||||
break;
|
||||
case Shader::BLEND_HARD_LIGHT:
|
||||
oss<<"Hard light";
|
||||
break;
|
||||
case Shader::BLEND_SOFT_LIGHT:
|
||||
oss<<"Soft light";
|
||||
break;
|
||||
case Shader::BLEND_SOFT_SUBTRACT:
|
||||
oss<<"Soft subtract";
|
||||
break;
|
||||
case Shader::BLEND_LIGHTEN_ONLY:
|
||||
oss<<"Lighten only";
|
||||
break;
|
||||
case Shader::BLEND_NONE:
|
||||
oss<<"None";
|
||||
break;
|
||||
}
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
//void ImGuiVisitor::visit(ImageShader &n)
|
||||
//{
|
||||
// ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
// // get index of the mask used in this ImageShader
|
||||
// int item_current = n.mask;
|
||||
//// if (ImGuiToolkit::ButtonIcon(10, 3)) n.mask = 0;
|
||||
//// ImGui::SameLine(0, 10);
|
||||
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
// // combo list of masks
|
||||
// if ( ImGui::Combo("Mask", &item_current, ImageShader::mask_names, IM_ARRAYSIZE(ImageShader::mask_names) ) )
|
||||
// {
|
||||
// if (item_current < (int) ImageShader::mask_presets.size())
|
||||
// n.mask = item_current;
|
||||
// else {
|
||||
// // TODO ask for custom mask
|
||||
// }
|
||||
// Action::manager().store("Mask "+ std::string(ImageShader::mask_names[n.mask]));
|
||||
// }
|
||||
// ImGui::PopID();
|
||||
//}
|
||||
|
||||
void ImGuiVisitor::visit(ImageProcessingShader &n)
|
||||
{
|
||||
ImGui::PushID(n.id());
|
||||
ImGui::PushID(std::to_string(n.id()).c_str());
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(6, 2)) {
|
||||
ImageProcessingShader defaultvalues;
|
||||
n = defaultvalues;
|
||||
}
|
||||
ImGuiToolkit::Icon(6, 2);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Filters");
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(6, 4)) n.gamma = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
||||
if (ImGuiToolkit::ButtonIcon(6, 4)) {
|
||||
n.gamma = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
||||
Action::manager().store("Gamma & Color");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::ColorEdit3("GammaColor", glm::value_ptr(n.gamma), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ;
|
||||
ImGui::ColorEdit3("Gamma Color", glm::value_ptr(n.gamma), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ;
|
||||
if (ImGui::IsItemDeactivatedAfterEdit())
|
||||
Action::manager().store("Gamma Color changed");
|
||||
|
||||
ImGui::SameLine(0, 5);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Gamma", &n.gamma.w, 0.5f, 10.f, "%.2f", 2.f);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Gamma " << std::setprecision(2) << n.gamma.w;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
// ImGui::SliderFloat4("Levels", glm::value_ptr(n.levels), 0.0, 1.0);
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(4, 1)) {
|
||||
if (ImGuiToolkit::ButtonIcon(5, 16)) {
|
||||
n.brightness = 0.f;
|
||||
n.contrast = 0.f;
|
||||
Action::manager().store("B & C 0.0 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
@@ -214,111 +280,260 @@ void ImGuiVisitor::visit(ImageProcessingShader &n)
|
||||
n.brightness = bc[0];
|
||||
n.contrast = bc[1];
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "B & C " << std::setprecision(2) << n.brightness << " " << n.contrast;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(2, 1)) n.saturation = 0.f;
|
||||
if (ImGuiToolkit::ButtonIcon(9, 16)) {
|
||||
n.saturation = 0.f;
|
||||
Action::manager().store("Saturation 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Saturation", &n.saturation, -1.0, 1.0);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Saturation " << std::setprecision(2) << n.saturation;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(12, 4)) n.hueshift = 0.f;
|
||||
if (ImGuiToolkit::ButtonIcon(12, 4)) {
|
||||
n.hueshift = 0.f;
|
||||
Action::manager().store("Hue shift 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Hue shift", &n.hueshift, 0.0, 1.0);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Hue shift " << std::setprecision(2) << n.hueshift;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(3, 1)) n.lumakey = 0.f;
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Lumakey", &n.lumakey, 0.0, 1.0);
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(8, 1)) n.threshold = 0.f;
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Threshold", &n.threshold, 0.0, 1.0, n.threshold < 0.001 ? "None" : "%.2f");
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(18, 1)) n.nbColors = 0;
|
||||
if (ImGuiToolkit::ButtonIcon(18, 1)) {
|
||||
n.nbColors = 0;
|
||||
Action::manager().store("Posterize None");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderInt("Posterize", &n.nbColors, 0, 16, n.nbColors == 0 ? "None" : "%d colors");
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Posterize ";
|
||||
if (n.nbColors == 0) oss << "None"; else oss << n.nbColors;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(1, 7)) n.filterid = 0;
|
||||
if (ImGuiToolkit::ButtonIcon(8, 1)) {
|
||||
n.threshold = 0.f;
|
||||
Action::manager().store("Threshold None");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::Combo("Filter", &n.filterid, ImageProcessingShader::filter_names, IM_ARRAYSIZE(ImageProcessingShader::filter_names) );
|
||||
ImGui::SliderFloat("Threshold", &n.threshold, 0.0, 1.0, n.threshold < 0.001 ? "None" : "%.2f");
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Threshold ";
|
||||
if (n.threshold < 0.001) oss << "None"; else oss << std::setprecision(2) << n.threshold;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(7, 1)) n.invert = 0;
|
||||
if (ImGuiToolkit::ButtonIcon(3, 1)) {
|
||||
n.lumakey = 0.f;
|
||||
Action::manager().store("Lumakey 0.0");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::Combo("Invert", &n.invert, "None\0Invert Color\0Invert Luminance\0");
|
||||
ImGui::SliderFloat("Lumakey", &n.lumakey, 0.0, 1.0);
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Lumakey " << std::setprecision(2) << n.lumakey;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(13, 4)) {
|
||||
n.chromakey = glm::vec4(0.f, 0.8f, 0.f, 1.f);
|
||||
n.chromadelta = 0.f;
|
||||
Action::manager().store("Chromakey & Color Reset");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::ColorEdit3("Chroma color", glm::value_ptr(n.chromakey), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel ) ;
|
||||
ImGui::ColorEdit3("Chroma color", glm::value_ptr(n.chromakey), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel ) ;
|
||||
if (ImGui::IsItemDeactivatedAfterEdit())
|
||||
Action::manager().store("Chroma color changed");
|
||||
ImGui::SameLine(0, 5);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
ImGui::SliderFloat("Chromakey", &n.chromadelta, 0.0, 1.0, n.chromadelta < 0.001 ? "None" : "Tolerance %.2f");
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << "Chromakey ";
|
||||
if (n.chromadelta < 0.001) oss << "None"; else oss << std::setprecision(2) << n.chromadelta;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(6, 16)) {
|
||||
n.invert = 0;
|
||||
Action::manager().store("Invert None");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::Combo("Invert", &n.invert, "None\0Invert Color\0Invert Luminance\0"))
|
||||
Action::manager().store("Invert " + std::string(n.invert<1 ? "None": (n.invert>1 ? "Luminance" : "Color")));
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(1, 7)) {
|
||||
n.filterid = 0;
|
||||
Action::manager().store("Filter None");
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::Combo("Filter", &n.filterid, ImageProcessingShader::filter_names, IM_ARRAYSIZE(ImageProcessingShader::filter_names) ) )
|
||||
Action::manager().store("Filter " + std::string(ImageProcessingShader::filter_names[n.filterid]));
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetTextLineHeight() / 2.f);
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
|
||||
void ImGuiVisitor::visit (Source& s)
|
||||
{
|
||||
ImGui::PushID(std::to_string(s.id()).c_str());
|
||||
// blending
|
||||
s.blendingShader()->accept(*this);
|
||||
|
||||
// preview
|
||||
float preview_width = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;
|
||||
ImVec2 imagesize ( preview_width, preview_width / s.frame()->aspectRatio());
|
||||
ImGui::Image((void*)(uintptr_t) s.frame()->texture(), imagesize);
|
||||
|
||||
float preview_height = 4.5f * ImGui::GetFrameHeightWithSpacing();
|
||||
ImVec2 pos = ImGui::GetCursorPos(); // remember where we were...
|
||||
|
||||
float space = ImGui::GetStyle().ItemSpacing.y;
|
||||
float width = preview_width;
|
||||
float height = s.frame()->projectionArea().y * width / ( s.frame()->projectionArea().x * s.frame()->aspectRatio());
|
||||
if (height > preview_height - space) {
|
||||
height = preview_height - space;
|
||||
width = height * s.frame()->aspectRatio() * ( s.frame()->projectionArea().x / s.frame()->projectionArea().y);
|
||||
}
|
||||
// centered image
|
||||
ImGui::SetCursorPos( ImVec2(pos.x + 0.5f * (preview_width-width), pos.y + 0.5f * (preview_height-height-space)) );
|
||||
ImGui::Image((void*)(uintptr_t) s.frame()->texture(), ImVec2(width, height));
|
||||
|
||||
// inform on visibility status
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y ) );
|
||||
if (s.active()) {
|
||||
if (s.blendingShader()->color.a > 0.f)
|
||||
ImGuiToolkit::HelpMarker("Visible", ICON_FA_EYE);
|
||||
else
|
||||
ImGuiToolkit::HelpMarker("Not visible", ICON_FA_EYE_SLASH);
|
||||
}
|
||||
else
|
||||
ImGuiToolkit::HelpMarker("Inactive", ICON_FA_SNOWFLAKE);
|
||||
|
||||
// Inform on workspace
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + ImGui::GetFrameHeightWithSpacing()) );
|
||||
if (s.workspace() == Source::BACKGROUND)
|
||||
ImGuiToolkit::HelpIcon("in Background",10, 16);
|
||||
else if (s.workspace() == Source::FOREGROUND)
|
||||
ImGuiToolkit::HelpIcon("in Foreground",12, 16);
|
||||
else
|
||||
ImGuiToolkit::HelpIcon("in Workspace",11, 16);
|
||||
|
||||
// locking
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 2.f * ImGui::GetFrameHeightWithSpacing()) );
|
||||
const char *tooltip[2] = {"Unlocked", "Locked"};
|
||||
bool l = s.locked();
|
||||
if (ImGuiToolkit::IconToggle(15,6,17,6, &l, tooltip ) ) {
|
||||
s.setLocked(l);
|
||||
if (l) {
|
||||
Mixer::selection().clear();
|
||||
Action::manager().store(s.name() + std::string(": lock."));
|
||||
}
|
||||
else {
|
||||
Mixer::selection().set(&s);
|
||||
Action::manager().store(s.name() + std::string(": unlock."));
|
||||
}
|
||||
}
|
||||
|
||||
// toggle enable/disable image processing
|
||||
bool on = s.imageProcessingEnabled();
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 15, pos.y -ImGui::GetFrameHeight() ) );
|
||||
const char *tooltip[2] = {"GPU Image processing\nCurrently disabled", "GPU Image processing\nCurrently enabled"};
|
||||
if (ImGuiToolkit::IconToggle(12, 11, 14, 1, &on, tooltip))
|
||||
s.setImageProcessingEnabled(on);
|
||||
|
||||
ImGui::SetCursorPos(pos); // ...come back
|
||||
ImGui::SetCursorPos( ImVec2(preview_width + 15, pos.y + 3.5f * ImGui::GetFrameHeightWithSpacing()) );
|
||||
if ( ImGuiToolkit::ButtonToggle(ICON_FA_MAGIC, &on) ){
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << ": " << ( on ? "Enable Filter" : "Disable Filter");
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
s.setImageProcessingEnabled(on);
|
||||
|
||||
// image processing pannel
|
||||
if (s.imageProcessingEnabled())
|
||||
if (s.imageProcessingEnabled()) {
|
||||
|
||||
// menu icon for image processing
|
||||
ImGui::SetCursorPos( ImVec2( preview_width - ImGui::GetTextLineHeight(), pos.y + 4.5f * ImGui::GetFrameHeightWithSpacing())); // ...come back
|
||||
if (ImGuiToolkit::IconButton(5, 8))
|
||||
ImGui::OpenPopup( "MenuImageProcessing" );
|
||||
if (ImGui::BeginPopup( "MenuImageProcessing" ))
|
||||
{
|
||||
if (ImGui::MenuItem("Reset" )){
|
||||
ImageProcessingShader defaultvalues;
|
||||
s.processingShader()->copy(defaultvalues);
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << ": " << "Reset Filter";
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
if (ImGui::MenuItem("Copy" )){
|
||||
std::string clipboard = SessionVisitor::getClipboard(s.processingShader());
|
||||
if (!clipboard.empty())
|
||||
ImGui::SetClipboardText(clipboard.c_str());
|
||||
}
|
||||
const char *clipboard = ImGui::GetClipboardText();
|
||||
const bool can_paste = (clipboard != nullptr && SessionLoader::isClipboard(clipboard));
|
||||
if (ImGui::MenuItem("Paste", NULL, false, can_paste)) {
|
||||
SessionLoader::applyImageProcessing(s, clipboard);
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << ": " << "Change Filter";
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// full panel for image processing
|
||||
ImGui::SetCursorPos( ImVec2( pos.x, pos.y + preview_height)); // ...come back
|
||||
s.processingShader()->accept(*this);
|
||||
}
|
||||
|
||||
// geometry direct control
|
||||
s.groupNode(View::GEOMETRY)->accept(*this);
|
||||
// geometry direct control for DEBUG
|
||||
// s.groupNode(View::GEOMETRY)->accept(*this);
|
||||
// s.groupNode((View::Mode) Settings::application.current_view)->accept(*this);
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (MediaSource& s)
|
||||
{
|
||||
if ( s.mediaplayer()->duration() == GST_CLOCK_TIME_NONE) {
|
||||
ImGuiToolkit::Icon(2,9);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
if ( s.mediaplayer()->isImage() )
|
||||
ImGui::Text("Image File");
|
||||
}
|
||||
else {
|
||||
ImGuiToolkit::Icon(18,13);
|
||||
ImGui::SameLine(0, 10);
|
||||
else
|
||||
ImGui::Text("Video File");
|
||||
if ( ImGui::Button(IMGUI_TITLE_MEDIAPLAYER, ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) {
|
||||
UserInterface::manager().showMediaPlayer( s.mediaplayer());
|
||||
}
|
||||
}
|
||||
|
||||
if ( ImGui::Button(IMGUI_TITLE_MEDIAPLAYER, ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
UserInterface::manager().showMediaPlayer( s.mediaplayer());
|
||||
ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Folder");
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (SessionSource& s)
|
||||
void ImGuiVisitor::visit (SessionFileSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(4,9);
|
||||
if (s.session() == nullptr)
|
||||
return;
|
||||
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Session File");
|
||||
// ImGui::Text("%s", SystemToolkit::base_filename(s.path()).c_str());
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(3, 2)) s.session()->setFading(0.f);
|
||||
float f = s.session()->fading();
|
||||
@@ -326,18 +541,44 @@ void ImGuiVisitor::visit (SessionSource& s)
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::SliderFloat("Fading", &f, 0.0, 1.0, f < 0.001 ? "None" : "%.2f") )
|
||||
s.session()->setFading(f);
|
||||
|
||||
if ( ImGui::Button( ICON_FA_FILE_UPLOAD " Make Current", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()){
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << ": Fading " << std::setprecision(2) << f;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
if ( ImGui::Button( ICON_FA_FILE_UPLOAD " Open Session", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
Mixer::manager().set( s.detach() );
|
||||
if ( ImGui::Button( ICON_FA_FILE_EXPORT " Import", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
Mixer::manager().merge( s.detach() );
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("File");
|
||||
|
||||
ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Folder");
|
||||
|
||||
ImGui::Text("Contains %d sources.", s.session()->numSource());
|
||||
if ( ImGui::Button( ICON_FA_FILE_EXPORT " Import", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
Mixer::manager().import( &s );
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (SessionGroupSource& s)
|
||||
{
|
||||
if (s.session() == nullptr)
|
||||
return;
|
||||
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Flat Sesion group");
|
||||
ImGui::Text("Contains %d sources.", s.session()->numSource());
|
||||
|
||||
if ( ImGui::Button( ICON_FA_UPLOAD " Expand", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ){
|
||||
Mixer::manager().import( &s );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (RenderSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(19,1);
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Rendering Output");
|
||||
if ( ImGui::Button(IMGUI_TITLE_PREVIEW, ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
@@ -346,11 +587,85 @@ void ImGuiVisitor::visit (RenderSource& s)
|
||||
|
||||
void ImGuiVisitor::visit (CloneSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(9,2);
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Clone of %s", s.origin()->name().c_str());
|
||||
std::string label = "Select " + s.origin()->name();
|
||||
if ( ImGui::Button(label.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
ImGui::Text("Clone");
|
||||
if ( ImGui::Button(s.origin()->name().c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
Mixer::manager().setCurrentSource(s.origin());
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Source");
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (PatternSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Pattern");
|
||||
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::BeginCombo("##Patterns", Pattern::pattern_types[s.pattern()->type()].c_str()) )
|
||||
{
|
||||
for (uint p = 0; p < Pattern::pattern_types.size(); ++p){
|
||||
if (ImGui::Selectable( Pattern::pattern_types[p].c_str() )) {
|
||||
s.setPattern(p, s.pattern()->resolution());
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << ": Pattern " << Pattern::pattern_types[p];
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Generator");
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (DeviceSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Device");
|
||||
|
||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||
if (ImGui::BeginCombo("##Hardware", s.device().c_str()))
|
||||
{
|
||||
for (int d = 0; d < Device::manager().numDevices(); ++d){
|
||||
std::string namedev = Device::manager().name(d);
|
||||
if (ImGui::Selectable( namedev.c_str() )) {
|
||||
s.setDevice(namedev);
|
||||
std::ostringstream oss;
|
||||
oss << s.name() << " Device " << namedev;
|
||||
Action::manager().store(oss.str());
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device().c_str()));
|
||||
if ( !confs.empty()) {
|
||||
DeviceConfig best = *confs.rbegin();
|
||||
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
|
||||
ImGui::Text("%s %s %dx%d@%.1ffps", best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiVisitor::visit (NetworkSource& s)
|
||||
{
|
||||
ImGuiToolkit::Icon(s.icon().x, s.icon().y);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::Text("Network stream");
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
|
||||
ImGui::Text("%s", s.connection().c_str());
|
||||
ImGui::PopStyleColor(1);
|
||||
NetworkStream *ns = s.networkStream();
|
||||
ImGui::Text(" - %s (%dx%d)\n - Server address %s", NetworkToolkit::protocol_name[ns->protocol()],
|
||||
ns->resolution().x, ns->resolution().y, ns->serverAddress().c_str());
|
||||
|
||||
if ( ImGui::Button( ICON_FA_REPLY " Reconnect", ImVec2(IMGUI_RIGHT_ALIGN, 0)) )
|
||||
{
|
||||
// TODO : reload ?
|
||||
s.setConnection(s.connection());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,24 +9,27 @@ 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 (MediaSurface& 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;
|
||||
};
|
||||
|
||||
#endif // IMGUIVISITOR_H
|
||||
|
||||
@@ -9,37 +9,16 @@ const char* ImageProcessingShader::filter_names[12] = { "None", "Blur", "Sharpen
|
||||
"Erosion 3x3", "Erosion 5x5", "Erosion 7x7", "Dilation 3x3", "Dilation 5x5", "Dilation 7x7" };
|
||||
|
||||
|
||||
ImageProcessingShader::ImageProcessingShader()
|
||||
ImageProcessingShader::ImageProcessingShader(): Shader()
|
||||
{
|
||||
program_ = &imageProcessingShadingProgram;
|
||||
reset();
|
||||
}
|
||||
|
||||
ImageProcessingShader::ImageProcessingShader(const ImageProcessingShader &S)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void ImageProcessingShader::use()
|
||||
{
|
||||
Shader::use();
|
||||
|
||||
// program_->setUniform("iChannelResolution[0]", iChannelResolution[0].x, iChannelResolution[0].y, iChannelResolution[0].z);
|
||||
|
||||
program_->setUniform("brightness", brightness);
|
||||
program_->setUniform("contrast", contrast);
|
||||
program_->setUniform("saturation", saturation);
|
||||
@@ -58,14 +37,10 @@ void ImageProcessingShader::use()
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ImageProcessingShader::reset()
|
||||
{
|
||||
Shader::reset();
|
||||
|
||||
// // no texture resolution yet
|
||||
// iChannelResolution[0] = glm::vec3(1.f);
|
||||
|
||||
// default values for image processing
|
||||
brightness = 0.f;
|
||||
contrast = 0.f;
|
||||
@@ -83,10 +58,8 @@ void ImageProcessingShader::reset()
|
||||
|
||||
}
|
||||
|
||||
void ImageProcessingShader::operator = (const ImageProcessingShader &S )
|
||||
void ImageProcessingShader::copy(const ImageProcessingShader &S)
|
||||
{
|
||||
Shader::operator =(S);
|
||||
|
||||
brightness = S.brightness;
|
||||
contrast = S.contrast;
|
||||
saturation = S.saturation;
|
||||
@@ -102,6 +75,11 @@ void ImageProcessingShader::operator = (const ImageProcessingShader &S )
|
||||
chromadelta = S.chromadelta;
|
||||
}
|
||||
|
||||
void ImageProcessingShader::operator = (const ImageProcessingShader &S )
|
||||
{
|
||||
copy(S);
|
||||
}
|
||||
|
||||
|
||||
void ImageProcessingShader::accept(Visitor& v)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "Shader.h"
|
||||
#include "ImageShader.h"
|
||||
|
||||
|
||||
class ImageProcessingShader : public Shader
|
||||
@@ -11,16 +11,13 @@ 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);
|
||||
|
||||
// // textures resolution
|
||||
// glm::vec3 iChannelResolution[1];
|
||||
void copy(const ImageProcessingShader &S);
|
||||
|
||||
// color effects
|
||||
float brightness; // [-1 1]
|
||||
|
||||
143
ImageShader.cpp
143
ImageShader.cpp
@@ -2,29 +2,28 @@
|
||||
|
||||
#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(), custom_textureindex(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
|
||||
@@ -35,40 +34,35 @@ 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]);
|
||||
|
||||
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);
|
||||
}
|
||||
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::operator = (const ImageShader &S)
|
||||
{
|
||||
Shader::operator =(S);
|
||||
|
||||
mask = S.mask;
|
||||
custom_textureindex = S.custom_textureindex;
|
||||
mask_texture = S.mask_texture;
|
||||
stipple = S.stipple;
|
||||
}
|
||||
|
||||
@@ -77,3 +71,82 @@ 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
|
||||
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::operator = (const MaskShader &S)
|
||||
{
|
||||
Shader::operator =(S);
|
||||
|
||||
mode = S.mode;
|
||||
shape = S.shape;
|
||||
blur = S.blur;
|
||||
size = S.size;
|
||||
}
|
||||
|
||||
|
||||
void MaskShader::accept(Visitor& v) {
|
||||
Shader::accept(v);
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef IMAGESHADER_H
|
||||
#ifndef IMAGESHADER_H
|
||||
#define IMAGESHADER_H
|
||||
|
||||
#include <string>
|
||||
@@ -14,22 +14,66 @@ class ImageShader : public Shader
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
ImageShader();
|
||||
// virtual ~ImageShader() {}
|
||||
|
||||
void use() override;
|
||||
void reset() override;
|
||||
void accept(Visitor& v) override;
|
||||
|
||||
void operator = (const ImageShader &S);
|
||||
|
||||
uint mask;
|
||||
uint custom_textureindex;
|
||||
float stipple;
|
||||
uint mask_texture;
|
||||
|
||||
static const char* mask_names[11];
|
||||
static std::vector< uint > mask_presets;
|
||||
// uniforms
|
||||
float stipple;
|
||||
};
|
||||
|
||||
class AlphaShader : public ImageShader
|
||||
{
|
||||
|
||||
public:
|
||||
AlphaShader();
|
||||
|
||||
};
|
||||
|
||||
|
||||
class MaskShader : public Shader
|
||||
{
|
||||
|
||||
public:
|
||||
MaskShader();
|
||||
|
||||
void use() override;
|
||||
void reset() override;
|
||||
void accept(Visitor& v) override;
|
||||
void operator = (const MaskShader &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
|
||||
|
||||
396
LayerView.cpp
Normal file
396
LayerView.cpp
Normal file
@@ -0,0 +1,396 @@
|
||||
// Opengl
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "Decorations.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "BoundingBoxVisitor.h"
|
||||
#include "ActionManager.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "LayerView.h"
|
||||
|
||||
LayerView::LayerView() : View(LAYER), aspect_ratio(1.f)
|
||||
{
|
||||
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)->setDepth(depth);
|
||||
}
|
||||
Action::manager().store(std::string("Selection Layer Distribute."));
|
||||
View::need_deep_update_++;
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_RULER_HORIZONTAL " Compress" )){
|
||||
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
|
||||
SourceList::iterator it = dsl.begin();
|
||||
float depth = (*it)->depth();
|
||||
for (++it; it != dsl.end(); ++it) {
|
||||
depth += LAYER_STEP;
|
||||
(*it)->setDepth(depth);
|
||||
}
|
||||
Action::manager().store(std::string("Selection Layer Compress."));
|
||||
View::need_deep_update_++;
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_EXCHANGE_ALT " Reverse order" )){
|
||||
SourceList dsl = depth_sorted(Mixer::selection().getCopy());
|
||||
SourceList::iterator it = dsl.begin();
|
||||
SourceList::reverse_iterator rit = dsl.rbegin();
|
||||
for (; it != dsl.end(); ++it, ++rit) {
|
||||
(*it)->setDepth((*rit)->depth());
|
||||
}
|
||||
Action::manager().store(std::string("Selection Layer Reverse order."));
|
||||
View::need_deep_update_++;
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LayerView::update(float dt)
|
||||
{
|
||||
View::update(dt);
|
||||
|
||||
// a more complete update is requested
|
||||
if (View::need_deep_update_ > 0) {
|
||||
|
||||
// update rendering of render frame
|
||||
FrameBuffer *output = Mixer::manager().session()->frame();
|
||||
if (output){
|
||||
// correct with aspect ratio
|
||||
aspect_ratio = output->aspectRatio();
|
||||
frame_->scale_.x = aspect_ratio;
|
||||
persp_left_->translation_.x = -aspect_ratio;
|
||||
persp_right_->translation_.x = aspect_ratio + 0.06;
|
||||
}
|
||||
|
||||
// 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_left(scene.root()->scale_.x * -2.f, scene.root()->scale_.y * -1.f, 0.f);
|
||||
glm::vec3 border_right(scene.root()->scale_.x * 8.f, scene.root()->scale_.y * 8.f, 0.f);
|
||||
scene.root()->translation_ = glm::clamp(scene.root()->translation_, border_left, border_right);
|
||||
}
|
||||
|
||||
int LayerView::size ()
|
||||
{
|
||||
float z = (scene.root()->scale_.x - LAYER_MIN_SCALE) / (LAYER_MAX_SCALE - LAYER_MIN_SCALE);
|
||||
return (int) ( sqrt(z) * 100.f);
|
||||
}
|
||||
|
||||
|
||||
std::pair<Node *, glm::vec2> LayerView::pick(glm::vec2 P)
|
||||
{
|
||||
// get picking from generic View
|
||||
std::pair<Node *, glm::vec2> pick = View::pick(P);
|
||||
|
||||
// deal with internal interactive objects
|
||||
if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) {
|
||||
|
||||
openContextMenu(MENU_SELECTION);
|
||||
}
|
||||
else {
|
||||
// get if a source was picked
|
||||
Source *s = Mixer::manager().findSource(pick.first);
|
||||
if (s != nullptr) {
|
||||
// pick on the lock icon; unlock source
|
||||
if ( pick.first == s->lock_) {
|
||||
lock(s, false);
|
||||
pick = { s->locker_, pick.second };
|
||||
}
|
||||
// pick on the open lock icon; lock source and cancel pick
|
||||
else if ( pick.first == s->unlock_ ) {
|
||||
lock(s, true);
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick a locked source; cancel pick
|
||||
else if ( s->locked() ) {
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick the symbol: ask to show editor
|
||||
else if ( pick.first == s->symbol_ ) {
|
||||
UserInterface::manager().showSourceEditor(s);
|
||||
}
|
||||
}
|
||||
else
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
|
||||
return pick;
|
||||
}
|
||||
|
||||
|
||||
float LayerView::setDepth(Source *s, float d)
|
||||
{
|
||||
if (!s)
|
||||
return -1.f;
|
||||
|
||||
// move the layer node of the source
|
||||
Group *sourceNode = s->group(mode_);
|
||||
|
||||
float depth = d < 0.f ? sourceNode->translation_.z : d;
|
||||
|
||||
// negative or no depth given; find the front most depth
|
||||
if ( depth < 0.f ) {
|
||||
// default to place visible in front of background
|
||||
depth = LAYER_BACKGROUND + LAYER_STEP;
|
||||
|
||||
// find the front-most souce in the workspace (behind FOREGROUND)
|
||||
for (NodeSet::iterator node = scene.ws()->begin(); node != scene.ws()->end(); ++node) {
|
||||
// place in front of previous sources
|
||||
depth = MAX(depth, (*node)->translation_.z + LAYER_STEP);
|
||||
|
||||
// in case node is already at max depth
|
||||
if ((*node)->translation_.z + DELTA_DEPTH > MAX_DEPTH )
|
||||
(*node)->translation_.z -= DELTA_DEPTH;
|
||||
}
|
||||
}
|
||||
|
||||
// change depth
|
||||
sourceNode->translation_.z = CLAMP( depth, MIN_DEPTH, MAX_DEPTH);
|
||||
|
||||
// request reordering of scene at next update
|
||||
View::need_deep_update_++;
|
||||
|
||||
// request update of source
|
||||
s->touch();
|
||||
|
||||
return sourceNode->translation_.z;
|
||||
}
|
||||
|
||||
View::Cursor LayerView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick)
|
||||
{
|
||||
if (!s)
|
||||
return Cursor();
|
||||
|
||||
// unproject
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(from, scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(to, scene.root()->transform_);
|
||||
|
||||
// compute delta translation
|
||||
glm::vec3 dest_translation = s->stored_status_->translation_ + gl_Position_to - gl_Position_from;
|
||||
|
||||
// discretized translation with ALT
|
||||
if (UserInterface::manager().altModifier())
|
||||
dest_translation.x = ROUND(dest_translation.x, 5.f);
|
||||
|
||||
// apply change
|
||||
float d = setDepth( s, MAX( -dest_translation.x, 0.f) );
|
||||
|
||||
std::ostringstream info;
|
||||
info << "Depth " << std::fixed << std::setprecision(2) << d << " ";
|
||||
// info << (s->locked() ? ICON_FA_LOCK : ICON_FA_LOCK_OPEN); // TODO static not locked
|
||||
|
||||
// store action in history
|
||||
current_action_ = s->name() + ": " + info.str();
|
||||
|
||||
return Cursor(Cursor_ResizeNESW, info.str() );
|
||||
}
|
||||
|
||||
void LayerView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static int accumulator = 0;
|
||||
accumulator++;
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(glm::vec2(movement.x-movement.y, 0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
bool first = true;
|
||||
glm::vec3 delta_translation(0.f);
|
||||
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
|
||||
// individual move with SHIFT
|
||||
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
|
||||
continue;
|
||||
|
||||
Group *sourceNode = (*it)->group(mode_);
|
||||
glm::vec3 dest_translation(0.f);
|
||||
|
||||
if (first) {
|
||||
// dest starts at current
|
||||
dest_translation = sourceNode->translation_;
|
||||
|
||||
// + ALT : discrete displacement
|
||||
if (UserInterface::manager().altModifier()) {
|
||||
if (accumulator > 10) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.21f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
accumulator = 0;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR;
|
||||
}
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
info << "Depth " << std::fixed << std::setprecision(2) << (*it)->depth() << " ";
|
||||
current_action_ = (*it)->name() + ": " + info.str();
|
||||
|
||||
// delta for others to follow
|
||||
delta_translation = dest_translation - sourceNode->translation_;
|
||||
}
|
||||
else {
|
||||
// dest = current + delta from first
|
||||
dest_translation = sourceNode->translation_ + delta_translation;
|
||||
}
|
||||
|
||||
// apply & request update
|
||||
setDepth( *it, MAX( -dest_translation.x, 0.f) );
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void LayerView::updateSelectionOverlay()
|
||||
{
|
||||
View::updateSelectionOverlay();
|
||||
|
||||
if (overlay_selection_->visible_) {
|
||||
// calculate bbox on selection
|
||||
GlmToolkit::AxisAlignedBoundingBox selection_box = BoundingBoxVisitor::AABB(Mixer::selection().getCopy(), this);
|
||||
overlay_selection_->scale_ = selection_box.scale();
|
||||
overlay_selection_->translation_ = selection_box.center();
|
||||
|
||||
// slightly extend the boundary of the selection
|
||||
overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.07f, 0.07f, 1.f) / overlay_selection_->scale_;
|
||||
}
|
||||
}
|
||||
32
LayerView.h
Normal file
32
LayerView.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef LAYERVIEW_H
|
||||
#define LAYERVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class LayerView : public View
|
||||
{
|
||||
public:
|
||||
LayerView();
|
||||
|
||||
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
|
||||
54
Log.cpp
54
Log.cpp
@@ -1,4 +1,7 @@
|
||||
#include "Log.h"
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
using namespace std;
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
@@ -6,16 +9,11 @@
|
||||
#endif
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include "ImGuiToolkit.h"
|
||||
#include "defines.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
#include "DialogToolkit.h"
|
||||
#include "Log.h"
|
||||
|
||||
// multiplatform
|
||||
#include <tinyfiledialogs.h>
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
using namespace std;
|
||||
|
||||
static std::mutex mtx;
|
||||
|
||||
@@ -43,6 +41,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");
|
||||
|
||||
@@ -66,6 +65,9 @@ struct AppLog
|
||||
|
||||
// window
|
||||
ImGui::SameLine(0, 0);
|
||||
static bool numbering = true;
|
||||
ImGuiToolkit::ButtonToggle( ICON_FA_SORT_NUMERIC_DOWN, &numbering );
|
||||
ImGui::SameLine();
|
||||
bool clear = ImGui::Button( ICON_FA_BACKSPACE " Clear");
|
||||
ImGui::SameLine();
|
||||
bool copy = ImGui::Button( ICON_FA_COPY " Copy");
|
||||
@@ -73,7 +75,7 @@ struct AppLog
|
||||
Filter.Draw("Filter", -60.0f);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
||||
|
||||
if (clear)
|
||||
Clear();
|
||||
@@ -118,7 +120,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);
|
||||
}
|
||||
@@ -195,15 +197,15 @@ void Log::Warning(const char* fmt, ...)
|
||||
Log::Info("Warning - %s\n", 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,6 +240,7 @@ void Log::Render(bool showNofitications, bool showWarnings)
|
||||
notifications.clear();
|
||||
}
|
||||
|
||||
|
||||
if (show_warnings) {
|
||||
ImGui::OpenPopup("Warning");
|
||||
if (ImGui::BeginPopupModal("Warning", NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
||||
@@ -255,8 +258,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 +283,6 @@ void Log::Render(bool showNofitications, bool showWarnings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Log::Error(const char* fmt, ...)
|
||||
@@ -279,7 +294,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_
|
||||
|
||||
241
Loopback.cpp
Normal file
241
Loopback.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
#include <thread>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// gstreamer
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "Loopback.h"
|
||||
|
||||
bool Loopback::system_loopback_initialized = false;
|
||||
|
||||
#if defined(LINUX)
|
||||
|
||||
/**
|
||||
*
|
||||
* Linux video 4 linux loopback device
|
||||
*
|
||||
* 1) Linux system has to have the v4l2loopback package
|
||||
* See documentation at https://github.com/umlaeute/v4l2loopback
|
||||
*
|
||||
* $ sudo -A apt install v4l2loopback-dkms
|
||||
*
|
||||
* 2) User (sudo) has to install a v4l2loopback
|
||||
*
|
||||
* $ sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10
|
||||
*
|
||||
* 3) But to do that, the user has to enter sudo passwd
|
||||
*
|
||||
* The command line above should be preceeded by
|
||||
* export SUDO_ASKPASS="/tmp/mysudo.sh"
|
||||
*
|
||||
* where mysudo.sh contains the following:
|
||||
* #!/bin/bash
|
||||
* zenity --password --title=Authentication
|
||||
*
|
||||
* 4) Optionaly, we can set the dynamic properties of the stream
|
||||
*
|
||||
* $ sudo v4l2loopback-ctl set-caps "RGBA:640x480" /dev/video10
|
||||
* $ sudo v4l2loopback-ctl set-fps 30 /dev/video10
|
||||
*
|
||||
* 5) Finally, the gstreamer pipeline can write into v4l2sink
|
||||
*
|
||||
* gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
|
||||
*
|
||||
*
|
||||
* Useful command lines for debug
|
||||
* $ v4l2-ctl --all -d 10
|
||||
* $ gst-launch-1.0 v4l2src device=/dev/video10 ! videoconvert ! autovideosink
|
||||
* $ gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
std::string Loopback::system_loopback_name = "/dev/video10";
|
||||
std::string Loopback::system_loopback_pipeline = "appsrc name=src ! videoconvert ! videorate ! video/x-raw,framerate=30/1 ! v4l2sink sync=false name=sink";
|
||||
|
||||
bool Loopback::initializeSystemLoopback()
|
||||
{
|
||||
if (!Loopback::systemLoopbackInitialized()) {
|
||||
|
||||
// create script for asking sudo password
|
||||
std::string sudoscript = SystemToolkit::full_filename(SystemToolkit::settings_path(), "sudo.sh");
|
||||
FILE *file = fopen(sudoscript.c_str(), "w");
|
||||
if (file) {
|
||||
fprintf(file, "#!/bin/bash\n");
|
||||
fprintf(file, "zenity --password --title=Authentication\n");
|
||||
fclose(file);
|
||||
|
||||
// make script executable
|
||||
int fildes = 0;
|
||||
fildes = open(sudoscript.c_str(), O_RDWR);
|
||||
fchmod(fildes, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH);
|
||||
close(fildes);
|
||||
|
||||
// create command line for installing v4l2loopback
|
||||
std::string cmdline = "export SUDO_ASKPASS=\"" + sudoscript + "\"\n";
|
||||
cmdline += "sudo -A apt install v4l2loopback-dkms 2>&1\n";
|
||||
cmdline += "sudo -A modprobe -r v4l2loopback 2>&1\n";
|
||||
cmdline += "sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\" 2>&1\n";
|
||||
|
||||
// execute v4l2 command line
|
||||
std::string report;
|
||||
FILE *fp = popen(cmdline.c_str(), "r");
|
||||
if (fp != NULL) {
|
||||
|
||||
// get stdout content from command line
|
||||
char linestdout[PATH_MAX];
|
||||
while (fgets(linestdout, PATH_MAX, fp) != NULL)
|
||||
report += linestdout;
|
||||
|
||||
// error reported by pclose?
|
||||
if (pclose(fp) != 0 )
|
||||
Log::Warning("Failed to initialize system v4l2loopback\n%s", report.c_str());
|
||||
// okay, probaly all good...
|
||||
else
|
||||
system_loopback_initialized = true;
|
||||
}
|
||||
else
|
||||
Log::Warning("Failed to initialize system v4l2loopback\nCannot execute command line");
|
||||
|
||||
}
|
||||
else
|
||||
Log::Warning("Failed to initialize system v4l2loopback\nCannot create script", sudoscript.c_str());
|
||||
}
|
||||
|
||||
return system_loopback_initialized;
|
||||
}
|
||||
|
||||
bool Loopback::systemLoopbackInitialized()
|
||||
{
|
||||
// test if already initialized
|
||||
if (!system_loopback_initialized) {
|
||||
// check the existence of loopback device
|
||||
if ( SystemToolkit::file_exists(system_loopback_name) )
|
||||
system_loopback_initialized = true;
|
||||
}
|
||||
|
||||
return system_loopback_initialized;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::string Loopback::system_loopback_name = "undefined";
|
||||
std::string Loopback::system_loopback_pipeline = "";
|
||||
|
||||
|
||||
bool Loopback::initializeSystemLoopback()
|
||||
{
|
||||
system_loopback_initialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Loopback::systemLoopbackInitialized()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Loopback::Loopback() : FrameGrabber()
|
||||
{
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 60);
|
||||
|
||||
}
|
||||
|
||||
void Loopback::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
|
||||
if (!Loopback::systemLoopbackInitialized()){
|
||||
Log::Warning("Loopback system shall be initialized first.");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// create a gstreamer pipeline
|
||||
std::string description = Loopback::system_loopback_pipeline;
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("Loopback Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// setup device sink
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"device", Loopback::system_loopback_name.c_str(),
|
||||
NULL);
|
||||
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
// "do-timestamp", TRUE,
|
||||
NULL);
|
||||
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
callbacks.need_data = FrameGrabber::callback_need_data;
|
||||
callbacks.enough_data = FrameGrabber::callback_enough_data;
|
||||
callbacks.seek_data = NULL; // stream type is not seekable
|
||||
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("Loopback Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// start recording
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("Loopback Could not open %s", Loopback::system_loopback_name.c_str());
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// all good
|
||||
#if defined(LINUX)
|
||||
Log::Notify("Loopback started (v4l2loopback on %s)", Loopback::system_loopback_name.c_str());
|
||||
#else
|
||||
Log::Notify("Loopback started (%s)", Loopback::system_loopback_name.c_str());
|
||||
#endif
|
||||
// start
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void Loopback::terminate()
|
||||
{
|
||||
Log::Notify("Loopback to %s terminated.", Loopback::system_loopback_name.c_str());
|
||||
}
|
||||
31
Loopback.h
Normal file
31
Loopback.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef LOOPBACK_H
|
||||
#define LOOPBACK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
#include "FrameGrabber.h"
|
||||
|
||||
|
||||
class Loopback : public FrameGrabber
|
||||
{
|
||||
static std::string system_loopback_pipeline;
|
||||
static std::string system_loopback_name;
|
||||
static bool system_loopback_initialized;
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
|
||||
public:
|
||||
|
||||
Loopback();
|
||||
|
||||
static bool systemLoopbackInitialized();
|
||||
static bool initializeSystemLoopback();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // LOOPBACK_H
|
||||
966
MediaPlayer.cpp
966
MediaPlayer.cpp
File diff suppressed because it is too large
Load Diff
321
MediaPlayer.h
321
MediaPlayer.h
@@ -2,107 +2,107 @@
|
||||
#define __GST_MEDIA_PLAYER_H_
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <future>
|
||||
|
||||
// GStreamer
|
||||
#include <gst/pbutils/gstdiscoverer.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsink.h>
|
||||
|
||||
#include "Timeline.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 TimeCounter {
|
||||
struct MediaInfo {
|
||||
|
||||
GstClockTime last_time;
|
||||
GstClockTime tic_time;
|
||||
int nbFrames;
|
||||
gdouble fps;
|
||||
public:
|
||||
TimeCounter();
|
||||
GstClockTime dt();
|
||||
void tic();
|
||||
void reset();
|
||||
gdouble frameRate() const;
|
||||
};
|
||||
Timeline timeline;
|
||||
guint width;
|
||||
guint par_width; // width to match pixel aspect ratio
|
||||
guint height;
|
||||
guint bitrate;
|
||||
guint framerate_n;
|
||||
guint framerate_d;
|
||||
std::string codec_name;
|
||||
bool isimage;
|
||||
bool interlaced;
|
||||
bool seekable;
|
||||
bool valid;
|
||||
|
||||
struct MediaSegment
|
||||
{
|
||||
GstClockTime begin;
|
||||
GstClockTime end;
|
||||
|
||||
MediaSegment()
|
||||
{
|
||||
begin = GST_CLOCK_TIME_NONE;
|
||||
end = GST_CLOCK_TIME_NONE;
|
||||
MediaInfo() {
|
||||
width = par_width = 640;
|
||||
height = 480;
|
||||
bitrate = 0;
|
||||
framerate_n = 1;
|
||||
framerate_d = 25;
|
||||
codec_name = "unknown";
|
||||
isimage = false;
|
||||
interlaced = false;
|
||||
seekable = false;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
MediaSegment(GstClockTime b, GstClockTime e)
|
||||
inline MediaInfo& operator = (const MediaInfo& b)
|
||||
{
|
||||
if ( b < e ) {
|
||||
begin = b;
|
||||
end = e;
|
||||
} else {
|
||||
begin = GST_CLOCK_TIME_NONE;
|
||||
end = GST_CLOCK_TIME_NONE;
|
||||
if (this != &b) {
|
||||
this->timeline.setEnd( b.timeline.end() );
|
||||
this->timeline.setStep( b.timeline.step() );
|
||||
this->timeline.setFirst( b.timeline.first() );
|
||||
this->width = b.width;
|
||||
this->par_width = b.par_width;
|
||||
this->height = b.height;
|
||||
this->bitrate = b.bitrate;
|
||||
this->framerate_n = b.framerate_n;
|
||||
this->framerate_d = b.framerate_d;
|
||||
this->codec_name = b.codec_name;
|
||||
this->valid = b.valid;
|
||||
this->isimage = b.isimage;
|
||||
this->interlaced = b.interlaced;
|
||||
this->seekable = b.seekable;
|
||||
}
|
||||
}
|
||||
inline bool is_valid() const
|
||||
{
|
||||
return begin != GST_CLOCK_TIME_NONE && end != GST_CLOCK_TIME_NONE && begin < end;
|
||||
}
|
||||
inline bool operator < (const MediaSegment b) const
|
||||
{
|
||||
return (this->is_valid() && b.is_valid() && this->end < b.begin);
|
||||
}
|
||||
inline bool operator == (const MediaSegment b) const
|
||||
{
|
||||
return (this->begin == b.begin && this->end == b.end);
|
||||
}
|
||||
inline bool operator != (const MediaSegment b) const
|
||||
{
|
||||
return (this->begin != b.begin || this->end != b.end);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct containsTime: public std::unary_function<MediaSegment, bool>
|
||||
{
|
||||
inline bool operator()(const MediaSegment s) const
|
||||
{
|
||||
return ( s.is_valid() && _t > s.begin && _t < s.end );
|
||||
}
|
||||
|
||||
containsTime(GstClockTime t) : _t(t) { }
|
||||
|
||||
private:
|
||||
GstClockTime _t;
|
||||
};
|
||||
|
||||
|
||||
typedef std::set<MediaSegment> MediaSegmentSet;
|
||||
|
||||
class MediaPlayer {
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor of a GStreamer Media
|
||||
* Constructor of a GStreamer Media Player
|
||||
*/
|
||||
MediaPlayer( std::string name = std::string() );
|
||||
MediaPlayer();
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~MediaPlayer();
|
||||
/**
|
||||
* Get unique id
|
||||
*/
|
||||
inline uint64_t id() const { return id_; }
|
||||
/**
|
||||
* Open a media using gstreamer URI
|
||||
* */
|
||||
void open( std::string path);
|
||||
void reopen();
|
||||
/**
|
||||
* Get name of the media
|
||||
* */
|
||||
std::string uri() const;
|
||||
/**
|
||||
* Get name of the file
|
||||
* */
|
||||
std::string filename() const;
|
||||
/**
|
||||
* Get name of Codec of the media
|
||||
* */
|
||||
MediaInfo media() const;
|
||||
/**
|
||||
* True if a media was oppenned
|
||||
* */
|
||||
@@ -116,23 +116,32 @@ public:
|
||||
* */
|
||||
void close();
|
||||
/**
|
||||
* Update status
|
||||
* Must be called in update loop
|
||||
* Update texture with latest frame
|
||||
* Must be called in rendering update loop
|
||||
* */
|
||||
void update();
|
||||
void update_old();
|
||||
|
||||
/**
|
||||
* Enable / Disable
|
||||
* Suspend playing activity
|
||||
* (restores playing state when re-enabled)
|
||||
* */
|
||||
void enable(bool on);
|
||||
|
||||
/**
|
||||
* True if enabled
|
||||
* */
|
||||
bool isEnabled() const;
|
||||
|
||||
/**
|
||||
* True if its an image
|
||||
* */
|
||||
bool isImage() const;
|
||||
/**
|
||||
* Pause / Play
|
||||
* Can play backward if play speed is negative
|
||||
* */
|
||||
void play(bool on);
|
||||
/**
|
||||
* Get Pause / Play
|
||||
* Get Pause / Play status
|
||||
* Performs a full check of the Gstreamer pipeline if testpipeline is true
|
||||
* */
|
||||
bool isPlaying(bool testpipeline = false) const;
|
||||
/**
|
||||
@@ -146,84 +155,106 @@ public:
|
||||
* */
|
||||
void setPlaySpeed(double s);
|
||||
/**
|
||||
* True if the player will loop when at begin or end
|
||||
* Loop Mode: Behavior when reaching an extremity
|
||||
* */
|
||||
typedef enum {
|
||||
LOOP_NONE = 0,
|
||||
LOOP_REWIND = 1,
|
||||
LOOP_BIDIRECTIONAL = 2
|
||||
} LoopMode;
|
||||
/**
|
||||
* Get the current loop mode
|
||||
* */
|
||||
LoopMode loop() const;
|
||||
/**
|
||||
* Set the player to loop
|
||||
* Set the loop mode
|
||||
* */
|
||||
void setLoop(LoopMode mode);
|
||||
/**
|
||||
* Restart from zero
|
||||
* Seek to next frame when paused
|
||||
* (aka next frame)
|
||||
* Can go backward if play speed is negative
|
||||
* */
|
||||
void step();
|
||||
/**
|
||||
* Jump fast when playing
|
||||
* (aka fast-forward)
|
||||
* Can go backward if play speed is negative
|
||||
* */
|
||||
void jump();
|
||||
/**
|
||||
* Seek to zero
|
||||
* */
|
||||
void rewind();
|
||||
/**
|
||||
* Seek to next frame when paused
|
||||
* Can go backward if play speed is negative
|
||||
* go to a valid position in media timeline
|
||||
* pos in nanoseconds.
|
||||
* return true if seek is performed
|
||||
* */
|
||||
void seekNextFrame();
|
||||
bool go_to(GstClockTime pos);
|
||||
/**
|
||||
* Seek to any position in media
|
||||
* pos in nanoseconds.
|
||||
* */
|
||||
void seekTo(GstClockTime pos);
|
||||
void seek(GstClockTime pos);
|
||||
/**
|
||||
* Jump by 10% of the duration
|
||||
* */
|
||||
void fastForward();
|
||||
* @brief timeline contains all info on timing:
|
||||
* - start position : timeline.start()
|
||||
* - end position : timeline.end()
|
||||
* - duration : timeline.duration()
|
||||
* - frame duration : timeline.step()
|
||||
*/
|
||||
Timeline *timeline();
|
||||
void setTimeline(const Timeline &tl);
|
||||
|
||||
float currentTimelineFading();
|
||||
/**
|
||||
* Get position time
|
||||
* */
|
||||
GstClockTime position();
|
||||
/**
|
||||
* Get total duration time
|
||||
* */
|
||||
GstClockTime duration();
|
||||
/**
|
||||
* Get duration of one frame
|
||||
* */
|
||||
GstClockTime frameDuration();
|
||||
/**
|
||||
* Get framerate of the media
|
||||
* */
|
||||
double frameRate() const;
|
||||
/**
|
||||
* Get name of Codec of the media
|
||||
* */
|
||||
std::string codec() const;
|
||||
/**
|
||||
* Get rendering update framerate
|
||||
* measured during play
|
||||
* */
|
||||
double updateFrameRate() const;
|
||||
/**
|
||||
* Get frame width
|
||||
* */
|
||||
guint width() const;
|
||||
/**
|
||||
* Get frame height
|
||||
* */
|
||||
guint height() const;
|
||||
/**
|
||||
* Get frames displayt aspect ratio
|
||||
* NB: can be different than width() / height()
|
||||
* */
|
||||
float aspectRatio() const;
|
||||
/**
|
||||
* Get the OpenGL texture
|
||||
* Must be called in OpenGL context
|
||||
* */
|
||||
guint texture() const;
|
||||
/**
|
||||
* Get Image properties
|
||||
* Get the name of the hardware decoder used
|
||||
* Empty string if none (i.e. software decoding)
|
||||
* */
|
||||
guint width() const;
|
||||
guint height() const;
|
||||
float aspectRatio() const;
|
||||
|
||||
std::string hardwareDecoderName();
|
||||
/**
|
||||
* Get name of the media
|
||||
* Forces open using software decoding
|
||||
* (i.e. without hadrware decoding)
|
||||
* */
|
||||
std::string uri() const;
|
||||
std::string filename() const;
|
||||
|
||||
void setSoftwareDecodingForced(bool on);
|
||||
bool softwareDecodingForced();
|
||||
/**
|
||||
* Accept visitors
|
||||
* Used for saving session file
|
||||
* */
|
||||
void accept(Visitor& v);
|
||||
|
||||
/**
|
||||
* @brief registered
|
||||
* @return list of media players currently registered
|
||||
@@ -234,54 +265,67 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
bool addPlaySegment(GstClockTime begin, GstClockTime end);
|
||||
bool addPlaySegment(MediaSegment s);
|
||||
bool removePlaySegmentAt(GstClockTime t);
|
||||
bool removeAllPlaySegmentOverlap(MediaSegment s);
|
||||
std::list< std::pair<guint64, guint64> > getPlaySegments() const;
|
||||
|
||||
std::string id_;
|
||||
// video player description
|
||||
uint64_t id_;
|
||||
std::string filename_;
|
||||
std::string uri_;
|
||||
guint textureindex_;
|
||||
guint width_;
|
||||
guint height_;
|
||||
guint par_width_; // width to match pixel aspect ratio
|
||||
guint bitrate_;
|
||||
|
||||
// general properties of media
|
||||
MediaInfo media_;
|
||||
std::future<MediaInfo> discoverer_;
|
||||
|
||||
// GST & Play status
|
||||
GstClockTime position_;
|
||||
GstClockTime start_position_;
|
||||
GstClockTime duration_;
|
||||
GstClockTime frame_duration_;
|
||||
gdouble rate_;
|
||||
LoopMode loop_;
|
||||
TimeCounter timecount_;
|
||||
gdouble framerate_;
|
||||
GstState desired_state_;
|
||||
GstElement *pipeline_;
|
||||
GstDiscoverer *discoverer_;
|
||||
std::stringstream discoverer_message_;
|
||||
std::string codec_name_;
|
||||
GstVideoInfo v_frame_video_info_;
|
||||
std::atomic<bool> ready_;
|
||||
std::atomic<bool> failed_;
|
||||
bool seeking_;
|
||||
bool enabled_;
|
||||
bool force_software_decoding_;
|
||||
std::string hardware_decoder_;
|
||||
|
||||
// fps counter
|
||||
struct TimeCounter {
|
||||
|
||||
GstClockTime last_time;
|
||||
GstClockTime tic_time;
|
||||
int nbFrames;
|
||||
gdouble fps;
|
||||
public:
|
||||
TimeCounter();
|
||||
GstClockTime dt();
|
||||
void tic();
|
||||
void reset();
|
||||
gdouble frameRate() const;
|
||||
};
|
||||
TimeCounter timecount_;
|
||||
|
||||
// frame stack
|
||||
typedef enum {
|
||||
EMPTY = 0,
|
||||
SAMPLE = 1,
|
||||
PREROLL = 2,
|
||||
EOS = 4
|
||||
SAMPLE = 0,
|
||||
PREROLL = 1,
|
||||
EOS = 2,
|
||||
INVALID = 3
|
||||
} FrameStatus;
|
||||
|
||||
struct Frame {
|
||||
GstVideoFrame vframe;
|
||||
FrameStatus status;
|
||||
bool full;
|
||||
GstClockTime position;
|
||||
std::mutex access;
|
||||
|
||||
Frame() {
|
||||
vframe.buffer = nullptr;
|
||||
status = EMPTY;
|
||||
full = false;
|
||||
status = INVALID;
|
||||
position = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
void unmap();
|
||||
};
|
||||
Frame frame_[N_VFRAME];
|
||||
guint write_index_;
|
||||
@@ -293,16 +337,7 @@ private:
|
||||
guint pbo_index_, pbo_next_index_;
|
||||
guint pbo_size_;
|
||||
|
||||
MediaSegmentSet segments_;
|
||||
MediaSegmentSet::iterator current_segment_;
|
||||
|
||||
bool ready_;
|
||||
bool failed_;
|
||||
bool seekable_;
|
||||
bool isimage_;
|
||||
bool interlaced_;
|
||||
bool enabled_;
|
||||
|
||||
// gst pipeline control
|
||||
void execute_open();
|
||||
void execute_loop_command();
|
||||
void execute_seek_command(GstClockTime target = GST_CLOCK_TIME_NONE);
|
||||
@@ -311,13 +346,13 @@ private:
|
||||
void init_texture(guint index);
|
||||
void fill_texture(guint index);
|
||||
bool fill_frame(GstBuffer *buf, FrameStatus status);
|
||||
|
||||
// gst callbacks
|
||||
static void callback_end_of_stream (GstAppSink *, gpointer);
|
||||
static GstFlowReturn callback_new_preroll (GstAppSink *, gpointer );
|
||||
static GstFlowReturn callback_new_sample (GstAppSink *, gpointer);
|
||||
|
||||
static void callback_discoverer_process (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, MediaPlayer *m);
|
||||
static void callback_discoverer_finished(GstDiscoverer *discoverer, MediaPlayer *m);
|
||||
|
||||
// global list of registered media player
|
||||
static std::list<MediaPlayer*> registered_;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Primitives.h"
|
||||
#include "Decorations.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
@@ -15,29 +15,21 @@ MediaSource::MediaSource() : Source(), path_("")
|
||||
{
|
||||
// create media player
|
||||
mediaplayer_ = new MediaPlayer;
|
||||
|
||||
// create media surface:
|
||||
// - textured with original texture from media player
|
||||
// - crop & repeat UV can be managed here
|
||||
// - additional custom shader can be associated
|
||||
mediasurface_ = new Surface(renderingshader_);
|
||||
|
||||
}
|
||||
|
||||
MediaSource::~MediaSource()
|
||||
{
|
||||
// delete media surface & player
|
||||
delete mediasurface_;
|
||||
// delete media player
|
||||
delete mediaplayer_;
|
||||
}
|
||||
|
||||
void MediaSource::setPath(const std::string &p)
|
||||
{
|
||||
Log::Notify("Creating Source with media '%s'", p.c_str());
|
||||
|
||||
path_ = p;
|
||||
mediaplayer_->open(path_);
|
||||
mediaplayer_->play(true);
|
||||
|
||||
Log::Notify("Opening %s", p.c_str());
|
||||
}
|
||||
|
||||
std::string MediaSource::path() const
|
||||
@@ -50,6 +42,14 @@ MediaPlayer *MediaSource::mediaplayer() const
|
||||
return mediaplayer_;
|
||||
}
|
||||
|
||||
glm::ivec2 MediaSource::icon() const
|
||||
{
|
||||
if (mediaplayer_->isImage())
|
||||
return glm::ivec2(4, 9);
|
||||
else
|
||||
return glm::ivec2(18, 13);
|
||||
}
|
||||
|
||||
bool MediaSource::failed() const
|
||||
{
|
||||
return mediaplayer_->failed();
|
||||
@@ -60,11 +60,6 @@ uint MediaSource::texture() const
|
||||
return mediaplayer_->texture();
|
||||
}
|
||||
|
||||
void MediaSource::replaceRenderingShader()
|
||||
{
|
||||
mediasurface_->replaceShader(renderingshader_);
|
||||
}
|
||||
|
||||
void MediaSource::init()
|
||||
{
|
||||
if ( mediaplayer_->isOpen() ) {
|
||||
@@ -76,32 +71,31 @@ void MediaSource::init()
|
||||
if (mediaplayer_->texture() != Resource::getTextureBlack()) {
|
||||
|
||||
// get the texture index from media player, apply it to the media surface
|
||||
mediasurface_->setTextureIndex( mediaplayer_->texture() );
|
||||
texturesurface_->setTextureIndex( mediaplayer_->texture() );
|
||||
|
||||
// create Frame buffer matching size of media player
|
||||
float height = float(mediaplayer()->width()) / mediaplayer()->aspectRatio();
|
||||
FrameBuffer *renderbuffer = new FrameBuffer(mediaplayer()->width(), (uint)height, true);
|
||||
float height = float(mediaplayer_->width()) / mediaplayer_->aspectRatio();
|
||||
FrameBuffer *renderbuffer = new FrameBuffer(mediaplayer_->width(), (uint)height, true);
|
||||
|
||||
// icon in mixing view
|
||||
if (mediaplayer_->isImage())
|
||||
symbol_ = new Symbol(Symbol::IMAGE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
else
|
||||
symbol_ = new Symbol(Symbol::VIDEO, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// icon in mixing view
|
||||
if (mediaplayer_->duration() == GST_CLOCK_TIME_NONE) {
|
||||
overlays_[View::MIXING]->attach( new Symbol(Symbol::IMAGE, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
overlays_[View::LAYER]->attach( new Symbol(Symbol::IMAGE, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
}
|
||||
else {
|
||||
overlays_[View::MIXING]->attach( new Symbol(Symbol::VIDEO, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
overlays_[View::LAYER]->attach( new Symbol(Symbol::VIDEO, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
}
|
||||
// force update of activation mode
|
||||
active_ = true;
|
||||
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
Log::Info("Source Media linked to Media %s.", mediaplayer()->uri().c_str());
|
||||
|
||||
// force update of activation mode
|
||||
active_ = true;
|
||||
touch();
|
||||
Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +109,7 @@ void MediaSource::setActive (bool on)
|
||||
|
||||
// change status of media player (only if status changed)
|
||||
if ( active_ != was_active ) {
|
||||
mediaplayer()->enable(active_);
|
||||
mediaplayer_->enable(active_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +118,7 @@ void MediaSource::update(float dt)
|
||||
Source::update(dt);
|
||||
|
||||
// update video
|
||||
if (active_)
|
||||
mediaplayer_->update();
|
||||
mediaplayer_->update();
|
||||
}
|
||||
|
||||
void MediaSource::render()
|
||||
@@ -133,10 +126,17 @@ void MediaSource::render()
|
||||
if (!initialized_)
|
||||
init();
|
||||
else {
|
||||
// blendingshader_->color.r = mediaplayer_->currentTimelineFading();
|
||||
// blendingshader_->color.g = mediaplayer_->currentTimelineFading();
|
||||
// blendingshader_->color.b = mediaplayer_->currentTimelineFading();
|
||||
|
||||
// render the media player into frame buffer
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
|
||||
renderbuffer_->begin();
|
||||
mediasurface_->draw(glm::identity<glm::mat4>(), projection);
|
||||
// texturesurface_->shader()->color.a = mediaplayer_->currentTimelineFading();
|
||||
texturesurface_->shader()->color.r = mediaplayer_->currentTimelineFading();
|
||||
texturesurface_->shader()->color.g = mediaplayer_->currentTimelineFading();
|
||||
texturesurface_->shader()->color.b = mediaplayer_->currentTimelineFading();
|
||||
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
|
||||
renderbuffer_->end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "Source.h"
|
||||
|
||||
class MediaPlayer;
|
||||
|
||||
class MediaSource : public Source
|
||||
{
|
||||
public:
|
||||
@@ -22,12 +24,12 @@ public:
|
||||
std::string path() const;
|
||||
MediaPlayer *mediaplayer() const;
|
||||
|
||||
glm::ivec2 icon() const override;
|
||||
|
||||
protected:
|
||||
|
||||
void init() override;
|
||||
void replaceRenderingShader() override;
|
||||
|
||||
Surface *mediasurface_;
|
||||
std::string path_;
|
||||
MediaPlayer *mediaplayer_;
|
||||
};
|
||||
|
||||
9
Mesh.cpp
9
Mesh.cpp
@@ -31,11 +31,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 +42,9 @@ template <typename T>
|
||||
T parseValue(std::istream& istream) {
|
||||
|
||||
T v;
|
||||
char space = ' ';
|
||||
istream >> v;
|
||||
if (!istream.eof()) {
|
||||
char space = ' ';
|
||||
istream >> space >> std::ws;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
57
Mixer.h
57
Mixer.h
@@ -1,10 +1,16 @@
|
||||
#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"
|
||||
|
||||
class SessionSource;
|
||||
|
||||
class Mixer
|
||||
{
|
||||
// Private Constructor
|
||||
@@ -31,39 +37,59 @@ public:
|
||||
// update session and all views
|
||||
void update();
|
||||
inline float dt() const { return dt_;}
|
||||
inline int fps() const { return int(roundf(1000.f/dt__));}
|
||||
|
||||
// draw session and current view
|
||||
void draw();
|
||||
|
||||
// creation of sources
|
||||
Source * createSourceFile (std::string path);
|
||||
Source * createSourceClone (std::string namesource = "");
|
||||
Source * createSourceFile (const std::string &path);
|
||||
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);
|
||||
void deleteSource (Source *s, bool withundo=true);
|
||||
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 ();
|
||||
void setCurrentSource (Source *s);
|
||||
void setCurrentSource (std::string namesource);
|
||||
void setCurrentSource (Node *node);
|
||||
void setCurrentSource (int index);
|
||||
void setCurrentSource (uint64_t id);
|
||||
void setCurrentNext ();
|
||||
void setCurrentPrevious ();
|
||||
void unsetCurrentSource ();
|
||||
|
||||
void setCurrentIndex (int index);
|
||||
void moveIndex (int current_index, int target_index);
|
||||
int indexCurrentSource ();
|
||||
Source * currentSource ();
|
||||
|
||||
// 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);
|
||||
|
||||
// management of view
|
||||
View *view (View::Mode m = View::INVALID);
|
||||
void setView (View::Mode m);
|
||||
|
||||
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 ();
|
||||
@@ -71,21 +97,31 @@ public:
|
||||
void saveas (const std::string& filename);
|
||||
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);
|
||||
|
||||
// operations depending on transition mode
|
||||
void close ();
|
||||
void open (const std::string& filename);
|
||||
|
||||
// create sources if clipboard contains well-formed xml text
|
||||
void paste (const std::string& clipboard);
|
||||
|
||||
protected:
|
||||
|
||||
Session *session_;
|
||||
Session *back_session_;
|
||||
std::list<Session *> garbage_;
|
||||
bool sessionSwapRequested_;
|
||||
void swap();
|
||||
|
||||
SourceList candidate_sources_;
|
||||
void insertSource(Source *s, View::Mode m = View::INVALID);
|
||||
SourceList stash_;
|
||||
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_;
|
||||
@@ -95,10 +131,11 @@ protected:
|
||||
MixingView mixing_;
|
||||
GeometryView geometry_;
|
||||
LayerView layer_;
|
||||
TextureView appearance_;
|
||||
TransitionView transition_;
|
||||
|
||||
guint64 update_time_;
|
||||
float dt_;
|
||||
float dt__;
|
||||
};
|
||||
|
||||
#endif // MIXER_H
|
||||
|
||||
372
MixingGroup.cpp
Normal file
372
MixingGroup.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
#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 "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_ = GlmToolkit::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_);
|
||||
}
|
||||
}
|
||||
83
MixingGroup.h
Normal file
83
MixingGroup.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#ifndef MIXINGGROUP_H
|
||||
#define MIXINGGROUP_H
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "View.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
class LineLoop;
|
||||
class Symbol;
|
||||
|
||||
class MixingGroup
|
||||
{
|
||||
|
||||
public:
|
||||
MixingGroup (SourceList sources);
|
||||
~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
|
||||
684
MixingView.cpp
Normal file
684
MixingView.cpp
Normal file
@@ -0,0 +1,684 @@
|
||||
// Opengl
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "Mixer.h"
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "Decorations.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "BoundingBoxVisitor.h"
|
||||
#include "ActionManager.h"
|
||||
#include "MixingGroup.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "MixingView.h"
|
||||
|
||||
// internal utility
|
||||
float sin_quad_texture(float x, float y);
|
||||
uint textureMixingQuadratic();
|
||||
|
||||
|
||||
|
||||
MixingView::MixingView() : View(MIXING), limbo_scale_(MIXING_LIMBO_SCALE)
|
||||
{
|
||||
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
|
||||
Mesh *tmp = new Mesh("mesh/disk.ply");
|
||||
tmp->scale_ = glm::vec3(limbo_scale_, limbo_scale_, 1.f);
|
||||
tmp->shader()->color = glm::vec4( COLOR_LIMBO_CIRCLE, 0.6f );
|
||||
scene.bg()->attach(tmp);
|
||||
|
||||
mixingCircle_ = new Mesh("mesh/disk.ply");
|
||||
mixingCircle_->shader()->color = glm::vec4( 1.f, 1.f, 1.f, 1.f );
|
||||
scene.bg()->attach(mixingCircle_);
|
||||
|
||||
circle_ = new Mesh("mesh/circle.ply");
|
||||
circle_->shader()->color = glm::vec4( COLOR_CIRCLE, 1.0f );
|
||||
scene.bg()->attach(circle_);
|
||||
|
||||
// Mixing scene foreground
|
||||
|
||||
// button frame
|
||||
tmp = new Mesh("mesh/disk.ply");
|
||||
tmp->scale_ = glm::vec3(0.033f, 0.033f, 1.f);
|
||||
tmp->translation_ = glm::vec3(0.f, 1.f, 0.f);
|
||||
tmp->shader()->color = glm::vec4( COLOR_CIRCLE, 0.9f );
|
||||
scene.fg()->attach(tmp);
|
||||
// interactive button
|
||||
button_white_ = new Disk();
|
||||
button_white_->scale_ = glm::vec3(0.026f, 0.026f, 1.f);
|
||||
button_white_->translation_ = glm::vec3(0.f, 1.f, 0.f);
|
||||
button_white_->color = glm::vec4( 0.85f, 0.85f, 0.85f, 1.0f );
|
||||
scene.fg()->attach(button_white_);
|
||||
// button frame
|
||||
tmp = new Mesh("mesh/disk.ply");
|
||||
tmp->scale_ = glm::vec3(0.033f, 0.033f, 1.f);
|
||||
tmp->translation_ = glm::vec3(0.f, -1.f, 0.f);
|
||||
tmp->shader()->color = glm::vec4( COLOR_CIRCLE, 0.9f );
|
||||
scene.fg()->attach(tmp);
|
||||
// interactive button
|
||||
button_black_ = new Disk();
|
||||
button_black_->scale_ = glm::vec3(0.026f, 0.026f, 1.f);
|
||||
button_black_->translation_ = glm::vec3(0.f, -1.f, 0.f);
|
||||
button_black_->color = glm::vec4( 0.1f, 0.1f, 0.1f, 1.0f );
|
||||
scene.fg()->attach(button_black_);
|
||||
// moving slider
|
||||
slider_root_ = new Group;
|
||||
scene.fg()->attach(slider_root_);
|
||||
// interactive slider
|
||||
slider_ = new Disk();
|
||||
slider_->scale_ = glm::vec3(0.08f, 0.08f, 1.f);
|
||||
slider_->translation_ = glm::vec3(0.0f, 1.0f, 0.f);
|
||||
slider_->color = glm::vec4( COLOR_CIRCLE, 0.9f );
|
||||
slider_root_->attach(slider_);
|
||||
// dark mask in front
|
||||
tmp = new Mesh("mesh/disk.ply");
|
||||
tmp->scale_ = glm::vec3(0.075f, 0.075f, 1.f);
|
||||
tmp->translation_ = glm::vec3(0.0f, 1.0f, 0.f);
|
||||
tmp->shader()->color = glm::vec4( COLOR_SLIDER_CIRCLE, 1.0f );
|
||||
slider_root_->attach(tmp);
|
||||
|
||||
|
||||
stashCircle_ = new Disk();
|
||||
stashCircle_->scale_ = glm::vec3(0.5f, 0.5f, 1.f);
|
||||
stashCircle_->translation_ = glm::vec3(2.f, -1.0f, 0.f);
|
||||
stashCircle_->color = glm::vec4( COLOR_STASH_CIRCLE, 0.6f );
|
||||
// scene.bg()->attach(stashCircle_);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MixingView::draw()
|
||||
{
|
||||
// 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 " Distribute" )){
|
||||
SourceList list;
|
||||
glm::vec2 center = glm::vec2(0.f, 0.f);
|
||||
for (SourceList::iterator it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
||||
list.push_back(*it);
|
||||
// compute barycenter (1)
|
||||
center += glm::vec2((*it)->group(View::MIXING)->translation_);
|
||||
}
|
||||
// compute barycenter (2)
|
||||
center /= list.size();
|
||||
// sort the vector of sources in clockwise order around the center pos_
|
||||
list = mixing_sorted( list, center);
|
||||
// average distance
|
||||
float d = 0.f;
|
||||
for (SourceList::iterator it = list.begin(); it != list.end(); ++it) {
|
||||
d += glm::distance(glm::vec2((*it)->group(View::MIXING)->translation_), center);
|
||||
}
|
||||
d /= list.size();
|
||||
// distribute with equal angle
|
||||
float angle = 0.f;
|
||||
for (SourceList::iterator it = list.begin(); it != list.end(); ++it) {
|
||||
glm::vec2 P = center + glm::rotate(glm::vec2(0.f, d), angle);
|
||||
(*it)->group(View::MIXING)->translation_.x = P.x;
|
||||
(*it)->group(View::MIXING)->translation_.y = P.y;
|
||||
(*it)->touch();
|
||||
angle -= glm::two_pi<float>() / float(list.size());
|
||||
}
|
||||
Action::manager().store(std::string("Selection Mixing Distribute."));
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_CLOUD_SUN " Expand & hide" )){
|
||||
SourceList::iterator it = Mixer::selection().begin();
|
||||
for (; it != Mixer::selection().end(); ++it) {
|
||||
(*it)->setAlpha(0.f);
|
||||
}
|
||||
Action::manager().store(std::string("Selection Mixing Expand & hide."));
|
||||
}
|
||||
if (ImGui::Selectable( ICON_FA_SUN " Compress & show" )){
|
||||
SourceList::iterator it = Mixer::selection().begin();
|
||||
for (; it != Mixer::selection().end(); ++it) {
|
||||
(*it)->setAlpha(0.99f);
|
||||
}
|
||||
Action::manager().store(std::string("Selection Mixing Compress & show."));
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void MixingView::resize ( int scale )
|
||||
{
|
||||
float z = CLAMP(0.01f * (float) scale, 0.f, 1.f);
|
||||
z *= z;
|
||||
z *= MIXING_MAX_SCALE - MIXING_MIN_SCALE;
|
||||
z += MIXING_MIN_SCALE;
|
||||
scene.root()->scale_.x = z;
|
||||
scene.root()->scale_.y = z;
|
||||
|
||||
// Clamp translation to acceptable area
|
||||
glm::vec3 border(scene.root()->scale_.x * 1.f, scene.root()->scale_.y * 1.f, 0.f);
|
||||
scene.root()->translation_ = glm::clamp(scene.root()->translation_, -border, border);
|
||||
}
|
||||
|
||||
int MixingView::size ()
|
||||
{
|
||||
float z = (scene.root()->scale_.x - MIXING_MIN_SCALE) / (MIXING_MAX_SCALE - MIXING_MIN_SCALE);
|
||||
return (int) ( sqrt(z) * 100.f);
|
||||
}
|
||||
|
||||
void MixingView::centerSource(Source *s)
|
||||
{
|
||||
// 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 slider to match the actual fading of the session
|
||||
//
|
||||
float f = Mixer::manager().session()->empty() ? 0.f : Mixer::manager().session()->fading();
|
||||
|
||||
// reverse calculate angle from fading & move slider
|
||||
slider_root_->rotation_.z = SIGN(slider_root_->rotation_.z) * asin(f) * 2.f;
|
||||
|
||||
// visual feedback on mixing circle
|
||||
f = 1.f - f;
|
||||
mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f);
|
||||
|
||||
// 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 session fading to match the slider angle (during animation)
|
||||
//
|
||||
|
||||
// calculate fading from angle
|
||||
float f = sin( ABS(slider_root_->rotation_.z) * 0.5f);
|
||||
|
||||
// apply fading
|
||||
if ( ABS_DIFF( f, Mixer::manager().session()->fading()) > EPSILON )
|
||||
{
|
||||
// apply fading to session
|
||||
Mixer::manager().session()->setFading(f);
|
||||
|
||||
// visual feedback on mixing circle
|
||||
f = 1.f - f;
|
||||
mixingCircle_->shader()->color = glm::vec4(f, f, f, 1.f);
|
||||
}
|
||||
|
||||
// update the selection overlay
|
||||
updateSelectionOverlay();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::pair<Node *, glm::vec2> MixingView::pick(glm::vec2 P)
|
||||
{
|
||||
// get picking from generic View
|
||||
std::pair<Node *, glm::vec2> pick = View::pick(P);
|
||||
|
||||
// deal with internal interactive objects
|
||||
if ( pick.first == button_white_ || pick.first == button_black_ ) {
|
||||
|
||||
RotateToCallback *anim = nullptr;
|
||||
if (pick.first == button_white_)
|
||||
anim = new RotateToCallback(0.f, 500.f);
|
||||
else
|
||||
anim = new RotateToCallback(SIGN(slider_root_->rotation_.z) * M_PI, 500.f);
|
||||
|
||||
// animate clic
|
||||
pick.first->update_callbacks_.push_back(new BounceScaleCallback(0.3f));
|
||||
|
||||
// reset & start animation
|
||||
slider_root_->update_callbacks_.clear();
|
||||
slider_root_->update_callbacks_.push_back(anim);
|
||||
|
||||
}
|
||||
else if ( overlay_selection_icon_ != nullptr && pick.first == overlay_selection_icon_ ) {
|
||||
|
||||
openContextMenu(MENU_SELECTION);
|
||||
}
|
||||
else {
|
||||
// get if a source was picked
|
||||
Source *s = Mixer::manager().findSource(pick.first);
|
||||
if (s != nullptr) {
|
||||
// pick on the lock icon; unlock source
|
||||
if ( pick.first == s->lock_) {
|
||||
lock(s, false);
|
||||
pick = { s->locker_, pick.second };
|
||||
}
|
||||
// pick on the open lock icon; lock source and cancel pick
|
||||
else if ( pick.first == s->unlock_ ) {
|
||||
lock(s, true);
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick a locked source ; cancel pick
|
||||
else if ( s->locked() ) {
|
||||
pick = { nullptr, glm::vec2(0.f) };
|
||||
}
|
||||
// pick the symbol: ask to show editor
|
||||
else if ( pick.first == s->symbol_ ) {
|
||||
UserInterface::manager().showSourceEditor(s);
|
||||
}
|
||||
// pick on the mixing group rotation icon
|
||||
else if ( pick.first == s->rotation_mixingroup_ ) {
|
||||
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;
|
||||
|
||||
// cursor feedback
|
||||
slider_->color = glm::vec4( COLOR_CIRCLE_OVER, 0.9f );
|
||||
std::ostringstream info;
|
||||
info << "Global opacity " << 100 - int(Mixer::manager().session()->fading() * 100.0) << " %";
|
||||
return Cursor(Cursor_Hand, info.str() );
|
||||
}
|
||||
|
||||
// nothing to do
|
||||
return Cursor();
|
||||
}
|
||||
//
|
||||
// Interaction with source
|
||||
//
|
||||
// compute delta translation
|
||||
s->group(mode_)->translation_ = s->stored_status_->translation_ + gl_Position_to - gl_Position_from;
|
||||
|
||||
// manage mixing group
|
||||
if (s->mixinggroup_ != nullptr ) {
|
||||
// inform mixing groups to follow the current source
|
||||
if (Source::isCurrent(s) && s->mixinggroup_->action() > MixingGroup::ACTION_UPDATE) {
|
||||
s->mixinggroup_->follow(s);
|
||||
// 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 );
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MixingView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static int accumulator = 0;
|
||||
accumulator++;
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
bool first = true;
|
||||
glm::vec3 delta_translation(0.f);
|
||||
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
|
||||
// individual move with SHIFT
|
||||
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
|
||||
continue;
|
||||
|
||||
Group *sourceNode = (*it)->group(mode_);
|
||||
glm::vec3 dest_translation(0.f);
|
||||
|
||||
if (first) {
|
||||
// dest starts at current
|
||||
dest_translation = sourceNode->translation_;
|
||||
|
||||
// + ALT : discrete displacement
|
||||
if (UserInterface::manager().altModifier()) {
|
||||
if (accumulator > 10) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.11f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
dest_translation.y = ROUND(dest_translation.y, 10.f);
|
||||
accumulator = 0;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR;
|
||||
}
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
if ((*it)->active()) {
|
||||
info << "Alpha " << std::fixed << std::setprecision(3) << (*it)->blendingShader()->color.a << " ";
|
||||
info << ( ((*it)->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
|
||||
}
|
||||
else
|
||||
info << "Inactive " << ICON_FA_SNOWFLAKE;
|
||||
current_action_ = (*it)->name() + ": " + info.str();
|
||||
|
||||
// delta for others to follow
|
||||
delta_translation = dest_translation - sourceNode->translation_;
|
||||
}
|
||||
else {
|
||||
// dest = current + delta from first
|
||||
dest_translation = sourceNode->translation_ + delta_translation;
|
||||
}
|
||||
|
||||
// apply & request update
|
||||
sourceNode->translation_ = dest_translation;
|
||||
(*it)->touch();
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MixingView::setAlpha(Source *s)
|
||||
{
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
// move the layer node of the source
|
||||
Group *sourceNode = s->group(mode_);
|
||||
glm::vec2 mix_pos = glm::vec2(DEFAULT_MIXING_TRANSLATION);
|
||||
|
||||
for(NodeSet::iterator it = scene.ws()->begin(); it != scene.ws()->end(); ++it) {
|
||||
// avoid superposing icons: distribute equally
|
||||
if ( glm::distance(glm::vec2((*it)->translation_), mix_pos) < DELTA_ALPHA) {
|
||||
mix_pos += glm::vec2(-0.03f, 0.03f);
|
||||
}
|
||||
}
|
||||
|
||||
sourceNode->translation_.x = mix_pos.x;
|
||||
sourceNode->translation_.y = mix_pos.y;
|
||||
|
||||
// request update
|
||||
s->touch();
|
||||
}
|
||||
|
||||
|
||||
void MixingView::updateSelectionOverlay()
|
||||
{
|
||||
View::updateSelectionOverlay();
|
||||
|
||||
if (overlay_selection_->visible_) {
|
||||
// calculate bbox on selection
|
||||
GlmToolkit::AxisAlignedBoundingBox selection_box = BoundingBoxVisitor::AABB(Mixer::selection().getCopy(), this);
|
||||
overlay_selection_->scale_ = selection_box.scale();
|
||||
overlay_selection_->translation_ = selection_box.center();
|
||||
|
||||
// slightly extend the boundary of the selection
|
||||
overlay_selection_frame_->scale_ = glm::vec3(1.f) + glm::vec3(0.01f, 0.01f, 1.f) / overlay_selection_->scale_;
|
||||
}
|
||||
}
|
||||
|
||||
#define CIRCLE_PIXELS 64
|
||||
#define CIRCLE_PIXEL_RADIUS 1024.0
|
||||
//#define CIRCLE_PIXELS 256
|
||||
//#define CIRCLE_PIXEL_RADIUS 16384.0
|
||||
//#define CIRCLE_PIXELS 1024
|
||||
//#define CIRCLE_PIXEL_RADIUS 262144.0
|
||||
|
||||
float sin_quad_texture(float x, float y) {
|
||||
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ) / CIRCLE_PIXEL_RADIUS, 0.f, 1.f ) );
|
||||
float D = sqrt( ( x * x )/ CIRCLE_PIXEL_RADIUS + ( y * y )/ CIRCLE_PIXEL_RADIUS );
|
||||
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
|
||||
}
|
||||
|
||||
uint textureMixingQuadratic()
|
||||
{
|
||||
static GLuint texid = 0;
|
||||
if (texid == 0) {
|
||||
// generate the texture with alpha exactly as computed for sources
|
||||
GLubyte matrix[CIRCLE_PIXELS*CIRCLE_PIXELS * 4];
|
||||
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);
|
||||
|
||||
}
|
||||
return texid;
|
||||
}
|
||||
43
MixingView.h
Normal file
43
MixingView.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef MIXINGVIEW_H
|
||||
#define MIXINGVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
//class MixingGroup;
|
||||
|
||||
class MixingView : public View
|
||||
{
|
||||
public:
|
||||
MixingView();
|
||||
|
||||
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);
|
||||
inline float limboScale() { return limbo_scale_; }
|
||||
|
||||
private:
|
||||
void updateSelectionOverlay() override;
|
||||
|
||||
float limbo_scale_;
|
||||
|
||||
Group *slider_root_;
|
||||
Disk *slider_;
|
||||
Disk *button_white_;
|
||||
Disk *button_black_;
|
||||
Disk *stashCircle_;
|
||||
Mesh *mixingCircle_;
|
||||
Mesh *circle_;
|
||||
};
|
||||
|
||||
|
||||
#endif // MIXINGVIEW_H
|
||||
316
NetworkSource.cpp
Normal file
316
NetworkSource.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/gst.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"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NETWORK_DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
// this is called when receiving an answer for streaming request
|
||||
void StreamerResponseListener::ProcessMessage( const osc::ReceivedMessage& m,
|
||||
const IpEndpointName& remoteEndpoint )
|
||||
{
|
||||
char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH];
|
||||
remoteEndpoint.AddressAndPortAsString(sender);
|
||||
|
||||
try{
|
||||
if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_STREAM_OFFER ) == 0 ){
|
||||
#ifdef NETWORK_DEBUG
|
||||
Log::Info("Received stream info from %s", sender);
|
||||
#endif
|
||||
NetworkToolkit::StreamConfig conf;
|
||||
|
||||
// someone is offering a stream
|
||||
osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin();
|
||||
conf.port = (arg++)->AsInt32();
|
||||
conf.protocol = (NetworkToolkit::Protocol) (arg++)->AsInt32();
|
||||
conf.width = (arg++)->AsInt32();
|
||||
conf.height = (arg++)->AsInt32();
|
||||
|
||||
// we got the offer from Streaming::manager()
|
||||
parent_->config_ = conf;
|
||||
parent_->connected_ = true;
|
||||
parent_->received_config_ = true;
|
||||
|
||||
}
|
||||
else if( std::strcmp( m.AddressPattern(), OSC_PREFIX OSC_STREAM_REJECT ) == 0 ){
|
||||
#ifdef NETWORK_DEBUG
|
||||
Log::Info("Received rejection from %s", sender);
|
||||
#endif
|
||||
parent_->connected_ = false;
|
||||
parent_->received_config_ = true;
|
||||
}
|
||||
}
|
||||
catch( osc::Exception& e ){
|
||||
// any parsing errors such as unexpected argument types, or
|
||||
// missing arguments get thrown as exceptions.
|
||||
Log::Info("error while parsing message '%s' from %s : %s", m.AddressPattern(), sender, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NetworkStream::NetworkStream(): Stream(),
|
||||
receiver_(nullptr), received_config_(false), connected_(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
glm::ivec2 NetworkStream::resolution() const
|
||||
{
|
||||
return glm::ivec2(config_.width, config_.height);
|
||||
}
|
||||
|
||||
|
||||
std::string NetworkStream::clientAddress() const
|
||||
{
|
||||
return config_.client_address + ":" + std::to_string(config_.port);
|
||||
}
|
||||
|
||||
std::string NetworkStream::serverAddress() const
|
||||
{
|
||||
return streamer_.address;
|
||||
}
|
||||
|
||||
void wait_for_stream_(UdpListeningReceiveSocket *receiver)
|
||||
{
|
||||
receiver->Run();
|
||||
}
|
||||
|
||||
void NetworkStream::connect(const std::string &nameconnection)
|
||||
{
|
||||
// start fresh
|
||||
if (connected())
|
||||
disconnect();
|
||||
received_config_ = false;
|
||||
|
||||
// refuse self referencing
|
||||
if (nameconnection.compare(Connection::manager().info().name) == 0) {
|
||||
Log::Warning("Cannot create self-referencing Network Source '%s'", nameconnection.c_str());
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// does this Connection exists?
|
||||
int streamer_index = Connection::manager().index(nameconnection);
|
||||
|
||||
// Nope, cannot connect to unknown connection
|
||||
if (streamer_index < 0) {
|
||||
Log::Warning("Cannot connect to %s: please make sure %s is active on this machine.", nameconnection.c_str(), APP_NAME);
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// ok, we want to ask to this connected streamer to send us a stream
|
||||
streamer_ = Connection::manager().info(streamer_index);
|
||||
std::string listener_address = NetworkToolkit::closest_host_ip(streamer_.address);
|
||||
|
||||
// prepare listener to receive stream config from remote streaming manager
|
||||
listener_.setParent(this);
|
||||
|
||||
// find an available port to receive response from remote streaming manager
|
||||
int listener_port_ = -1;
|
||||
for (int trial = 0; receiver_ == nullptr && trial < 10 ; trial++) {
|
||||
try {
|
||||
// invent a port which would be available
|
||||
listener_port_ = 72000 + rand()%1000;
|
||||
// try to create receiver (through exception on fail)
|
||||
receiver_ = new UdpListeningReceiveSocket(IpEndpointName(listener_address.c_str(), listener_port_), &listener_);
|
||||
}
|
||||
catch (const std::runtime_error&) {
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
}
|
||||
if (receiver_ == nullptr) {
|
||||
Log::Notify("Cannot establish connection with %s. Please check your network.", streamer_.name.c_str());
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// build OSC message
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
p.Clear();
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_STREAM_REQUEST );
|
||||
// send my listening port to indicate to Connection::manager where to reply
|
||||
p << listener_port_;
|
||||
p << Connection::manager().info().name.c_str();
|
||||
p << osc::EndMessage;
|
||||
|
||||
// send OSC message to streamer
|
||||
UdpTransmitSocket socket( IpEndpointName(streamer_.address.c_str(), streamer_.port_stream_request) );
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
|
||||
// Now we wait for the offer from the streamer
|
||||
std::thread(wait_for_stream_, receiver_).detach();
|
||||
|
||||
#ifdef NETWORK_DEBUG
|
||||
Log::Info("Asking %s:%d for a stream", streamer_.address.c_str(), streamer_.port_stream_request);
|
||||
Log::Info("Waiting for response at %s:%d", Connection::manager().info().address.c_str(), listener_port_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkStream::disconnect()
|
||||
{
|
||||
// receiver should not be active anyway, make sure it is deleted
|
||||
if (receiver_) {
|
||||
delete receiver_;
|
||||
receiver_ = nullptr;
|
||||
}
|
||||
|
||||
if (connected_) {
|
||||
// build OSC message to inform disconnection
|
||||
char buffer[IP_MTU_SIZE];
|
||||
osc::OutboundPacketStream p( buffer, IP_MTU_SIZE );
|
||||
p.Clear();
|
||||
p << osc::BeginMessage( OSC_PREFIX OSC_STREAM_DISCONNECT );
|
||||
p << config_.port; // send my stream port to identify myself to the streamer Connection::manager
|
||||
p << osc::EndMessage;
|
||||
|
||||
// send OSC message to streamer
|
||||
UdpTransmitSocket socket( IpEndpointName(streamer_.address.c_str(), streamer_.port_stream_request) );
|
||||
socket.Send( p.Data(), p.Size() );
|
||||
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
bool NetworkStream::connected() const
|
||||
{
|
||||
return connected_ && Stream::isPlaying();
|
||||
}
|
||||
|
||||
void NetworkStream::update()
|
||||
{
|
||||
Stream::update();
|
||||
|
||||
if ( !ready_ && !failed_ && received_config_)
|
||||
{
|
||||
// only once
|
||||
received_config_ = false;
|
||||
|
||||
// stop receiving streamer info
|
||||
if (receiver_)
|
||||
receiver_->AsynchronousBreak();
|
||||
|
||||
if (connected_) {
|
||||
|
||||
#ifdef NETWORK_DEBUG
|
||||
Log::Info("Creating Network Stream %d (%d x %d)", config_.port, config_.width, config_.height);
|
||||
#endif
|
||||
// prepare pipeline parameter with port given in config_
|
||||
std::string parameter = std::to_string(config_.port);
|
||||
|
||||
// make sure the shared memory socket exists
|
||||
if (config_.protocol == NetworkToolkit::SHM_RAW) {
|
||||
// for shared memory, the parameter is a file location in settings
|
||||
parameter = SystemToolkit::full_filename(SystemToolkit::temp_path(), "shm") + parameter;
|
||||
// try few times to see if file exists and wait 20ms each time
|
||||
for(int trial = 0; trial < 5; trial ++){
|
||||
if ( SystemToolkit::file_exists(parameter))
|
||||
break;
|
||||
std::this_thread::sleep_for (std::chrono::milliseconds(20));
|
||||
}
|
||||
// failed to find the shm socket file: cannot connect
|
||||
if (!SystemToolkit::file_exists(parameter)) {
|
||||
Log::Warning("Cannot connect to shared memory %s.", parameter.c_str());
|
||||
failed_ = true;
|
||||
}
|
||||
parameter = "\"" + parameter + "\"";
|
||||
}
|
||||
|
||||
// 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";
|
||||
|
||||
// open the pipeline with generic stream class
|
||||
Stream::open(pipeline.str(), config_.width, config_.height);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log::Warning("Connection was rejected by %s.\nMake sure it accepts connection and try again.", streamer_.name.c_str());
|
||||
failed_=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NetworkSource::NetworkSource() : StreamSource()
|
||||
{
|
||||
// create stream
|
||||
stream_ = static_cast<Stream *>( new NetworkStream );
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::SHARE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
|
||||
NetworkSource::~NetworkSource()
|
||||
{
|
||||
networkStream()->disconnect();
|
||||
}
|
||||
|
||||
NetworkStream *NetworkSource::networkStream() const
|
||||
{
|
||||
return dynamic_cast<NetworkStream *>(stream_);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
std::string NetworkSource::connection() const
|
||||
{
|
||||
return connection_name_;
|
||||
}
|
||||
|
||||
void NetworkSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
82
NetworkSource.h
Normal file
82
NetworkSource.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#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();
|
||||
|
||||
void connect(const std::string &nameconnection);
|
||||
bool connected() const;
|
||||
void disconnect();
|
||||
|
||||
void update() override;
|
||||
|
||||
glm::ivec2 resolution() const;
|
||||
inline NetworkToolkit::Protocol protocol() const { return config_.protocol; }
|
||||
std::string clientAddress() const;
|
||||
std::string serverAddress() const;
|
||||
|
||||
private:
|
||||
// connection information
|
||||
ConnectionInfo streamer_;
|
||||
StreamerResponseListener listener_;
|
||||
UdpListeningReceiveSocket *receiver_;
|
||||
std::atomic<bool> received_config_;
|
||||
std::atomic<bool> connected_;
|
||||
|
||||
NetworkToolkit::StreamConfig config_;
|
||||
};
|
||||
|
||||
|
||||
class NetworkSource : public StreamSource
|
||||
{
|
||||
std::string connection_name_;
|
||||
|
||||
public:
|
||||
NetworkSource();
|
||||
~NetworkSource();
|
||||
|
||||
// Source interface
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
// StreamSource interface
|
||||
Stream *stream() const override { return stream_; }
|
||||
NetworkStream *networkStream() const;
|
||||
|
||||
// specific interface
|
||||
void setConnection(const std::string &nameconnection);
|
||||
std::string connection() const;
|
||||
|
||||
glm::ivec2 icon() const override { return glm::ivec2(18, 11); }
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // NETWORKSOURCE_H
|
||||
208
NetworkToolkit.cpp
Normal file
208
NetworkToolkit.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#ifdef linux
|
||||
#include <linux/netdevice.h>
|
||||
#endif
|
||||
|
||||
// OSC IP gethostbyname
|
||||
#include "ip/NetworkingUtils.h"
|
||||
|
||||
#include "NetworkToolkit.h"
|
||||
|
||||
|
||||
/***
|
||||
*
|
||||
* TCP Server JPEG : broadcast
|
||||
* SND:
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! jpegenc ! rtpjpegpay ! rtpstreampay ! tcpserversink port=5400
|
||||
* RCV:
|
||||
* gst-launch-1.0 tcpclientsrc port=5400 ! application/x-rtp-stream,encoding-name=JPEG ! rtpstreamdepay! rtpjpegdepay ! jpegdec ! autovideosink
|
||||
*
|
||||
* TCP Server H264 : broadcast
|
||||
* SND:
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! x264enc ! rtph264pay ! rtpstreampay ! tcpserversink port=5400
|
||||
* RCV:
|
||||
* gst-launch-1.0 tcpclientsrc port=5400 ! application/x-rtp-stream,media=video,encoding-name=H264,payload=96,clock-rate=90000 ! rtpstreamdepay ! rtpjitterbuffer ! rtph264depay ! avdec_h264 ! autovideosink
|
||||
*
|
||||
* UDP unicast
|
||||
* SND
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! videoconvert ! video/x-raw, format=RGB, framerate=30/1 ! queue max-size-buffers=3 ! jpegenc ! rtpjpegpay ! udpsink port=5000 host=127.0.0.1
|
||||
* RCV
|
||||
* gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
|
||||
*
|
||||
* * UDP multicast : hass to know the PORT and IP of all clients
|
||||
* SND
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! jpegenc ! rtpjpegpay ! multiudpsink clients="127.0.0.1:5000,127.0.0.1:5001"
|
||||
* RCV
|
||||
* gst-launch-1.0 -v udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
|
||||
* gst-launch-1.0 -v udpsrc port=5001 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink
|
||||
*
|
||||
* RAW UDP (caps has to match exactly, and depends on resolution)
|
||||
* SND
|
||||
* gst-launch-1.0 -v videotestsrc is-live=true ! video/x-raw,format=RGBA,width=1920,height=1080 ! rtpvrawpay ! udpsink port=5000 host=127.0.0.1
|
||||
* RCV
|
||||
* gst-launch-1.0 udpsrc port=5000 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)RAW, sampling=(string)RGBA, depth=(string)8, width=(string)1920, height=(string)1080, colorimetry=(string)SMPTE240M, payload=(int)96, ssrc=(uint)2272750581, timestamp-offset=(uint)1699493959, seqnum-offset=(uint)14107, a-framerate=(string)30" ! rtpvrawdepay ! videoconvert ! autovideosink
|
||||
*
|
||||
*
|
||||
* SHM RAW RGB
|
||||
* SND
|
||||
* gst-launch-1.0 videotestsrc is-live=true ! video/x-raw, format=RGB, framerate=30/1 ! shmsink socket-path=/tmp/blah
|
||||
* 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
|
||||
*
|
||||
* */
|
||||
|
||||
const char* NetworkToolkit::protocol_name[NetworkToolkit::DEFAULT] = {
|
||||
"Shared Memory",
|
||||
"RTP JPEG Stream",
|
||||
"RTP H264 Stream",
|
||||
"RTP JPEG Broadcast",
|
||||
"RTP H264 Broadcast"
|
||||
};
|
||||
|
||||
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"
|
||||
};
|
||||
|
||||
const std::vector<std::string> NetworkToolkit::protocol_receive_pipeline {
|
||||
|
||||
"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"
|
||||
};
|
||||
|
||||
bool initialized_ = false;
|
||||
std::vector<std::string> ipstrings_;
|
||||
std::vector<unsigned long> iplongs_;
|
||||
|
||||
|
||||
void add_interface(int fd, const char *name) {
|
||||
struct ifreq ifreq;
|
||||
memset(&ifreq, 0, sizeof ifreq);
|
||||
strncpy(ifreq.ifr_name, name, IFNAMSIZ);
|
||||
if(ioctl(fd, SIOCGIFADDR, &ifreq)==0) {
|
||||
char host[128];
|
||||
int family;
|
||||
switch(family=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);
|
||||
break;
|
||||
default:
|
||||
case AF_UNSPEC:
|
||||
return; /* ignore */
|
||||
}
|
||||
// add only if not already listed
|
||||
std::string hostip(host);
|
||||
if ( std::find(ipstrings_.begin(), ipstrings_.end(), hostip) == ipstrings_.end() )
|
||||
{
|
||||
ipstrings_.push_back( hostip );
|
||||
iplongs_.push_back( GetHostByName(host) );
|
||||
// printf("%s %s %lu\n", name, host, GetHostByName(host));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void list_interfaces()
|
||||
{
|
||||
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;
|
||||
#ifndef linux
|
||||
len=IFNAMSIZ + ifreq->ifr_addr.sa_len;
|
||||
#else
|
||||
len=sizeof *ifreq;
|
||||
#endif
|
||||
add_interface(fd, ifreq->ifr_name);
|
||||
ifreq=(struct ifreq*)((char*)ifreq+len);
|
||||
i+=len;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
std::vector<std::string> NetworkToolkit::host_ips()
|
||||
{
|
||||
if (!initialized_)
|
||||
list_interfaces();
|
||||
|
||||
return ipstrings_;
|
||||
}
|
||||
|
||||
|
||||
bool NetworkToolkit::is_host_ip(const std::string &ip)
|
||||
{
|
||||
if ( ip.compare("localhost") == 0)
|
||||
return true;
|
||||
|
||||
if (!initialized_)
|
||||
list_interfaces();
|
||||
|
||||
return std::find(ipstrings_.begin(), ipstrings_.end(), ip) != ipstrings_.end();
|
||||
}
|
||||
|
||||
std::string NetworkToolkit::closest_host_ip(const std::string &ip)
|
||||
{
|
||||
std::string address = "localhost";
|
||||
|
||||
if (!initialized_)
|
||||
list_interfaces();
|
||||
|
||||
// discard trivial case
|
||||
if ( ip.compare("localhost") != 0)
|
||||
{
|
||||
int index_mini = -1;
|
||||
unsigned long host = GetHostByName( ip.c_str() );
|
||||
unsigned long mini = host;
|
||||
|
||||
for (size_t i=0; i < iplongs_.size(); i++){
|
||||
unsigned long diff = host > iplongs_[i] ? host-iplongs_[i] : iplongs_[i]-host;
|
||||
if (diff < mini) {
|
||||
mini = diff;
|
||||
index_mini = (int) i;
|
||||
}
|
||||
}
|
||||
|
||||
if (index_mini>0)
|
||||
address = ipstrings_[index_mini];
|
||||
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
std::string NetworkToolkit::hostname()
|
||||
{
|
||||
char hostname[1024];
|
||||
hostname[1023] = '\0';
|
||||
gethostname(hostname, 1023);
|
||||
|
||||
return std::string(hostname);
|
||||
}
|
||||
78
NetworkToolkit.h
Normal file
78
NetworkToolkit.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef NETWORKTOOLKIT_H
|
||||
#define NETWORKTOOLKIT_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define OSC_PREFIX "/vimix"
|
||||
#define OSC_PING "/ping"
|
||||
#define OSC_PONG "/pong"
|
||||
#define OSC_STREAM_REQUEST "/request"
|
||||
#define OSC_STREAM_OFFER "/offer"
|
||||
#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_JPEG,
|
||||
UDP_H264,
|
||||
TCP_JPEG,
|
||||
TCP_H264,
|
||||
DEFAULT
|
||||
} Protocol;
|
||||
|
||||
|
||||
struct StreamConfig {
|
||||
|
||||
Protocol protocol;
|
||||
std::string client_name;
|
||||
std::string client_address;
|
||||
int port;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
StreamConfig () {
|
||||
protocol = DEFAULT;
|
||||
client_name = "";
|
||||
client_address = "127.0.0.1";
|
||||
port = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
|
||||
inline StreamConfig& operator = (const StreamConfig& o)
|
||||
{
|
||||
if (this != &o) {
|
||||
this->client_name = o.client_name;
|
||||
this->client_address = o.client_address;
|
||||
this->port = o.port;
|
||||
this->protocol = o.protocol;
|
||||
this->width = o.width;
|
||||
this->height = o.height;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
extern const char* protocol_name[DEFAULT];
|
||||
extern const std::vector<std::string> protocol_send_pipeline;
|
||||
extern const std::vector<std::string> protocol_receive_pipeline;
|
||||
|
||||
std::string hostname();
|
||||
std::vector<std::string> host_ips();
|
||||
bool is_host_ip(const std::string &ip);
|
||||
std::string closest_host_ip(const std::string &ip);
|
||||
|
||||
}
|
||||
|
||||
#endif // NETWORKTOOLKIT_H
|
||||
164
PatternSource.cpp
Normal file
164
PatternSource.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <sstream>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "PatternSource.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "ImageShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Decorations.h"
|
||||
#include "Stream.h"
|
||||
#include "Visitor.h"
|
||||
#include "Log.h"
|
||||
|
||||
#define MAX_PATTERN 24
|
||||
|
||||
// smpte (0) – SMPTE 100%% color bars
|
||||
// snow (1) – Random (television snow)
|
||||
// black (2) – 100%% Black
|
||||
// white (3) – 100%% White
|
||||
// red (4) – Red
|
||||
// green (5) – Green
|
||||
// blue (6) – Blue
|
||||
// checkers-1 (7) – Checkers 1px
|
||||
// checkers-2 (8) – Checkers 2px
|
||||
// checkers-4 (9) – Checkers 4px
|
||||
// checkers-8 (10) – Checkers 8px
|
||||
// circular (11) – Circular
|
||||
// blink (12) – Blink
|
||||
// smpte75 (13) – SMPTE 75%% color bars
|
||||
// zone-plate (14) – Zone plate
|
||||
// gamut (15) – Gamut checkers
|
||||
// chroma-zone-plate (16) – Chroma zone plate
|
||||
// solid-color (17) – Solid color
|
||||
// ball (18) – Moving ball
|
||||
// smpte100 (19) – SMPTE 100%% color bars
|
||||
// bar (20) – Bar
|
||||
// pinwheel (21) – Pinwheel
|
||||
// spokes (22) – Spokes
|
||||
// gradient (23) – Gradient
|
||||
// colors (24) – Colors
|
||||
const char* pattern_internal_[MAX_PATTERN] = { "videotestsrc pattern=black",
|
||||
"videotestsrc pattern=white",
|
||||
"videotestsrc pattern=gradient",
|
||||
"videotestsrc pattern=checkers-1 ! video/x-raw,format=GRAY8 ! videoconvert",
|
||||
"videotestsrc pattern=checkers-8 ! video/x-raw,format=GRAY8 ! videoconvert",
|
||||
"videotestsrc pattern=circular",
|
||||
"frei0r-src-lissajous0r ratiox=0.001 ratioy=0.999 ! videoconvert",
|
||||
"videotestsrc pattern=pinwheel",
|
||||
"videotestsrc pattern=spokes",
|
||||
"videotestsrc pattern=red",
|
||||
"videotestsrc pattern=green",
|
||||
"videotestsrc pattern=blue",
|
||||
"videotestsrc pattern=smpte100",
|
||||
"videotestsrc pattern=colors",
|
||||
"videotestsrc pattern=smpte",
|
||||
"videotestsrc pattern=snow",
|
||||
"videotestsrc pattern=blink",
|
||||
"videotestsrc pattern=zone-plate",
|
||||
"videotestsrc pattern=chroma-zone-plate",
|
||||
"videotestsrc pattern=bar horizontal-speed=5",
|
||||
"videotestsrc pattern=ball",
|
||||
"frei0r-src-ising0r",
|
||||
"videotestsrc pattern=black ! timeoverlay halignment=center valignment=center font-desc=\"Sans, 72\" ",
|
||||
"videotestsrc pattern=black ! clockoverlay halignment=center valignment=center font-desc=\"Sans, 72\" "
|
||||
};
|
||||
|
||||
std::vector<std::string> Pattern::pattern_types = { "Black",
|
||||
"White",
|
||||
"Gradient",
|
||||
"Checkers 1x1 px",
|
||||
"Checkers 8x8 px",
|
||||
"Circles",
|
||||
"Lissajous",
|
||||
"Pinwheel",
|
||||
"Spokes",
|
||||
"Red",
|
||||
"Green",
|
||||
"Blue",
|
||||
"Color bars",
|
||||
"RGB grid",
|
||||
"SMPTE test pattern",
|
||||
"Television snow",
|
||||
"Blink",
|
||||
"Fresnel zone plate",
|
||||
"Chroma zone plate",
|
||||
"Bar moving",
|
||||
"Ball bouncing"
|
||||
#if GST_VERSION_MINOR > 17
|
||||
,
|
||||
"Blob",
|
||||
"Timer",
|
||||
"Clock"
|
||||
#endif
|
||||
};
|
||||
|
||||
Pattern::Pattern() : Stream(), type_(MAX_PATTERN) // invalid pattern
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
glm::ivec2 Pattern::resolution()
|
||||
{
|
||||
return glm::ivec2( width_, height_);
|
||||
}
|
||||
|
||||
|
||||
void Pattern::open( uint pattern, glm::ivec2 res )
|
||||
{
|
||||
type_ = MIN(pattern, MAX_PATTERN-1);
|
||||
std::string gstreamer_pattern = pattern_internal_[type_];
|
||||
|
||||
// there is always a special case...
|
||||
switch(type_)
|
||||
{
|
||||
case 18: // zone plates
|
||||
case 17:
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << " kx2=" << (int)(res.x * 10.f / res.y) << " ky2=10 kt=4";
|
||||
gstreamer_pattern += oss.str(); // Zone plate
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// all patterns before 'SMPTE test pattern' are single frames (not animated)
|
||||
single_frame_ = type_ < 14;
|
||||
|
||||
// (private) open stream
|
||||
Stream::open(gstreamer_pattern, res.x, res.y);
|
||||
}
|
||||
|
||||
PatternSource::PatternSource() : StreamSource()
|
||||
{
|
||||
// create stream
|
||||
stream_ = static_cast<Stream *>( new Pattern );
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::PATTERN, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
void PatternSource::setPattern(uint type, glm::ivec2 resolution)
|
||||
{
|
||||
Log::Notify("Creating Source with pattern '%s'", Pattern::pattern_types[type].c_str());
|
||||
|
||||
pattern()->open( (uint) type, resolution );
|
||||
stream_->play(true);
|
||||
}
|
||||
|
||||
void PatternSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
Pattern *PatternSource::pattern() const
|
||||
{
|
||||
return dynamic_cast<Pattern *>(stream_);
|
||||
}
|
||||
|
||||
|
||||
42
PatternSource.h
Normal file
42
PatternSource.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef PATTERNSOURCE_H
|
||||
#define PATTERNSOURCE_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "StreamSource.h"
|
||||
|
||||
class Pattern : public Stream
|
||||
{
|
||||
public:
|
||||
static std::vector<std::string> pattern_types;
|
||||
|
||||
Pattern();
|
||||
void open( uint pattern, glm::ivec2 res);
|
||||
|
||||
glm::ivec2 resolution();
|
||||
inline uint type() const { return type_; }
|
||||
|
||||
private:
|
||||
uint type_;
|
||||
};
|
||||
|
||||
class PatternSource : public StreamSource
|
||||
{
|
||||
public:
|
||||
PatternSource();
|
||||
|
||||
// Source interface
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
// StreamSource interface
|
||||
Stream *stream() const override { return stream_; }
|
||||
|
||||
// specific interface
|
||||
Pattern *pattern() const;
|
||||
void setPattern(uint type, glm::ivec2 resolution);
|
||||
|
||||
glm::ivec2 icon() const override { return glm::ivec2(11, 5); }
|
||||
|
||||
};
|
||||
|
||||
#endif // PATTERNSOURCE_H
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "PickingVisitor.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "Primitives.h"
|
||||
#include "Decorations.h"
|
||||
|
||||
#include "GlmToolkit.h"
|
||||
@@ -12,15 +11,15 @@
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
|
||||
PickingVisitor::PickingVisitor(glm::vec3 coordinates) : Visitor()
|
||||
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) : Visitor()
|
||||
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 );
|
||||
}
|
||||
@@ -29,20 +28,16 @@ void PickingVisitor::visit(Node &n)
|
||||
{
|
||||
// use the transform modified during update
|
||||
modelview_ *= n.transform_;
|
||||
|
||||
// modelview_ *= transform(n.translation_, n.rotation_, n.scale_);
|
||||
// Log::Info("Node %d", n.id());
|
||||
// Log::Info("%s", glm::to_string(modelview_).c_str());
|
||||
}
|
||||
|
||||
void PickingVisitor::visit(Group &n)
|
||||
{
|
||||
if (!n.visible_)
|
||||
if (!n.visible_ && !force_)
|
||||
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;
|
||||
}
|
||||
@@ -50,7 +45,7 @@ void PickingVisitor::visit(Group &n)
|
||||
|
||||
void PickingVisitor::visit(Switch &n)
|
||||
{
|
||||
if (!n.visible_ || n.numChildren()<1)
|
||||
if ((!n.visible_ && !force_) || n.numChildren()<1)
|
||||
return;
|
||||
|
||||
glm::mat4 mv = modelview_;
|
||||
@@ -65,7 +60,7 @@ void PickingVisitor::visit(Primitive &)
|
||||
|
||||
void PickingVisitor::visit(Surface &n)
|
||||
{
|
||||
if (!n.visible_)
|
||||
if (!n.visible_ && !force_)
|
||||
return;
|
||||
|
||||
// if more than one point given for testing: test overlap
|
||||
@@ -73,33 +68,39 @@ void PickingVisitor::visit(Surface &n)
|
||||
// create bounding box for those points (2 in practice)
|
||||
GlmToolkit::AxisAlignedBoundingBox bb_points;
|
||||
bb_points.extend(points_);
|
||||
|
||||
// apply inverse transform
|
||||
bb_points = bb_points.transformed(glm::inverse(modelview_)) ;
|
||||
|
||||
// test bounding box for overlap with inverse transform bbox
|
||||
if ( bb_points.intersect( n.bbox() ) )
|
||||
// if ( n.bbox().contains( bb_points ) )
|
||||
// update the coordinates of the Surface bounding box to match transform
|
||||
GlmToolkit::AxisAlignedBoundingBox surf;
|
||||
surf = n.bbox().transformed(modelview_);
|
||||
// Test inclusion of all four corners of the Surface inside the selection bounding box
|
||||
if ( bb_points.contains( surf) ) {
|
||||
// add this surface to the nodes picked
|
||||
nodes_.push_back( std::pair(&n, glm::vec2(0.f)) );
|
||||
}
|
||||
// // ALTERNATIVE BEHAVIOR : test bounding box for overlap only
|
||||
// // apply inverse transform
|
||||
// bb_points = bb_points.transformed(glm::inverse(modelview_)) ;
|
||||
// if ( bb_points.intersect( n.bbox() ) ) {
|
||||
// // add this surface to the nodes picked
|
||||
// nodes_.push_back( std::pair(&n, glm::vec2(0.f)) );
|
||||
// }
|
||||
|
||||
}
|
||||
// only one point
|
||||
else if (points_.size() > 0) {
|
||||
|
||||
// apply inverse transform to the point of interest
|
||||
glm::vec4 P = glm::inverse(modelview_) * glm::vec4( points_[0], 1.f );
|
||||
|
||||
// test bounding box for picking from a single point
|
||||
if ( n.bbox().contains( glm::vec3(P)) )
|
||||
if ( n.bbox().contains( glm::vec3(P)) ) {
|
||||
// add this surface to the nodes picked
|
||||
nodes_.push_back( std::pair(&n, glm::vec2(P)) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PickingVisitor::visit(Disk &n)
|
||||
{
|
||||
// discard if not visible or if not exactly one point given for picking
|
||||
if (!n.visible_ || points_.size() != 1)
|
||||
if ((!n.visible_ && !force_) || points_.size() != 1)
|
||||
return;
|
||||
|
||||
// apply inverse transform to the point of interest
|
||||
@@ -115,7 +116,7 @@ void PickingVisitor::visit(Disk &n)
|
||||
void PickingVisitor::visit(Handles &n)
|
||||
{
|
||||
// discard if not visible or if not exactly one point given for picking
|
||||
if (!n.visible_ || points_.size() != 1)
|
||||
if ((!n.visible_ && !force_) || points_.size() != 1)
|
||||
return;
|
||||
|
||||
// apply inverse transform to the point of interest
|
||||
@@ -125,6 +126,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
|
||||
@@ -145,9 +155,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 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 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 )
|
||||
@@ -157,6 +186,23 @@ void PickingVisitor::visit(Handles &n)
|
||||
}
|
||||
|
||||
|
||||
void PickingVisitor::visit(Symbol& n)
|
||||
{
|
||||
// discard if not visible or if not exactly one point given for picking
|
||||
if ((!n.visible_ && !force_) || points_.size() != 1)
|
||||
return;
|
||||
|
||||
// apply inverse transform to the point of interest
|
||||
glm::vec4 P = glm::inverse(modelview_) * glm::vec4( points_[0], 1.f );
|
||||
|
||||
// test bounding box for picking from a single point
|
||||
if ( n.bbox().contains( glm::vec3(P)) ) {
|
||||
// add this to the nodes picked
|
||||
nodes_.push_back( std::pair(&n, glm::vec2(P)) );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PickingVisitor::visit(Scene &n)
|
||||
{
|
||||
n.root()->accept(*this);
|
||||
|
||||
@@ -21,12 +21,17 @@ class PickingVisitor: public Visitor
|
||||
std::vector<glm::vec3> points_;
|
||||
glm::mat4 modelview_;
|
||||
std::vector< std::pair<Node *, glm::vec2> > nodes_;
|
||||
bool force_;
|
||||
|
||||
public:
|
||||
|
||||
PickingVisitor(glm::vec3 coordinates);
|
||||
PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end);
|
||||
std::vector< std::pair<Node *, glm::vec2> > picked() { return nodes_; }
|
||||
PickingVisitor(glm::vec3 coordinates, bool force = false);
|
||||
PickingVisitor(glm::vec3 selectionstart, glm::vec3 selection_end, bool force = false);
|
||||
|
||||
bool empty() const {return nodes_.empty(); }
|
||||
std::pair<Node *, glm::vec2> back() const { return nodes_.back(); }
|
||||
std::vector< std::pair<Node *, glm::vec2> >::const_reverse_iterator rbegin() { return nodes_.rbegin(); }
|
||||
std::vector< std::pair<Node *, glm::vec2> >::const_reverse_iterator rend() { return nodes_.rend(); }
|
||||
|
||||
// Elements of Scene
|
||||
void visit(Scene& n) override;
|
||||
@@ -45,6 +50,11 @@ public:
|
||||
* @param n
|
||||
*/
|
||||
void visit(Handles& n) override;
|
||||
/**
|
||||
* @brief visit Disk : picking grabber for mixing view
|
||||
* @param n
|
||||
*/
|
||||
void visit(Symbol& n) override;
|
||||
/**
|
||||
* @brief visit Disk : picking grabber for mixing view
|
||||
* @param n
|
||||
|
||||
489
Primitives.cpp
489
Primitives.cpp
@@ -11,6 +11,7 @@
|
||||
#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
|
||||
@@ -18,7 +19,7 @@
|
||||
|
||||
|
||||
|
||||
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)
|
||||
@@ -85,8 +86,12 @@ void Surface::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
if ( !initialized() )
|
||||
init();
|
||||
|
||||
if ( textureindex_ )
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
if ( textureindex_ ) {
|
||||
glBindTexture(GL_TEXTURE_2D, textureindex_);
|
||||
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());
|
||||
|
||||
@@ -116,9 +121,8 @@ void ImageSurface::accept(Visitor& v)
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
MediaSurface::MediaSurface(const std::string& p, Shader *s) : Surface(s)
|
||||
MediaSurface::MediaSurface(const std::string& p, Shader *s) : Surface(s), path_(p)
|
||||
{
|
||||
path_ = p;
|
||||
mediaplayer_ = new MediaPlayer;
|
||||
}
|
||||
|
||||
@@ -137,13 +141,12 @@ void MediaSurface::init()
|
||||
|
||||
void MediaSurface::draw(glm::mat4 modelview, glm::mat4 projection)
|
||||
{
|
||||
if ( !initialized() )
|
||||
if ( !initialized() ) {
|
||||
init();
|
||||
|
||||
// set the texture to the media player once openned
|
||||
// TODO: avoid to repeat with a static flag?
|
||||
if ( mediaplayer_->isOpen() )
|
||||
textureindex_ = mediaplayer_->texture();
|
||||
// set the texture to the media player once openned
|
||||
if ( mediaplayer_->isOpen() )
|
||||
textureindex_ = mediaplayer_->texture();
|
||||
}
|
||||
|
||||
Surface::draw(modelview, projection);
|
||||
}
|
||||
@@ -164,19 +167,8 @@ void MediaSurface::accept(Visitor& 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)
|
||||
@@ -197,7 +189,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)
|
||||
@@ -211,8 +202,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() )
|
||||
@@ -231,63 +220,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;
|
||||
@@ -312,43 +282,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;
|
||||
@@ -358,10 +344,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();
|
||||
@@ -373,19 +359,302 @@ 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_);
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
111
Primitives.h
111
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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -94,7 +98,6 @@ 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;
|
||||
|
||||
@@ -125,54 +128,100 @@ 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);
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
|
||||
|
||||
76
README.md
76
README.md
@@ -1,43 +1,87 @@
|
||||
# vimix
|
||||
Live Video Mixer
|
||||
Live Video Mixing
|
||||
|
||||
*/!\ Work in progress*
|
||||
vimix performs graphical mixing and blending of several movie clips and
|
||||
computer generated graphics, with image processing effects in real-time.
|
||||
|
||||
v-mix is the successor for GLMixer - https://sourceforge.net/projects/glmixer/
|
||||
Its intuitive and hands-on user interface gives direct control on image opacity and
|
||||
shape for producing live graphics during concerts and VJ-ing sessions.
|
||||
|
||||
The ouput image is typically projected full-screen on an external
|
||||
monitor or a projector, but can be recorded live (no audio).
|
||||
|
||||
vimix is the successor for GLMixer - https://sourceforge.net/projects/glmixer/
|
||||
|
||||
# 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
|
||||
|
||||
NB: You'll need to setup the snap permissions.
|
||||
|
||||
### Mac OSX
|
||||
|
||||
Download and open a release package from https://github.com/brunoherbelin/vimix/releases
|
||||
NB: You'll need to accept the exception in OSX security preference.
|
||||
|
||||
## Clone
|
||||
|
||||
git clone --recursive https://github.com/brunoherbelin/vimix.git
|
||||
|
||||
This will create the directory 'vimix', download the latest version of vimix code,
|
||||
and (recursively) clone all the internal git dependencies.
|
||||
|
||||
**To only update a cloned git copy:**
|
||||
|
||||
git pull
|
||||
|
||||
## Compile
|
||||
|
||||
```
|
||||
cmake -G Ninja
|
||||
ninja
|
||||
```
|
||||
mkdir vimix-build
|
||||
cd vimix-build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ../vimix
|
||||
cmake --build .
|
||||
|
||||
### Dependencies
|
||||
|
||||
**Compiling tools:**
|
||||
|
||||
- gcc
|
||||
- make
|
||||
- cmake
|
||||
- Ninja
|
||||
- git
|
||||
|
||||
**Libraries:**
|
||||
|
||||
- gstreamer
|
||||
- libpng
|
||||
- gst-plugins : 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 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 icu4c
|
||||
|
||||
|
||||
#### Ubuntu
|
||||
#### Generate snap
|
||||
|
||||
**tools:**
|
||||
From vimix root directory
|
||||
snapcraft
|
||||
snap install --dangerous vimix_0.5_amd64.snap
|
||||
|
||||
### Memcheck
|
||||
|
||||
apt-get install build-essential cmake ninja-build
|
||||
|
||||
**libs:**
|
||||
|
||||
apt-get install libpng-dev libglfw3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||
G_SLICE=always-malloc valgrind --tool=massif ./vimix
|
||||
|
||||
G_SLICE=always-malloc valgrind --leak-check=full --log-file=vimix_mem.txt ./vimix
|
||||
|
||||
526
Recorder.cpp
526
Recorder.cpp
@@ -20,110 +20,107 @@
|
||||
|
||||
#include "Recorder.h"
|
||||
|
||||
// use glReadPixel or glGetTextImage
|
||||
// read pixels & pbo should be the fastest
|
||||
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
|
||||
#define USE_GLREADPIXEL
|
||||
|
||||
using namespace std;
|
||||
|
||||
Recorder::Recorder() : finished_(false), pbo_index_(0), pbo_next_index_(0), size_(0)
|
||||
PNGRecorder::PNGRecorder() : FrameGrabber()
|
||||
{
|
||||
pbo_[0] = pbo_[1] = 0;
|
||||
}
|
||||
|
||||
PNGRecorder::PNGRecorder() : Recorder()
|
||||
void PNGRecorder::init(GstCaps *caps)
|
||||
{
|
||||
// ignore
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
|
||||
// create a gstreamer pipeline
|
||||
std::string description = "appsrc name=src ! videoconvert ! pngenc ! filesink name=sink";
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("PNG Capture Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// verify location path (path is always terminated by the OS dependent separator)
|
||||
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
|
||||
if (path.empty())
|
||||
path = SystemToolkit::home_path();
|
||||
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".png";
|
||||
|
||||
filename_ = path + SystemToolkit::date_time_string() + "_vimix.png";
|
||||
// setup file sink
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"location", filename_.c_str(),
|
||||
"sync", FALSE,
|
||||
NULL);
|
||||
|
||||
}
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
// "do-timestamp", TRUE,
|
||||
NULL);
|
||||
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
callbacks.need_data = FrameGrabber::callback_need_data;
|
||||
callbacks.enough_data = FrameGrabber::callback_enough_data;
|
||||
callbacks.seek_data = NULL; // stream type is not seekable
|
||||
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
||||
|
||||
// Thread to perform slow operation of saving to file
|
||||
void save_png(std::string filename, unsigned char *data, uint w, uint h, uint c)
|
||||
{
|
||||
// got data to save ?
|
||||
if (data) {
|
||||
// save file
|
||||
stbi_write_png(filename.c_str(), w, h, c, data, w * c);
|
||||
// notify
|
||||
Log::Notify("Capture %s ready (%d x %d %d)", filename.c_str(), w, h, c);
|
||||
// done
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void PNGRecorder::addFrame(FrameBuffer *frame_buffer, float)
|
||||
{
|
||||
// ignore
|
||||
if (frame_buffer == nullptr)
|
||||
return;
|
||||
|
||||
// get what is needed from frame buffer
|
||||
uint w = frame_buffer->width();
|
||||
uint h = frame_buffer->height();
|
||||
uint c = frame_buffer->use_alpha() ? 4 : 3;
|
||||
|
||||
// first iteration: initialize and get frame
|
||||
if (size_ < 1)
|
||||
{
|
||||
// init size
|
||||
size_ = w * h * c;
|
||||
|
||||
// create PBO
|
||||
glGenBuffers(2, pbo_);
|
||||
|
||||
// set writing PBO
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
||||
|
||||
#ifdef USE_GLREADPIXEL
|
||||
// get frame
|
||||
frame_buffer->readPixels();
|
||||
#else
|
||||
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
}
|
||||
// second iteration; get frame and save file
|
||||
else {
|
||||
|
||||
// set reading PBO
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
|
||||
|
||||
// get pixels
|
||||
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
if (NULL != ptr) {
|
||||
// prepare memory buffer0
|
||||
unsigned char * data = (unsigned char*) malloc(size_);
|
||||
// transfer frame to data
|
||||
memmove(data, ptr, size_);
|
||||
// save in separate thread
|
||||
std::thread(save_png, filename_, data, w, h, c).detach();
|
||||
}
|
||||
// unmap
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
|
||||
// ok done
|
||||
glDeleteBuffers(2, pbo_);
|
||||
|
||||
// recorded one frame
|
||||
Log::Warning("PNG Capture Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// unsigned char * data = (unsigned char*) malloc(size);
|
||||
// GLenum format = frame_buffer->use_alpha() ? GL_RGBA : GL_RGB;
|
||||
// glGetTextureSubImage( frame_buffer->texture(), 0, 0, 0, 0, w, h, 1, format, GL_UNSIGNED_BYTE, size, data);
|
||||
// all good
|
||||
Log::Info("PNG Capture started.");
|
||||
|
||||
// start recording !!
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void PNGRecorder::terminate()
|
||||
{
|
||||
Log::Notify("PNG Capture %s is ready.", filename_.c_str());
|
||||
}
|
||||
|
||||
void PNGRecorder::addFrame(GstBuffer *buffer, GstCaps *caps, float dt)
|
||||
{
|
||||
FrameGrabber::addFrame(buffer, caps, dt);
|
||||
|
||||
// PNG Recorder specific :
|
||||
// stop after one frame
|
||||
if (timestamp_ > 0) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* VideoRecorder::profile_name[VideoRecorder::DEFAULT] = {
|
||||
"H264 (Baseline)",
|
||||
"H264 (Realtime)",
|
||||
"H264 (High 4:4:4)",
|
||||
"H265 (Realtime)",
|
||||
"H265 (HQ Animation)",
|
||||
@@ -144,8 +141,13 @@ const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// veryfast (3)
|
||||
// faster (4)
|
||||
// fast (5)
|
||||
"x264enc pass=4 quantizer=23 speed-preset=3 threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
|
||||
"x264enc pass=4 quantizer=16 speed-preset=4 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
|
||||
#ifndef APPLE
|
||||
// "video/x-raw, format=I420 ! x264enc pass=4 quantizer=26 speed-preset=3 threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
|
||||
"video/x-raw, format=I420 ! x264enc tune=\"zerolatency\" threads=4 ! video/x-h264, profile=baseline ! h264parse ! ",
|
||||
#else
|
||||
"video/x-raw, format=I420 ! vtenc_h264_hw realtime=1 allow-frame-reordering=0 ! h264parse ! ",
|
||||
#endif
|
||||
"video/x-raw, format=Y444_10LE ! x264enc pass=4 quantizer=16 speed-preset=4 threads=4 ! video/x-h264, profile=(string)high-4:4:4 ! h264parse ! ",
|
||||
// Control x265 encoder quality :
|
||||
// NB: apparently x265 only accepts I420 format :(
|
||||
// speed-preset
|
||||
@@ -163,7 +165,7 @@ const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// 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=22\" ! video/x-h265, profile=(string)main ! h265parse ! ",
|
||||
"video/x-raw, format=I420 ! x265enc tune=6 speed-preset=4 option-string=\"crf=24\" ! video/x-h265, profile=(string)main ! h265parse ! ",
|
||||
// Apple ProRes encoding parameters
|
||||
// pass
|
||||
// cbr (0) – Constant Bitrate Encoding
|
||||
@@ -176,7 +178,8 @@ const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// 3 ‘hq’
|
||||
// 4 ‘4444’
|
||||
"avenc_prores_ks pass=2 profile=2 quantizer=26 ! ",
|
||||
"avenc_prores_ks pass=2 profile=4 quantizer=18 ! ",
|
||||
"video/x-raw, format=Y444_10LE ! avenc_prores_ks pass=2 profile=4 quantizer=12 ! ",
|
||||
// VP8 WebM encoding
|
||||
"vp8enc end-usage=vbr cpu-used=8 max-quantizer=35 deadline=100000 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=100 ! ",
|
||||
"jpegenc ! "
|
||||
};
|
||||
@@ -194,298 +197,115 @@ const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// "qtmux ! filesink name=sink";
|
||||
|
||||
|
||||
VideoRecorder::VideoRecorder() : Recorder(), frame_buffer_(nullptr), width_(0), height_(0),
|
||||
recording_(false), accept_buffer_(false), pipeline_(nullptr), src_(nullptr), timestamp_(0)
|
||||
VideoRecorder::VideoRecorder() : FrameGrabber()
|
||||
{
|
||||
|
||||
// configure fix parameter
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
|
||||
timeframe_ = 2 * frame_duration_;
|
||||
}
|
||||
|
||||
VideoRecorder::~VideoRecorder()
|
||||
void VideoRecorder::init(GstCaps *caps)
|
||||
{
|
||||
if (src_ != nullptr)
|
||||
gst_object_unref (src_);
|
||||
if (pipeline_ != nullptr) {
|
||||
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline_);
|
||||
}
|
||||
|
||||
glDeleteBuffers(2, pbo_);
|
||||
}
|
||||
|
||||
void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
{
|
||||
// TODO : avoid software videoconvert by using a GPU shader to produce Y444 frames
|
||||
|
||||
// ignore
|
||||
if (frame_buffer == nullptr)
|
||||
if (caps == nullptr)
|
||||
return;
|
||||
|
||||
// first frame for initialization
|
||||
if (frame_buffer_ == nullptr) {
|
||||
// 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];
|
||||
|
||||
// set frame buffer as input
|
||||
frame_buffer_ = frame_buffer;
|
||||
// verify location path (path is always terminated by the OS dependent separator)
|
||||
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
|
||||
if (path.empty())
|
||||
path = SystemToolkit::home_path();
|
||||
|
||||
// define stream properties
|
||||
width_ = frame_buffer_->width();
|
||||
height_ = frame_buffer_->height();
|
||||
size_ = width_ * height_ * (frame_buffer_->use_alpha() ? 4 : 3);
|
||||
// setup filename & muxer
|
||||
if( Settings::application.record.profile == JPEG_MULTI) {
|
||||
std::string folder = path + "vimix_" + SystemToolkit::date_time_string();
|
||||
filename_ = SystemToolkit::full_filename(folder, "%05d.jpg");
|
||||
if (SystemToolkit::create_directory(folder))
|
||||
description += "multifilesink name=sink";
|
||||
}
|
||||
else if( Settings::application.record.profile == VP8) {
|
||||
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".webm";
|
||||
description += "webmmux ! filesink name=sink";
|
||||
}
|
||||
else {
|
||||
filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".mov";
|
||||
description += "qtmux ! filesink name=sink";
|
||||
}
|
||||
|
||||
// create PBOs
|
||||
glGenBuffers(2, pbo_);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[1]);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[0]);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ);
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("VideoRecorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// create a gstreamer pipeline
|
||||
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];
|
||||
// setup file sink
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"location", filename_.c_str(),
|
||||
"sync", FALSE,
|
||||
NULL);
|
||||
|
||||
// verify location path (path is always terminated by the OS dependent separator)
|
||||
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
|
||||
if (path.empty())
|
||||
path = SystemToolkit::home_path();
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
|
||||
// setup filename & muxer
|
||||
if( Settings::application.record.profile == JPEG_MULTI) {
|
||||
std::string folder = path + SystemToolkit::date_time_string() + "_vimix_jpg";
|
||||
filename_ = SystemToolkit::full_filename(folder, "%05d.jpg");
|
||||
if (SystemToolkit::create_directory(folder))
|
||||
description += "multifilesink name=sink";
|
||||
}
|
||||
else if( Settings::application.record.profile == VP8) {
|
||||
filename_ = path + SystemToolkit::date_time_string() + "_vimix.webm";
|
||||
description += "webmmux ! filesink name=sink";
|
||||
}
|
||||
else {
|
||||
filename_ = path + SystemToolkit::date_time_string() + "_vimix.mov";
|
||||
description += "qtmux ! filesink name=sink";
|
||||
}
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
// "do-timestamp", TRUE,
|
||||
NULL);
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("VideoRecorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
|
||||
// setup file sink
|
||||
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
||||
"location", filename_.c_str(),
|
||||
"sync", FALSE,
|
||||
NULL);
|
||||
// instruct src to use the required caps
|
||||
caps_ = gst_caps_copy( caps );
|
||||
gst_app_src_set_caps (src_, caps_);
|
||||
|
||||
// setup custom app source
|
||||
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
||||
if (src_) {
|
||||
// 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);
|
||||
|
||||
g_object_set (G_OBJECT (src_),
|
||||
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
||||
"is-live", TRUE,
|
||||
"format", GST_FORMAT_TIME,
|
||||
// "do-timestamp", TRUE,
|
||||
NULL);
|
||||
}
|
||||
else {
|
||||
Log::Warning("VideoRecorder Could not configure source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Direct encoding (no buffering)
|
||||
gst_app_src_set_max_bytes( src_, 0 );
|
||||
// gst_app_src_set_max_bytes( src_, 2 * buf_size_);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// instruct src to use the required caps
|
||||
GstCaps *caps = gst_caps_new_simple ("video/x-raw",
|
||||
"format", G_TYPE_STRING, frame_buffer_->use_alpha() ? "RGBA" : "RGB",
|
||||
"width", G_TYPE_INT, width_,
|
||||
"height", G_TYPE_INT, height_,
|
||||
"framerate", GST_TYPE_FRACTION, 30, 1,
|
||||
NULL);
|
||||
gst_app_src_set_caps (src_, caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
// setup callbacks
|
||||
GstAppSrcCallbacks callbacks;
|
||||
callbacks.need_data = callback_need_data;
|
||||
callbacks.enough_data = callback_enough_data;
|
||||
callbacks.seek_data = NULL; // stream type is not seekable
|
||||
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("VideoRecorder Could not configure capture source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// start recording
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("VideoRecorder Could not record %s", filename_.c_str());
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// all good
|
||||
Log::Info("VideoRecorder start recording (%s %d x %d)", profile_name[Settings::application.record.profile], width_, height_);
|
||||
|
||||
// start recording !!
|
||||
recording_ = true;
|
||||
}
|
||||
// frame buffer changed ?
|
||||
else if (frame_buffer_ != frame_buffer) {
|
||||
|
||||
// if an incompatilble frame buffer given: stop recorder
|
||||
if ( frame_buffer->width() != width_ ||
|
||||
frame_buffer->height() != height_ ||
|
||||
frame_buffer->use_alpha() != frame_buffer_->use_alpha()) {
|
||||
|
||||
stop();
|
||||
Log::Warning("Recording interrupted: new session (%d x %d) incompatible with recording (%d x %d)", frame_buffer->width(), frame_buffer->height(), width_, height_);
|
||||
}
|
||||
else {
|
||||
// accepting a new frame buffer as input
|
||||
frame_buffer_ = frame_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// store a frame if recording is active
|
||||
if (recording_ && size_ > 0)
|
||||
{
|
||||
// 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 buffer target for writing in a new frame
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_index_]);
|
||||
|
||||
#ifdef USE_GLREADPIXEL
|
||||
// get frame
|
||||
frame_buffer->readPixels();
|
||||
#else
|
||||
glBindTexture(GL_TEXTURE_2D, frame_buffer->texture());
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
|
||||
// update case ; alternating indices
|
||||
if ( pbo_next_index_ != pbo_index_ ) {
|
||||
|
||||
// set buffer target for saving the frame
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_[pbo_next_index_]);
|
||||
|
||||
// new buffer
|
||||
GstBuffer *buffer = gst_buffer_new_and_alloc (size_);
|
||||
|
||||
// set timing of buffer
|
||||
buffer->pts = timestamp_;
|
||||
buffer->duration = frame_duration_;
|
||||
|
||||
// map gst buffer into a memory WRITE target
|
||||
GstMapInfo map;
|
||||
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
|
||||
|
||||
// map PBO pixels into a memory READ pointer
|
||||
unsigned char* ptr = (unsigned char*) glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
|
||||
// transfer pixels from PBO memory to buffer memory
|
||||
if (NULL != ptr)
|
||||
memmove(map.data, ptr, size_);
|
||||
|
||||
// un-map
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
|
||||
// push
|
||||
// Log::Info("VideoRecorder push data %ld", buffer->pts);
|
||||
gst_app_src_push_buffer (src_, buffer);
|
||||
// NB: buffer will be unrefed by the appsrc
|
||||
|
||||
// next timestamp
|
||||
timestamp_ += frame_duration_;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
// alternate indices
|
||||
pbo_next_index_ = pbo_index_;
|
||||
pbo_index_ = (pbo_index_ + 1) % 2;
|
||||
|
||||
// restart frame counter
|
||||
timeframe_ = 0;
|
||||
}
|
||||
|
||||
}
|
||||
// did the recording terminate with sink receiving end-of-stream ?
|
||||
else
|
||||
{
|
||||
// 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));
|
||||
|
||||
if (msg) {
|
||||
// Log::Info("received EOS");
|
||||
// stop the pipeline
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
Log::Warning("VideoRecorder Could not stop");
|
||||
else
|
||||
Log::Notify("Recording %s ready.", filename_.c_str());
|
||||
|
||||
finished_ = true;
|
||||
}
|
||||
}
|
||||
// all good
|
||||
Log::Info("Video Recording started (%s)", profile_name[Settings::application.record.profile]);
|
||||
|
||||
// start recording !!
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
void VideoRecorder::stop ()
|
||||
void VideoRecorder::terminate()
|
||||
{
|
||||
// send end of stream
|
||||
gst_app_src_end_of_stream (src_);
|
||||
// Log::Info("VideoRecorder push EOS");
|
||||
|
||||
// stop recording
|
||||
recording_ = false;
|
||||
Log::Notify("Video Recording %s is ready.", filename_.c_str());
|
||||
}
|
||||
|
||||
std::string VideoRecorder::info()
|
||||
std::string VideoRecorder::info() const
|
||||
{
|
||||
if (recording_)
|
||||
if (active_)
|
||||
return GstToolkit::time_to_string(timestamp_);
|
||||
else
|
||||
return "Saving file...";
|
||||
}
|
||||
|
||||
|
||||
double VideoRecorder::duration()
|
||||
{
|
||||
return gst_guint64_to_gdouble( GST_TIME_AS_MSECONDS(timestamp_) ) / 1000.0;
|
||||
}
|
||||
|
||||
// appsrc needs data and we should start sending
|
||||
void VideoRecorder::callback_need_data (GstAppSrc *, guint , gpointer p)
|
||||
{
|
||||
// Log::Info("H264Recording callback_need_data");
|
||||
VideoRecorder *rec = (VideoRecorder *)p;
|
||||
if (rec) {
|
||||
rec->accept_buffer_ = rec->recording_ ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
// appsrc has enough data and we can stop sending
|
||||
void VideoRecorder::callback_enough_data (GstAppSrc *, gpointer p)
|
||||
{
|
||||
// Log::Info("H264Recording callback_enough_data");
|
||||
VideoRecorder *rec = (VideoRecorder *)p;
|
||||
if (rec) {
|
||||
rec->accept_buffer_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
72
Recorder.h
72
Recorder.h
@@ -1,78 +1,36 @@
|
||||
#ifndef RECORDER_H
|
||||
#define RECORDER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
class FrameBuffer;
|
||||
#include "FrameGrabber.h"
|
||||
|
||||
/**
|
||||
* @brief The Recorder class defines the base class for all recorders
|
||||
* used to save images or videos from a frame buffer.
|
||||
*
|
||||
* The Mixer class calls addFrame() at each newly rendered frame for all of its recorder.
|
||||
*/
|
||||
class Recorder
|
||||
{
|
||||
public:
|
||||
Recorder();
|
||||
virtual ~Recorder() {}
|
||||
|
||||
virtual void addFrame(FrameBuffer *frame_buffer, float dt) = 0;
|
||||
virtual void stop() { }
|
||||
virtual std::string info() { return ""; }
|
||||
virtual double duration() { return 0.0; }
|
||||
|
||||
inline bool finished() const { return finished_; }
|
||||
|
||||
protected:
|
||||
// thread-safe testing termination
|
||||
std::atomic<bool> finished_;
|
||||
|
||||
// PBO
|
||||
guint pbo_[2];
|
||||
guint pbo_index_, pbo_next_index_;
|
||||
guint size_;
|
||||
};
|
||||
|
||||
class PNGRecorder : public Recorder
|
||||
class PNGRecorder : public FrameGrabber
|
||||
{
|
||||
std::string filename_;
|
||||
|
||||
public:
|
||||
|
||||
PNGRecorder();
|
||||
void addFrame(FrameBuffer *frame_buffer, float) override;
|
||||
|
||||
protected:
|
||||
|
||||
void init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
void addFrame(GstBuffer *buffer, GstCaps *caps, float dt) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class VideoRecorder : public Recorder
|
||||
class VideoRecorder : public FrameGrabber
|
||||
{
|
||||
std::string filename_;
|
||||
|
||||
// Frame buffer information
|
||||
FrameBuffer *frame_buffer_;
|
||||
uint width_;
|
||||
uint height_;
|
||||
|
||||
// operation
|
||||
std::atomic<bool> recording_;
|
||||
std::atomic<bool> accept_buffer_;
|
||||
|
||||
// gstreamer pipeline
|
||||
GstElement *pipeline_;
|
||||
GstAppSrc *src_;
|
||||
GstClockTime timeframe_;
|
||||
GstClockTime timestamp_;
|
||||
GstClockTime frame_duration_;
|
||||
|
||||
static void callback_need_data (GstAppSrc *, guint, gpointer user_data);
|
||||
static void callback_enough_data (GstAppSrc *, gpointer user_data);
|
||||
void init(GstCaps *caps) override;
|
||||
void terminate() override;
|
||||
|
||||
public:
|
||||
|
||||
@@ -91,13 +49,7 @@ public:
|
||||
static const std::vector<std::string> profile_description;
|
||||
|
||||
VideoRecorder();
|
||||
~VideoRecorder();
|
||||
|
||||
void addFrame(FrameBuffer *frame_buffer, float dt) override;
|
||||
void stop() override;
|
||||
std::string info() override;
|
||||
|
||||
double duration() override;
|
||||
std::string info() const override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
84
RenderView.cpp
Normal file
84
RenderView.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
// Opengl
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "defines.h"
|
||||
#include "Settings.h"
|
||||
#include "Decorations.h"
|
||||
|
||||
#include "RenderView.h"
|
||||
|
||||
|
||||
RenderView::RenderView() : View(RENDERING), frame_buffer_(nullptr), fading_overlay_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
RenderView::~RenderView()
|
||||
{
|
||||
if (frame_buffer_)
|
||||
delete frame_buffer_;
|
||||
if (fading_overlay_)
|
||||
delete fading_overlay_;
|
||||
}
|
||||
|
||||
bool RenderView::canSelect(Source *s) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void RenderView::setFading(float f)
|
||||
{
|
||||
if (fading_overlay_ == nullptr)
|
||||
fading_overlay_ = new Surface;
|
||||
|
||||
fading_overlay_->shader()->color.a = CLAMP( f < EPSILON ? 0.f : f, 0.f, 1.f);
|
||||
}
|
||||
|
||||
float RenderView::fading() const
|
||||
{
|
||||
if (fading_overlay_)
|
||||
return fading_overlay_->shader()->color.a;
|
||||
else
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
void RenderView::setResolution(glm::vec3 resolution, bool useAlpha)
|
||||
{
|
||||
// use default resolution if invalid resolution is given (default behavior)
|
||||
if (resolution.x < 2.f || resolution.y < 2.f)
|
||||
resolution = FrameBuffer::getResolutionFromParameters(Settings::application.render.ratio, Settings::application.render.res);
|
||||
|
||||
// do we need to change resolution ?
|
||||
if (frame_buffer_ && frame_buffer_->resolution() != resolution) {
|
||||
|
||||
// new frame buffer
|
||||
delete frame_buffer_;
|
||||
frame_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
if (!frame_buffer_)
|
||||
// output frame is an RBG Multisamples FrameBuffer
|
||||
frame_buffer_ = new FrameBuffer(resolution, useAlpha, true);
|
||||
|
||||
// reset fading
|
||||
setFading();
|
||||
}
|
||||
|
||||
void RenderView::draw()
|
||||
{
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -SCENE_DEPTH, 1.f);
|
||||
|
||||
if (frame_buffer_) {
|
||||
// draw in frame buffer
|
||||
glm::mat4 P = glm::scale( projection, glm::vec3(1.f / frame_buffer_->aspectRatio(), 1.f, 1.f));
|
||||
|
||||
// render the scene normally (pre-multiplied alpha in RGB)
|
||||
frame_buffer_->begin();
|
||||
scene.root()->draw(glm::identity<glm::mat4>(), P);
|
||||
fading_overlay_->draw(glm::identity<glm::mat4>(), projection);
|
||||
frame_buffer_->end();
|
||||
}
|
||||
}
|
||||
27
RenderView.h
Normal file
27
RenderView.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef RENDERVIEW_H
|
||||
#define RENDERVIEW_H
|
||||
|
||||
#include "View.h"
|
||||
|
||||
class RenderView : public View
|
||||
{
|
||||
FrameBuffer *frame_buffer_;
|
||||
Surface *fading_overlay_;
|
||||
|
||||
public:
|
||||
RenderView ();
|
||||
~RenderView ();
|
||||
|
||||
void draw () override;
|
||||
bool canSelect(Source *) override;
|
||||
|
||||
void setResolution (glm::vec3 resolution = glm::vec3(0.f), bool useAlpha = false);
|
||||
glm::vec3 resolution() const { return frame_buffer_->resolution(); }
|
||||
|
||||
void setFading(float f = 0.f);
|
||||
float fading() const;
|
||||
|
||||
inline FrameBuffer *frame () const { return frame_buffer_; }
|
||||
};
|
||||
|
||||
#endif // RENDERVIEW_H
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <gst/gl/gstglcontext.h>
|
||||
|
||||
#ifdef GLFW_EXPOSE_NATIVE_COCOA
|
||||
#include <gst/gl/cocoa/gstgldisplay_cocoa.h>
|
||||
//#include <gst/gl/cocoa/gstgldisplay_cocoa.h>
|
||||
#endif
|
||||
#ifdef GLFW_EXPOSE_NATIVE_GLX
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "Primitives.h"
|
||||
#include "Mixer.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "RenderingManager.h"
|
||||
|
||||
@@ -67,7 +68,7 @@ static void WindowRefreshCallback( GLFWwindow * )
|
||||
|
||||
static void WindowResizeCallback( GLFWwindow *w, int width, int height)
|
||||
{
|
||||
int id = GLFW_window_[w]->id();
|
||||
int id = GLFW_window_[w]->index();
|
||||
if (!Settings::application.windows[id].fullscreen) {
|
||||
Settings::application.windows[id].w = width;
|
||||
Settings::application.windows[id].h = height;
|
||||
@@ -76,28 +77,27 @@ static void WindowResizeCallback( GLFWwindow *w, int width, int height)
|
||||
|
||||
static void WindowMoveCallback( GLFWwindow *w, int x, int y)
|
||||
{
|
||||
int id = GLFW_window_[w]->id();
|
||||
int id = GLFW_window_[w]->index();
|
||||
if (!Settings::application.windows[id].fullscreen) {
|
||||
Settings::application.windows[id].x = x;
|
||||
Settings::application.windows[id].y = y;
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowEscapeFullscreen( GLFWwindow *w, int key, int scancode, int action, int)
|
||||
static void WindowEscapeFullscreen( GLFWwindow *w, int key, int, int action, int)
|
||||
{
|
||||
if (action == GLFW_PRESS && key == GLFW_KEY_ESCAPE)
|
||||
{
|
||||
// escape fullscreen
|
||||
GLFW_window_[w]->setFullscreen(nullptr);
|
||||
GLFW_window_[w]->exitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
static void WindowToggleFullscreen( GLFWwindow *w, int button, int action, int)
|
||||
{
|
||||
static double seconds = 0.f;
|
||||
|
||||
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
|
||||
{
|
||||
static double seconds = 0.f;
|
||||
// detect double clic
|
||||
if ( glfwGetTime() - seconds < 0.2f ) {
|
||||
// toggle fullscreen
|
||||
@@ -156,6 +156,18 @@ bool Rendering::init()
|
||||
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("Fond the following GPU decoding plugin(s):");
|
||||
for(auto it = gpuplugins.begin(); it != gpuplugins.end(); it++)
|
||||
Log::Info(" - %s", (*it).c_str());
|
||||
}
|
||||
else {
|
||||
Log::Info("No GPU decoding plugin found.");
|
||||
}
|
||||
}
|
||||
|
||||
//#if GST_GL_HAVE_PLATFORM_WGL
|
||||
// global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
|
||||
@@ -219,6 +231,7 @@ void Rendering::pushBackDrawCallback(RenderingCallback function)
|
||||
|
||||
void Rendering::draw()
|
||||
{
|
||||
// guint64 _time = gst_util_get_timestamp ();
|
||||
|
||||
// operate on main window context
|
||||
main_.makeCurrent();
|
||||
@@ -228,7 +241,7 @@ void Rendering::draw()
|
||||
|
||||
// Custom 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)();
|
||||
}
|
||||
@@ -257,8 +270,24 @@ 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_();
|
||||
|
||||
#ifndef USE_GST_APPSINK_CALLBACKS
|
||||
// no g_main_loop_run(loop) : update global GMainContext
|
||||
g_main_context_iteration(NULL, FALSE);
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -394,12 +423,12 @@ WindowSurface::WindowSurface(Shader *s) : Primitive(s)
|
||||
|
||||
|
||||
RenderingWindow::RenderingWindow() : window_(nullptr), master_(nullptr),
|
||||
id_(-1), dpi_scale_(1.f), textureid_(0), fbo_(0), surface_(nullptr)
|
||||
index_(-1), dpi_scale_(1.f), textureid_(0), fbo_(0), surface_(nullptr), request_toggle_fullscreen_(false)
|
||||
{
|
||||
}
|
||||
|
||||
RenderingWindow::~RenderingWindow()
|
||||
{
|
||||
{
|
||||
if (surface_ != nullptr)
|
||||
delete surface_;
|
||||
if (fbo_ != 0)
|
||||
@@ -408,7 +437,7 @@ RenderingWindow::~RenderingWindow()
|
||||
|
||||
void RenderingWindow::setTitle(const std::string &title)
|
||||
{
|
||||
std::string fulltitle = Settings::application.windows[id_].name;
|
||||
std::string fulltitle = Settings::application.windows[index_].name;
|
||||
if ( !title.empty() )
|
||||
fulltitle += " -- " + title;
|
||||
|
||||
@@ -429,8 +458,8 @@ void RenderingWindow::setIcon(const std::string &resource)
|
||||
|
||||
bool RenderingWindow::isFullscreen ()
|
||||
{
|
||||
return (glfwGetWindowMonitor(window_) != nullptr);
|
||||
// return Settings::application.windows[id_].fullscreen;
|
||||
// return (glfwGetWindowMonitor(window_) != nullptr);
|
||||
return Settings::application.windows[index_].fullscreen;
|
||||
}
|
||||
|
||||
GLFWmonitor *RenderingWindow::monitorAt(int x, int y)
|
||||
@@ -448,7 +477,14 @@ GLFWmonitor *RenderingWindow::monitorAt(int x, int y)
|
||||
int i = 0;
|
||||
for (; i < count_monitors; i++) {
|
||||
int workarea_x, workarea_y, workarea_width, workarea_height;
|
||||
#if GLFW_VERSION_MINOR > 2
|
||||
glfwGetMonitorWorkarea(monitors[i], &workarea_x, &workarea_y, &workarea_width, &workarea_height);
|
||||
#else
|
||||
glfwGetMonitorPos(monitors[i], &workarea_x, &workarea_y);
|
||||
const GLFWvidmode *vm = glfwGetVideoMode(monitors[i]);
|
||||
workarea_width = vm->width;
|
||||
workarea_height = vm->height;
|
||||
#endif
|
||||
if ( x >= workarea_x && x <= workarea_x + workarea_width &&
|
||||
y >= workarea_y && y <= workarea_y + workarea_height)
|
||||
break;
|
||||
@@ -491,45 +527,72 @@ GLFWmonitor *RenderingWindow::monitor()
|
||||
// pick at the coordinates given or at pos of window
|
||||
int x, y;
|
||||
glfwGetWindowPos(window_, &x, &y);
|
||||
|
||||
return monitorAt(x, y);
|
||||
}
|
||||
|
||||
void RenderingWindow::setFullscreen(GLFWmonitor *mo)
|
||||
void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
|
||||
{
|
||||
// done request
|
||||
request_toggle_fullscreen_ = false;
|
||||
|
||||
// if in fullscreen mode
|
||||
if (mo == nullptr) {
|
||||
// store fullscreen mode
|
||||
Settings::application.windows[index_].fullscreen = false;
|
||||
|
||||
// set to window mode
|
||||
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||
glfwSetWindowMonitor( window_, nullptr, Settings::application.windows[id_].x,
|
||||
Settings::application.windows[id_].y,
|
||||
Settings::application.windows[id_].w,
|
||||
Settings::application.windows[id_].h, 0 );
|
||||
Settings::application.windows[id_].fullscreen = false;
|
||||
glfwSetWindowMonitor( window_, nullptr, Settings::application.windows[index_].x,
|
||||
Settings::application.windows[index_].y,
|
||||
Settings::application.windows[index_].w,
|
||||
Settings::application.windows[index_].h, 0 );
|
||||
}
|
||||
// not in fullscreen mode
|
||||
else {
|
||||
// set to fullscreen mode
|
||||
Settings::application.windows[id_].fullscreen = true;
|
||||
Settings::application.windows[id_].monitor = glfwGetMonitorName(mo);
|
||||
// store fullscreen mode
|
||||
Settings::application.windows[index_].fullscreen = true;
|
||||
Settings::application.windows[index_].monitor = glfwGetMonitorName(mo);
|
||||
|
||||
// set to fullscreen mode
|
||||
const GLFWvidmode * mode = glfwGetVideoMode(mo);
|
||||
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
||||
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||
|
||||
// Enable vsync on output window only (i.e. not 0 if has a master)
|
||||
// Workaround for disabled vsync in fullscreen (https://github.com/glfw/glfw/issues/1072)
|
||||
glfwSwapInterval( nullptr == master_ ? 0 : Settings::application.render.vsync);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void RenderingWindow::exitFullscreen()
|
||||
{
|
||||
if (isFullscreen()) {
|
||||
// exit fullscreen
|
||||
request_toggle_fullscreen_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderingWindow::toggleFullscreen()
|
||||
{
|
||||
// if in fullscreen mode
|
||||
if (isFullscreen()) {
|
||||
// exit fullscreen
|
||||
setFullscreen(nullptr);
|
||||
}
|
||||
// not in fullscreen mode
|
||||
else {
|
||||
// enter fullscreen in monitor where the window is
|
||||
setFullscreen(monitor());
|
||||
request_toggle_fullscreen_ = true;
|
||||
}
|
||||
|
||||
void RenderingWindow::toggleFullscreen_()
|
||||
{
|
||||
if (request_toggle_fullscreen_) {
|
||||
|
||||
// if in fullscreen mode
|
||||
if (glfwGetWindowMonitor(window_) != nullptr) {
|
||||
// exit fullscreen
|
||||
setFullscreen_(nullptr);
|
||||
}
|
||||
// not in fullscreen mode
|
||||
else {
|
||||
// enter fullscreen in monitor where the window is
|
||||
setFullscreen_(monitor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,12 +606,21 @@ int RenderingWindow::height()
|
||||
return window_attributes_.viewport.y;
|
||||
}
|
||||
|
||||
int RenderingWindow::maxHeight()
|
||||
int RenderingWindow::pixelsforRealHeight(float milimeters)
|
||||
{
|
||||
int workarea_x, workarea_y, workarea_width, workarea_height;
|
||||
glfwGetMonitorWorkarea(monitor(), &workarea_x, &workarea_y, &workarea_width, &workarea_height);
|
||||
GLFWmonitor *mo = monitor();
|
||||
|
||||
return workarea_height * dpi_scale_;
|
||||
int mm_w = 0;
|
||||
int mm_h = 0;
|
||||
glfwGetMonitorPhysicalSize(mo, &mm_w, &mm_h);
|
||||
|
||||
float pixels = milimeters;
|
||||
if (mm_h > 0)
|
||||
pixels *= static_cast<float>(glfwGetVideoMode(mo)->height) / static_cast<float>(mm_h);
|
||||
else
|
||||
pixels *= 5; // something reasonnable if monitor's physical size is unknown
|
||||
|
||||
return static_cast<int>( round(pixels) );
|
||||
}
|
||||
|
||||
float RenderingWindow::aspectRatio()
|
||||
@@ -556,13 +628,13 @@ float RenderingWindow::aspectRatio()
|
||||
return static_cast<float>(window_attributes_.viewport.x) / static_cast<float>(window_attributes_.viewport.y);
|
||||
}
|
||||
|
||||
bool RenderingWindow::init(int id, GLFWwindow *share)
|
||||
bool RenderingWindow::init(int index, GLFWwindow *share)
|
||||
{
|
||||
id_ = id;
|
||||
index_ = index;
|
||||
master_ = share;
|
||||
|
||||
// access Settings
|
||||
Settings::WindowConfig winset = Settings::application.windows[id_];
|
||||
Settings::WindowConfig winset = Settings::application.windows[index_];
|
||||
|
||||
// do not show at creation
|
||||
glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
|
||||
@@ -572,7 +644,7 @@ bool RenderingWindow::init(int id, GLFWwindow *share)
|
||||
// create the window normal
|
||||
window_ = glfwCreateWindow(winset.w, winset.h, winset.name.c_str(), NULL, master_);
|
||||
if (window_ == NULL){
|
||||
Log::Error("Failed to create GLFW Window %d", id_);
|
||||
Log::Error("Failed to create GLFW Window %d", index_);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -610,9 +682,11 @@ bool RenderingWindow::init(int id, GLFWwindow *share)
|
||||
|
||||
// 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 ) {
|
||||
@@ -626,7 +700,7 @@ bool RenderingWindow::init(int id, GLFWwindow *share)
|
||||
glfwMakeContextCurrent(master_);
|
||||
}
|
||||
else {
|
||||
// Disable vsync on main window
|
||||
// Disable vsync on main window
|
||||
glfwSwapInterval(0);
|
||||
// Enable Antialiasing multisampling
|
||||
if (Settings::application.render.multisampling > 0) {
|
||||
@@ -644,9 +718,9 @@ void RenderingWindow::show()
|
||||
{
|
||||
glfwShowWindow(window_);
|
||||
|
||||
if ( Settings::application.windows[id_].fullscreen ) {
|
||||
GLFWmonitor *mo = monitorNamed(Settings::application.windows[id_].monitor);
|
||||
setFullscreen(mo);
|
||||
if ( Settings::application.windows[index_].fullscreen ) {
|
||||
GLFWmonitor *mo = monitorNamed(Settings::application.windows[index_].monitor);
|
||||
setFullscreen_(mo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
|
||||
#include "Screenshot.h"
|
||||
|
||||
class GLFWmonitor;
|
||||
class GLFWwindow;
|
||||
|
||||
typedef struct GLFWmonitor GLFWmonitor;
|
||||
typedef struct GLFWwindow GLFWwindow;
|
||||
class FrameBuffer;
|
||||
|
||||
struct RenderingAttrib
|
||||
@@ -23,9 +24,11 @@ struct RenderingAttrib
|
||||
|
||||
class RenderingWindow
|
||||
{
|
||||
friend class Rendering;
|
||||
|
||||
GLFWwindow *window_, *master_;
|
||||
RenderingAttrib window_attributes_;
|
||||
int id_;
|
||||
int index_;
|
||||
float dpi_scale_;
|
||||
|
||||
// objects to render
|
||||
@@ -33,16 +36,19 @@ class RenderingWindow
|
||||
uint fbo_;
|
||||
class WindowSurface *surface_;
|
||||
|
||||
bool request_toggle_fullscreen_;
|
||||
void toggleFullscreen_ ();
|
||||
void setFullscreen_(GLFWmonitor *mo);
|
||||
|
||||
public:
|
||||
RenderingWindow();
|
||||
~RenderingWindow();
|
||||
|
||||
inline int id() const { return id_; }
|
||||
inline int index() const { return index_; }
|
||||
inline RenderingAttrib& attribs() { return window_attributes_; }
|
||||
inline GLFWwindow *window() const { return window_; }
|
||||
|
||||
bool init(int id, GLFWwindow *share = NULL);
|
||||
bool init(int index, GLFWwindow *share = NULL);
|
||||
void setIcon(const std::string &resource);
|
||||
void setTitle(const std::string &title = "");
|
||||
|
||||
@@ -57,7 +63,7 @@ public:
|
||||
|
||||
// fullscreen
|
||||
bool isFullscreen ();
|
||||
void setFullscreen(GLFWmonitor *mo);
|
||||
void exitFullscreen();
|
||||
void toggleFullscreen ();
|
||||
|
||||
// get width of rendering area
|
||||
@@ -66,8 +72,8 @@ public:
|
||||
int height();
|
||||
// get aspect ratio of rendering area
|
||||
float aspectRatio();
|
||||
// get total height available in monitor
|
||||
int maxHeight();
|
||||
// get number of pixels to render X milimeters in height
|
||||
int pixelsforRealHeight(float milimeters);
|
||||
|
||||
inline float dpiScale() const { return dpi_scale_; }
|
||||
|
||||
@@ -121,7 +127,7 @@ public:
|
||||
void popAttrib();
|
||||
RenderingAttrib currentAttrib();
|
||||
|
||||
// get hold on the main window
|
||||
// get hold on the windows
|
||||
inline RenderingWindow& mainWindow() { return main_; }
|
||||
inline RenderingWindow& outputWindow() { return output_; }
|
||||
|
||||
|
||||
47
Resource.cpp
47
Resource.cpp
@@ -10,11 +10,8 @@
|
||||
// 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>
|
||||
@@ -33,6 +30,10 @@ uint Resource::getTextureBlack()
|
||||
if (tex_index_black == 0) {
|
||||
glGenTextures(1, &tex_index_black);
|
||||
glBindTexture( GL_TEXTURE_2D, tex_index_black);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
unsigned char clearColor[4] = {0, 0, 0, 255};
|
||||
// texture with one black pixel
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
|
||||
@@ -50,6 +51,10 @@ uint Resource::getTextureWhite()
|
||||
if (tex_index_white == 0) {
|
||||
glGenTextures(1, &tex_index_white);
|
||||
glBindTexture( GL_TEXTURE_2D, tex_index_white);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
unsigned char clearColor[4] = {255, 255, 255, 255};
|
||||
// texture with one black pixel
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
|
||||
@@ -59,6 +64,27 @@ uint Resource::getTextureWhite()
|
||||
return tex_index_white;
|
||||
}
|
||||
|
||||
uint Resource::getTextureTransparent()
|
||||
{
|
||||
static uint tex_index_transparent = 0;
|
||||
|
||||
// generate texture (once)
|
||||
if (tex_index_transparent == 0) {
|
||||
glGenTextures(1, &tex_index_transparent);
|
||||
glBindTexture( GL_TEXTURE_2D, tex_index_transparent);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
unsigned char clearColor[4] = {0, 0, 0, 0};
|
||||
// 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);
|
||||
}
|
||||
|
||||
return tex_index_transparent;
|
||||
}
|
||||
|
||||
const char *Resource::getData(const std::string& path, size_t* out_file_size){
|
||||
|
||||
auto fs = cmrc::vmix::get_filesystem();
|
||||
@@ -73,7 +99,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());
|
||||
}
|
||||
|
||||
@@ -89,7 +115,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());
|
||||
}
|
||||
|
||||
@@ -180,11 +206,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;
|
||||
|
||||
@@ -248,6 +274,8 @@ uint Resource::getTextureImage(const std::string& path, float *aspect_ratio)
|
||||
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);
|
||||
|
||||
// free memory
|
||||
stbi_image_free(img);
|
||||
@@ -265,7 +293,6 @@ std::string Resource::listDirectory()
|
||||
{
|
||||
// enter icons directory
|
||||
auto fs = cmrc::vmix::get_filesystem();
|
||||
cmrc::file file;
|
||||
cmrc::directory_iterator it = fs.iterate_directory("");
|
||||
cmrc::directory_iterator itend = it.end();
|
||||
|
||||
|
||||
@@ -22,12 +22,15 @@ namespace Resource
|
||||
// Returns the OpenGL generated Texture index
|
||||
uint getTextureImage(const std::string& path, float *aspect_ratio = nullptr);
|
||||
|
||||
// Returns the OpenGL generated Texture index for an empty 1x1 black transparent pixel texture
|
||||
// Returns the OpenGL generated Texture index for an empty 1x1 black opaque pixel texture
|
||||
uint getTextureBlack();
|
||||
|
||||
// Returns the OpenGL generated Texture index for an empty 1x1 white opaque pixel texture
|
||||
uint getTextureWhite();
|
||||
|
||||
// Returns the OpenGL generated Texture index for an empty 1x1 back transparent pixel texture
|
||||
uint getTextureTransparent();
|
||||
|
||||
// Generic access to pointer to data
|
||||
const char *getData(const std::string& path, size_t* out_file_size);
|
||||
|
||||
|
||||
89
Scene.cpp
89
Scene.cpp
@@ -1,5 +1,13 @@
|
||||
#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 "Scene.h"
|
||||
#include "Shader.h"
|
||||
#include "Primitives.h"
|
||||
#include "Visitor.h"
|
||||
@@ -8,33 +16,33 @@
|
||||
#include "GlmToolkit.h"
|
||||
#include "SessionVisitor.h"
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include "Scene.h"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/random.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <algorithm>
|
||||
#define DEBUG_SCENE 0
|
||||
static int num_nodes_ = 0;
|
||||
|
||||
// Node
|
||||
Node::Node() : initialized_(false), visible_(true), refcount_(0)
|
||||
{
|
||||
// create unique id
|
||||
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
|
||||
id_ = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 100000000;
|
||||
id_ = GlmToolkit::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()
|
||||
@@ -52,10 +60,11 @@ void Node::copyTransform(Node *other)
|
||||
{
|
||||
if (!other)
|
||||
return;
|
||||
transform_ = glm::identity<glm::mat4>();
|
||||
transform_ = other->transform_;
|
||||
scale_ = other->scale_;
|
||||
rotation_ = other->rotation_;
|
||||
translation_ = other->translation_;
|
||||
crop_ = other->crop_;
|
||||
}
|
||||
|
||||
void Node::update( float dt)
|
||||
@@ -73,7 +82,7 @@ void Node::update( float dt)
|
||||
delete callback;
|
||||
}
|
||||
else {
|
||||
iter++;
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,14 +121,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 )
|
||||
@@ -127,7 +136,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)
|
||||
@@ -199,9 +208,16 @@ void Primitive::accept(Visitor& v)
|
||||
void Primitive::replaceShader( Shader *newshader )
|
||||
{
|
||||
if (newshader) {
|
||||
if (shader_)
|
||||
glm::mat4 iTransform = newshader->iTransform;
|
||||
glm::vec4 color = newshader->color;
|
||||
if (shader_) {
|
||||
iTransform = shader_->iTransform;
|
||||
color = shader_->color;
|
||||
delete shader_;
|
||||
}
|
||||
shader_ = newshader;
|
||||
shader_->iTransform = iTransform;
|
||||
shader_->color = color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,8 +247,10 @@ void Group::clear()
|
||||
|
||||
void Group::attach(Node *child)
|
||||
{
|
||||
children_.insert(child);
|
||||
child->refcount_++;
|
||||
if (child != nullptr) {
|
||||
children_.insert(child);
|
||||
child->refcount_++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -245,17 +263,18 @@ void Group::sort()
|
||||
children_.swap(ordered_children);
|
||||
}
|
||||
|
||||
void Group::detatch(Node *child)
|
||||
void Group::detach(Node *child)
|
||||
{
|
||||
// find the node with this id, and erase it out of the list of children
|
||||
// NB: do NOT delete with remove : this takes all nodes with same depth (i.e. equal depth in set)
|
||||
NodeSet::iterator it = std::find_if(children_.begin(), children_.end(), hasId(child->id()));
|
||||
if ( it != children_.end()) {
|
||||
// detatch child from group parent
|
||||
children_.erase(it);
|
||||
child->refcount_--;
|
||||
if (child != nullptr) {
|
||||
// find the node with this id, and erase it out of the list of children
|
||||
// NB: do NOT delete with remove : this takes all nodes with same depth (i.e. equal depth in set)
|
||||
NodeSet::iterator it = std::find_if(children_.begin(), children_.end(), hasId(child->id()));
|
||||
if ( it != children_.end()) {
|
||||
// detatch child from group parent
|
||||
children_.erase(it);
|
||||
child->refcount_--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Group::update( float dt )
|
||||
@@ -264,7 +283,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 );
|
||||
}
|
||||
}
|
||||
@@ -281,7 +300,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 );
|
||||
}
|
||||
}
|
||||
@@ -375,13 +394,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;
|
||||
@@ -435,8 +454,12 @@ Scene::Scene()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
12
Scene.h
12
Scene.h
@@ -41,7 +41,7 @@ class Group;
|
||||
*/
|
||||
class Node {
|
||||
|
||||
int id_;
|
||||
uint64_t id_;
|
||||
bool initialized_;
|
||||
|
||||
public:
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
virtual ~Node ();
|
||||
|
||||
// unique identifyer generated at instanciation
|
||||
inline int id () const { return id_; }
|
||||
inline uint64_t id () const { return id_; }
|
||||
|
||||
// must initialize the node before draw
|
||||
virtual void init () { initialized_ = true; }
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
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
|
||||
@@ -140,9 +140,9 @@ struct hasId: public std::unary_function<Node*, bool>
|
||||
{
|
||||
return (e && e->id() == _id);
|
||||
}
|
||||
hasId(int id) : _id(id) { }
|
||||
hasId(uint64_t id) : _id(id) { }
|
||||
private:
|
||||
int _id;
|
||||
uint64_t _id;
|
||||
};
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ public:
|
||||
// container
|
||||
void clear();
|
||||
void attach (Node *child);
|
||||
void detatch (Node *child);
|
||||
void detach (Node *child);
|
||||
inline uint numChildren () const { return children_.size(); }
|
||||
|
||||
// Group specific access to its Nodes
|
||||
|
||||
@@ -24,8 +24,10 @@ Screenshot::Screenshot()
|
||||
|
||||
Screenshot::~Screenshot()
|
||||
{
|
||||
glDeleteBuffers(1, &Pbo);
|
||||
if (Data) free(Data);
|
||||
if (Pbo > 0)
|
||||
glDeleteBuffers(1, &Pbo);
|
||||
if (Data)
|
||||
free(Data);
|
||||
}
|
||||
|
||||
bool Screenshot::isFull()
|
||||
@@ -37,7 +39,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)
|
||||
@@ -56,7 +58,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
|
||||
@@ -83,6 +85,7 @@ void Screenshot::save(std::string filename)
|
||||
|
||||
// ready for next
|
||||
Pbo_full = false;
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -126,11 +129,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,11 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "SearchVisitor.h"
|
||||
|
||||
#include "Scene.h"
|
||||
#include "MediaSource.h"
|
||||
#include "Session.h"
|
||||
#include "SessionSource.h"
|
||||
|
||||
SearchVisitor::SearchVisitor(Node *node) : Visitor(), node_(node), found_(false)
|
||||
{
|
||||
@@ -20,7 +25,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 +44,65 @@ void SearchVisitor::visit(Scene &n)
|
||||
// search only in workspace
|
||||
n.ws()->accept(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
SearchFileVisitor::SearchFileVisitor() : Visitor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SearchFileVisitor::visit(Node &n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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,5 +1,9 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "defines.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "Source.h"
|
||||
|
||||
#include "Selection.h"
|
||||
|
||||
Selection::Selection()
|
||||
@@ -100,17 +104,34 @@ void Selection::clear()
|
||||
selection_.clear();
|
||||
}
|
||||
|
||||
uint Selection::size()
|
||||
uint Selection::size() const
|
||||
{
|
||||
return selection_.size();
|
||||
}
|
||||
|
||||
Source *Selection::front()
|
||||
{
|
||||
if (selection_.empty())
|
||||
return nullptr;
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -136,4 +157,14 @@ SourceList::iterator Selection::end()
|
||||
return selection_.end();
|
||||
}
|
||||
|
||||
std::string Selection::clipboard() const
|
||||
{
|
||||
return SessionVisitor::getClipboard(selection_);
|
||||
}
|
||||
|
||||
SourceList Selection::getCopy() const
|
||||
{
|
||||
SourceList dsl = selection_;
|
||||
return dsl;
|
||||
}
|
||||
|
||||
|
||||
22
Selection.h
22
Selection.h
@@ -1,7 +1,7 @@
|
||||
#ifndef SELECTION_H
|
||||
#define SELECTION_H
|
||||
|
||||
#include "Source.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
class Selection
|
||||
{
|
||||
@@ -9,22 +9,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();
|
||||
Source *back();
|
||||
|
||||
// properties
|
||||
bool contains (Source *s);
|
||||
bool empty();
|
||||
uint size ();
|
||||
bool empty() const;
|
||||
uint size () const;
|
||||
|
||||
// extract
|
||||
std::string clipboard() const;
|
||||
SourceList getCopy() const;
|
||||
|
||||
protected:
|
||||
SourceList::iterator find (Source *s);
|
||||
|
||||
359
Session.cpp
359
Session.cpp
@@ -4,15 +4,17 @@
|
||||
#include "Settings.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Session.h"
|
||||
#include "GarbageVisitor.h"
|
||||
#include "Recorder.h"
|
||||
#include "FrameGrabber.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "SessionSource.h"
|
||||
#include "MixingGroup.h"
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
Session::Session() : filename_(""), failedSource_(nullptr), active_(true), fading_target_(0.f)
|
||||
Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f), filename_("")
|
||||
{
|
||||
config_[View::RENDERING] = new Group;
|
||||
config_[View::RENDERING]->scale_ = render_.resolution();
|
||||
config_[View::RENDERING]->scale_ = glm::vec3(0.f);
|
||||
|
||||
config_[View::GEOMETRY] = new Group;
|
||||
config_[View::GEOMETRY]->scale_ = Settings::application.views[View::GEOMETRY].default_scale;
|
||||
@@ -25,19 +27,33 @@ Session::Session() : filename_(""), failedSource_(nullptr), active_(true), fadin
|
||||
config_[View::MIXING] = new Group;
|
||||
config_[View::MIXING]->scale_ = Settings::application.views[View::MIXING].default_scale;
|
||||
config_[View::MIXING]->translation_ = Settings::application.views[View::MIXING].default_translation;
|
||||
|
||||
config_[View::TEXTURE] = new Group;
|
||||
config_[View::TEXTURE]->scale_ = Settings::application.views[View::TEXTURE].default_scale;
|
||||
config_[View::TEXTURE]->translation_ = Settings::application.views[View::TEXTURE].default_translation;
|
||||
}
|
||||
|
||||
|
||||
Session::~Session()
|
||||
{
|
||||
// delete all recorders
|
||||
clearRecorders();
|
||||
// TODO delete all mixing groups?
|
||||
auto group_iter = mixing_groups_.begin();
|
||||
while ( group_iter != mixing_groups_.end() ){
|
||||
delete (*group_iter);
|
||||
group_iter = mixing_groups_.erase(group_iter);
|
||||
}
|
||||
|
||||
// delete all sources
|
||||
for(auto it = sources_.begin(); it != sources_.end(); ) {
|
||||
// erase this source from the list
|
||||
it = deleteSource(*it);
|
||||
}
|
||||
|
||||
delete config_[View::RENDERING];
|
||||
delete config_[View::GEOMETRY];
|
||||
delete config_[View::LAYER];
|
||||
delete config_[View::MIXING];
|
||||
delete config_[View::TEXTURE];
|
||||
}
|
||||
|
||||
void Session::setActive (bool on)
|
||||
@@ -53,10 +69,18 @@ void Session::setActive (bool on)
|
||||
// update all sources
|
||||
void Session::update(float dt)
|
||||
{
|
||||
failedSource_ = nullptr;
|
||||
// no update until render view is initialized
|
||||
if ( render_.frame() == nullptr )
|
||||
return;
|
||||
|
||||
// pre-render of all sources
|
||||
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); it++){
|
||||
failedSource_ = nullptr;
|
||||
for( SourceList::iterator it = sources_.begin(); it != sources_.end(); ++it){
|
||||
|
||||
// ensure the RenderSource is rendering this session
|
||||
RenderSource *s = dynamic_cast<RenderSource *>( *it );
|
||||
if ( s!= nullptr && s->session() != this )
|
||||
s->setSession(this);
|
||||
|
||||
if ( (*it)->failed() ) {
|
||||
failedSource_ = (*it);
|
||||
@@ -69,6 +93,19 @@ void Session::update(float dt)
|
||||
}
|
||||
}
|
||||
|
||||
// update session's mixing groups
|
||||
auto group_iter = mixing_groups_.begin();
|
||||
while ( group_iter != mixing_groups_.end() ){
|
||||
// update all valid groups
|
||||
if ((*group_iter)->size() > 1) {
|
||||
(*group_iter)->update(dt);
|
||||
group_iter++;
|
||||
}
|
||||
else
|
||||
// delete invalid groups (singletons)
|
||||
group_iter = deleteMixingGroup(group_iter);
|
||||
}
|
||||
|
||||
// apply fading (smooth dicotomic reaching)
|
||||
float f = render_.fading();
|
||||
if ( ABS_DIFF(f, fading_target_) > EPSILON) {
|
||||
@@ -81,56 +118,84 @@ void Session::update(float dt)
|
||||
// draw render view in Frame Buffer
|
||||
render_.draw();
|
||||
|
||||
// send frame to recorders
|
||||
std::list<Recorder *>::iterator iter;
|
||||
for (iter=recorders_.begin(); iter != recorders_.end(); )
|
||||
{
|
||||
Recorder *rec = *iter;
|
||||
|
||||
rec->addFrame(render_.frame(), dt);
|
||||
|
||||
if (rec->finished()) {
|
||||
iter = recorders_.erase(iter);
|
||||
delete rec;
|
||||
}
|
||||
else {
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SourceList::iterator Session::addSource(Source *s)
|
||||
{
|
||||
// insert the source in the rendering
|
||||
render_.scene.ws()->attach(s->group(View::RENDERING));
|
||||
// insert the source to the beginning of the list
|
||||
sources_.push_front(s);
|
||||
// return the iterator to the source created at the beginning
|
||||
return sources_.begin();
|
||||
// lock before change
|
||||
access_.lock();
|
||||
|
||||
// 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);
|
||||
// return the iterator to the source created at the beginning
|
||||
its = sources_.begin();
|
||||
}
|
||||
|
||||
// unlock access
|
||||
access_.unlock();
|
||||
|
||||
return its;
|
||||
}
|
||||
|
||||
SourceList::iterator Session::deleteSource(Source *s)
|
||||
{
|
||||
// lock before change
|
||||
access_.lock();
|
||||
|
||||
// find the source
|
||||
SourceList::iterator its = find(s);
|
||||
// ok, its in the list !
|
||||
if (its != sources_.end()) {
|
||||
|
||||
// remove Node from the rendering scene
|
||||
render_.scene.ws()->detatch( s->group(View::RENDERING) );
|
||||
|
||||
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
||||
// inform group
|
||||
if (s->mixingGroup() != nullptr)
|
||||
s->mixingGroup()->detach(s);
|
||||
// erase the source from the update list & get next element
|
||||
its = sources_.erase(its);
|
||||
|
||||
// delete the source : safe now
|
||||
delete s;
|
||||
}
|
||||
|
||||
// unlock access
|
||||
access_.unlock();
|
||||
|
||||
// return end of next element
|
||||
return its;
|
||||
}
|
||||
|
||||
|
||||
void Session::removeSource(Source *s)
|
||||
{
|
||||
// lock before change
|
||||
access_.lock();
|
||||
|
||||
// find the source
|
||||
SourceList::iterator its = find(s);
|
||||
// ok, its in the list !
|
||||
if (its != sources_.end()) {
|
||||
// remove Node from the rendering scene
|
||||
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
||||
// inform group
|
||||
if (s->mixingGroup() != nullptr)
|
||||
s->mixingGroup()->detach(s);
|
||||
// erase the source from the update list & get next element
|
||||
sources_.erase(its);
|
||||
}
|
||||
|
||||
// unlock access
|
||||
access_.unlock();
|
||||
}
|
||||
|
||||
|
||||
Source *Session::popSource()
|
||||
{
|
||||
Source *s = nullptr;
|
||||
@@ -139,10 +204,8 @@ Source *Session::popSource()
|
||||
if (its != sources_.end())
|
||||
{
|
||||
s = *its;
|
||||
|
||||
// remove Node from the rendering scene
|
||||
render_.scene.ws()->detatch( s->group(View::RENDERING) );
|
||||
|
||||
render_.scene.ws()->detach( s->group(View::RENDERING) );
|
||||
// erase the source from the update list & get next element
|
||||
sources_.erase(its);
|
||||
}
|
||||
@@ -150,10 +213,12 @@ Source *Session::popSource()
|
||||
return s;
|
||||
}
|
||||
|
||||
void Session::setResolution(glm::vec3 resolution)
|
||||
void Session::setResolution(glm::vec3 resolution, bool useAlpha)
|
||||
{
|
||||
render_.setResolution(resolution);
|
||||
config_[View::RENDERING]->scale_ = resolution;
|
||||
// setup the render view: if not specified the default config resulution will be used
|
||||
render_.setResolution( resolution, useAlpha );
|
||||
// store the actual resolution set in the render view
|
||||
config_[View::RENDERING]->scale_ = render_.resolution();
|
||||
}
|
||||
|
||||
void Session::setFading(float f, bool forcenow)
|
||||
@@ -174,25 +239,16 @@ SourceList::iterator Session::end()
|
||||
return sources_.end();
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(int index)
|
||||
{
|
||||
if (index<0)
|
||||
return sources_.end();
|
||||
|
||||
int i = 0;
|
||||
SourceList::iterator it = sources_.begin();
|
||||
while ( i < index && it != sources_.end() ){
|
||||
i++;
|
||||
it++;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(Source *s)
|
||||
{
|
||||
return std::find(sources_.begin(), sources_.end(), s);
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(uint64_t id)
|
||||
{
|
||||
return std::find_if(sources_.begin(), sources_.end(), Source::hasId(id));
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(std::string namesource)
|
||||
{
|
||||
return std::find_if(sources_.begin(), sources_.end(), Source::hasName(namesource));
|
||||
@@ -203,16 +259,45 @@ SourceList::iterator Session::find(Node *node)
|
||||
return std::find_if(sources_.begin(), sources_.end(), Source::hasNode(node));
|
||||
}
|
||||
|
||||
SourceList::iterator Session::find(float depth_from, float depth_to)
|
||||
{
|
||||
return std::find_if(sources_.begin(), sources_.end(), Source::hasDepth(depth_from, depth_to));
|
||||
}
|
||||
|
||||
SourceList Session::getDepthSortedList() const
|
||||
{
|
||||
return depth_sorted(sources_);
|
||||
}
|
||||
|
||||
uint Session::numSource() const
|
||||
{
|
||||
return sources_.size();
|
||||
}
|
||||
|
||||
SourceIdList Session::getIdList() const
|
||||
{
|
||||
return ids(sources_);
|
||||
}
|
||||
|
||||
bool Session::empty() const
|
||||
{
|
||||
return sources_.empty();
|
||||
}
|
||||
|
||||
SourceList::iterator Session::at(int index)
|
||||
{
|
||||
if (index<0)
|
||||
return sources_.end();
|
||||
|
||||
int i = 0;
|
||||
SourceList::iterator it = sources_.begin();
|
||||
while ( i < index && it != sources_.end() ){
|
||||
i++;
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
int Session::index(SourceList::iterator it) const
|
||||
{
|
||||
int index = -1;
|
||||
@@ -226,49 +311,135 @@ int Session::index(SourceList::iterator it) const
|
||||
return index;
|
||||
}
|
||||
|
||||
void Session::addRecorder(Recorder *rec)
|
||||
void Session::move(int current_index, int target_index)
|
||||
{
|
||||
recorders_.push_back(rec);
|
||||
}
|
||||
|
||||
|
||||
Recorder *Session::frontRecorder()
|
||||
{
|
||||
if (recorders_.empty())
|
||||
return nullptr;
|
||||
else
|
||||
return recorders_.front();
|
||||
}
|
||||
|
||||
void Session::stopRecorders()
|
||||
{
|
||||
std::list<Recorder *>::iterator iter;
|
||||
for (iter=recorders_.begin(); iter != recorders_.end(); )
|
||||
(*iter)->stop();
|
||||
}
|
||||
|
||||
void Session::clearRecorders()
|
||||
{
|
||||
std::list<Recorder *>::iterator iter;
|
||||
for (iter=recorders_.begin(); iter != recorders_.end(); )
|
||||
{
|
||||
Recorder *rec = *iter;
|
||||
rec->stop();
|
||||
iter = recorders_.erase(iter);
|
||||
delete rec;
|
||||
}
|
||||
}
|
||||
|
||||
void Session::transferRecorders(Session *dest)
|
||||
{
|
||||
if (dest == nullptr)
|
||||
if ( current_index < 0 || current_index > (int) sources_.size()
|
||||
|| target_index < 0 || target_index > (int) sources_.size()
|
||||
|| target_index == current_index )
|
||||
return;
|
||||
|
||||
std::list<Recorder *>::iterator iter;
|
||||
for (iter=recorders_.begin(); iter != recorders_.end(); )
|
||||
{
|
||||
dest->recorders_.push_back(*iter);
|
||||
iter = recorders_.erase(iter);
|
||||
SourceList::iterator from = at(current_index);
|
||||
SourceList::iterator to = at(target_index);
|
||||
if ( target_index > current_index )
|
||||
++to;
|
||||
|
||||
Source *s = (*from);
|
||||
sources_.erase(from);
|
||||
sources_.insert(to, s);
|
||||
}
|
||||
|
||||
bool Session::canlink (SourceList sources)
|
||||
{
|
||||
bool canlink = true;
|
||||
|
||||
// verify that all sources given are valid in the sesion
|
||||
validate(sources);
|
||||
|
||||
for (auto it = sources.begin(); it != sources.end(); it++) {
|
||||
// this source is linked
|
||||
if ( (*it)->mixingGroup() != nullptr ) {
|
||||
// askt its group to detach it
|
||||
canlink = false;
|
||||
}
|
||||
}
|
||||
|
||||
return canlink;
|
||||
}
|
||||
|
||||
void Session::link(SourceList sources, Group *parent)
|
||||
{
|
||||
// we need at least 2 sources to make a group
|
||||
if (sources.size() > 1) {
|
||||
|
||||
unlink(sources);
|
||||
|
||||
// create and add a new mixing group
|
||||
MixingGroup *g = new MixingGroup(sources);
|
||||
mixing_groups_.push_back(g);
|
||||
|
||||
// if provided, attach the group to the parent
|
||||
if (g && parent != nullptr)
|
||||
g->attachTo( parent );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Session::unlink (SourceList sources)
|
||||
{
|
||||
// verify that all sources given are valid in the sesion
|
||||
validate(sources);
|
||||
|
||||
// brute force : detach all given sources
|
||||
for (auto it = sources.begin(); it != sources.end(); it++) {
|
||||
// this source is linked
|
||||
if ( (*it)->mixingGroup() != nullptr ) {
|
||||
// askt its group to detach it
|
||||
(*it)->mixingGroup()->detach(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::list<SourceList> Session::getMixingGroups () const
|
||||
{
|
||||
std::list<SourceList> lmg;
|
||||
|
||||
for (auto group_it = mixing_groups_.begin(); group_it!= mixing_groups_.end(); group_it++)
|
||||
lmg.push_back( (*group_it)->getCopy() );
|
||||
|
||||
return lmg;
|
||||
}
|
||||
|
||||
|
||||
std::list<MixingGroup *>::iterator Session::deleteMixingGroup (std::list<MixingGroup *>::iterator g)
|
||||
{
|
||||
if (g != mixing_groups_.end()) {
|
||||
delete (*g);
|
||||
return mixing_groups_.erase(g);
|
||||
}
|
||||
return mixing_groups_.end();
|
||||
}
|
||||
|
||||
std::list<MixingGroup *>::iterator Session::beginMixingGroup()
|
||||
{
|
||||
return mixing_groups_.begin();
|
||||
}
|
||||
|
||||
std::list<MixingGroup *>::iterator Session::endMixingGroup()
|
||||
{
|
||||
return mixing_groups_.end();
|
||||
}
|
||||
|
||||
void Session::lock()
|
||||
{
|
||||
access_.lock();
|
||||
}
|
||||
|
||||
void Session::unlock()
|
||||
{
|
||||
access_.unlock();
|
||||
}
|
||||
|
||||
|
||||
void Session::validate (SourceList &sources)
|
||||
{
|
||||
// verify that all sources given are valid in the sesion
|
||||
// and remove the invalid sources
|
||||
for (auto _it = sources.begin(); _it != sources.end(); ) {
|
||||
SourceList::iterator found = std::find(sources_.begin(), sources_.end(), *_it);
|
||||
if ( found == sources_.end() )
|
||||
_it = sources.erase(_it);
|
||||
else
|
||||
_it++;
|
||||
}
|
||||
}
|
||||
|
||||
Session *Session::load(const std::string& filename, uint recursion)
|
||||
{
|
||||
// create session
|
||||
SessionCreator creator(recursion);
|
||||
creator.load(filename);
|
||||
|
||||
// return created session
|
||||
return creator.session();
|
||||
}
|
||||
|
||||
|
||||
68
Session.h
68
Session.h
@@ -1,11 +1,13 @@
|
||||
#ifndef SESSION_H
|
||||
#define SESSION_H
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "View.h"
|
||||
#include "RenderView.h"
|
||||
#include "Source.h"
|
||||
|
||||
class Recorder;
|
||||
class FrameGrabber;
|
||||
class MixingGroup;
|
||||
|
||||
class Session
|
||||
{
|
||||
@@ -13,12 +15,18 @@ public:
|
||||
Session();
|
||||
~Session();
|
||||
|
||||
static Session *load(const std::string& filename, uint recursion = 0);
|
||||
|
||||
// add given source into the session
|
||||
SourceList::iterator addSource (Source *s);
|
||||
|
||||
// delete the source s from the session
|
||||
SourceList::iterator deleteSource (Source *s);
|
||||
|
||||
// remove this source from the session
|
||||
// Does not delete the source
|
||||
void removeSource(Source *s);
|
||||
|
||||
// get ptr to front most source and remove it from the session
|
||||
// Does not delete the source
|
||||
Source *popSource();
|
||||
@@ -28,11 +36,18 @@ public:
|
||||
uint numSource() const;
|
||||
SourceList::iterator begin ();
|
||||
SourceList::iterator end ();
|
||||
SourceList::iterator find (int index);
|
||||
SourceList::iterator find (Source *s);
|
||||
SourceList::iterator find (std::string name);
|
||||
SourceList::iterator find (Node *node);
|
||||
int index (SourceList::iterator it) const;
|
||||
SourceList::iterator find (float depth_from, float depth_to);
|
||||
SourceList getDepthSortedList () const;
|
||||
|
||||
SourceList::iterator find (uint64_t id);
|
||||
SourceIdList getIdList() const;
|
||||
|
||||
SourceList::iterator at (int index);
|
||||
int index (SourceList::iterator it) const;
|
||||
void move (int current_index, int target_index);
|
||||
|
||||
// update all sources and mark sources which failed
|
||||
void update (float dt);
|
||||
@@ -42,41 +57,58 @@ public:
|
||||
inline bool active () { return active_; }
|
||||
|
||||
// return the last source which failed
|
||||
Source *failedSource() { return failedSource_; }
|
||||
Source *failedSource () { return failedSource_; }
|
||||
|
||||
// get frame result of render
|
||||
inline FrameBuffer *frame () const { return render_.frame(); }
|
||||
|
||||
// Recorders
|
||||
void addRecorder(Recorder *rec);
|
||||
Recorder *frontRecorder();
|
||||
void stopRecorders();
|
||||
void clearRecorders();
|
||||
void transferRecorders(Session *dest);
|
||||
|
||||
// configure rendering resolution
|
||||
void setResolution(glm::vec3 resolution);
|
||||
void setResolution (glm::vec3 resolution, bool useAlpha = false);
|
||||
|
||||
// manipulate fading of output
|
||||
void setFading(float f, bool forcenow = false);
|
||||
inline float fading() const { return fading_target_; }
|
||||
void setFading (float f, bool forcenow = false);
|
||||
inline float fading () const { return fading_target_; }
|
||||
|
||||
// configuration for group nodes of views
|
||||
inline Group *config (View::Mode m) const { return config_.at(m); }
|
||||
|
||||
// name of file containing this session (for transfer)
|
||||
void setFilename(const std::string &filename) { filename_ = filename; }
|
||||
std::string filename() const { return filename_; }
|
||||
void setFilename (const std::string &filename) { filename_ = filename; }
|
||||
std::string filename () const { return filename_; }
|
||||
|
||||
// get the list of sources in mixing groups
|
||||
std::list<SourceList> getMixingGroups () const;
|
||||
// returns true if something can be done to create a mixing group
|
||||
bool canlink (SourceList sources);
|
||||
// try to link sources of the given list:
|
||||
// can either create a new mixing group or extend an existing group
|
||||
void link (SourceList sources, Group *parent = nullptr);
|
||||
// try to unlink sources of the given list:
|
||||
// can either delete an entire mixing group, or remove the given sources from existing groups
|
||||
void unlink (SourceList sources);
|
||||
// iterators for looping over mixing groups
|
||||
std::list<MixingGroup *>::iterator beginMixingGroup ();
|
||||
std::list<MixingGroup *>::iterator endMixingGroup ();
|
||||
std::list<MixingGroup *>::iterator deleteMixingGroup (std::list<MixingGroup *>::iterator g);
|
||||
|
||||
// lock and unlock access (e.g. while saving)
|
||||
void lock ();
|
||||
void unlock ();
|
||||
|
||||
protected:
|
||||
RenderView render_;
|
||||
std::string filename_;
|
||||
Source *failedSource_;
|
||||
SourceList sources_;
|
||||
void validate(SourceList &sources);
|
||||
|
||||
std::list<MixingGroup *> mixing_groups_;
|
||||
std::map<View::Mode, Group*> config_;
|
||||
bool active_;
|
||||
std::list<Recorder *> recorders_;
|
||||
std::list<FrameGrabber *> grabbers_;
|
||||
float fading_target_;
|
||||
std::mutex access_;
|
||||
};
|
||||
|
||||
|
||||
#endif // SESSION_H
|
||||
|
||||
@@ -8,12 +8,18 @@
|
||||
#include "Source.h"
|
||||
#include "MediaSource.h"
|
||||
#include "SessionSource.h"
|
||||
#include "StreamSource.h"
|
||||
#include "PatternSource.h"
|
||||
#include "DeviceSource.h"
|
||||
#include "NetworkSource.h"
|
||||
#include "Session.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
using namespace tinyxml2;
|
||||
|
||||
|
||||
@@ -21,137 +27,425 @@ std::string SessionCreator::info(const std::string& filename)
|
||||
{
|
||||
std::string ret = "";
|
||||
|
||||
XMLDocument doc;
|
||||
XMLError eResult = doc.LoadFile(filename.c_str());
|
||||
if ( XMLResultError(eResult))
|
||||
return ret;
|
||||
// if the file exists
|
||||
if (SystemToolkit::file_exists(filename)) {
|
||||
// try to load the file
|
||||
XMLDocument doc;
|
||||
XMLError eResult = doc.LoadFile(filename.c_str());
|
||||
// silently ignore on error
|
||||
if ( !XMLResultError(eResult, false)) {
|
||||
|
||||
XMLElement *header = doc.FirstChildElement(APP_NAME);
|
||||
if (header != nullptr && header->Attribute("date") != 0) {
|
||||
int s = header->IntAttribute("size");
|
||||
ret = std::to_string( s ) + " source" + ( s > 1 ? "s\n" : "\n");
|
||||
std::string date( header->Attribute("date") );
|
||||
ret += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " ";
|
||||
ret += date.substr(8,2) + ":" + date.substr(10,2) + "\n";
|
||||
XMLElement *header = doc.FirstChildElement(APP_NAME);
|
||||
if (header != nullptr && header->Attribute("date") != 0) {
|
||||
int s = header->IntAttribute("size");
|
||||
ret = std::to_string( s ) + " source" + ( s > 1 ? "s\n" : "\n");
|
||||
const char *att_string = header->Attribute("resolution");
|
||||
if (att_string)
|
||||
ret += std::string( att_string ) + "\n";
|
||||
att_string = header->Attribute("date");
|
||||
if (att_string) {
|
||||
std::string date( att_string );
|
||||
ret += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " @ ";
|
||||
ret += date.substr(8,2) + ":" + date.substr(10,2);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SessionCreator::SessionCreator(Session *session): Visitor(), session_(session)
|
||||
SessionCreator::SessionCreator(int recursion): SessionLoader(nullptr, recursion)
|
||||
{
|
||||
xmlDoc_ = new XMLDocument;
|
||||
|
||||
}
|
||||
|
||||
bool SessionCreator::load(const std::string& filename)
|
||||
void SessionCreator::load(const std::string& filename)
|
||||
{
|
||||
XMLError eResult = xmlDoc_->LoadFile(filename.c_str());
|
||||
if ( XMLResultError(eResult))
|
||||
return false;
|
||||
XMLError eResult = xmlDoc_.LoadFile(filename.c_str());
|
||||
if ( XMLResultError(eResult)){
|
||||
Log::Warning("%s could not be openned.", filename.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
XMLElement *header = xmlDoc_->FirstChildElement(APP_NAME);
|
||||
XMLElement *header = xmlDoc_.FirstChildElement(APP_NAME);
|
||||
if (header == nullptr) {
|
||||
Log::Warning("%s is not a %s session file.", filename.c_str(), APP_NAME);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
int version_major = -1, version_minor = -1;
|
||||
header->QueryIntAttribute("major", &version_major); // TODO incompatible if major is different?
|
||||
header->QueryIntAttribute("major", &version_major);
|
||||
header->QueryIntAttribute("minor", &version_minor);
|
||||
if (version_major != XML_VERSION_MAJOR || version_minor != XML_VERSION_MINOR){
|
||||
Log::Warning("%s is in a different versions of session file. Loading might fail.", filename.c_str());
|
||||
return false;
|
||||
Log::Warning("%s session file is in version v%d.%d. but this vimix program expects v%d.%d.\n"
|
||||
"Loading might fail or lead to different or incomplete configuration.\n"
|
||||
"You can save this session again to avoid this warning.",
|
||||
filename.c_str(), version_major, version_minor, XML_VERSION_MAJOR, XML_VERSION_MINOR);
|
||||
// return;
|
||||
}
|
||||
|
||||
// ok, ready to read sources
|
||||
loadSession( xmlDoc_->FirstChildElement("Session") );
|
||||
// excellent, session was created: load optionnal config
|
||||
if (session_){
|
||||
loadConfig( xmlDoc_->FirstChildElement("Views") );
|
||||
}
|
||||
// session file seems legit, create a session
|
||||
session_ = new Session;
|
||||
|
||||
return true;
|
||||
// load views config (includes resolution of session rendering)
|
||||
loadConfig( xmlDoc_.FirstChildElement("Views") );
|
||||
|
||||
// ready to read sources
|
||||
SessionLoader::load( xmlDoc_.FirstChildElement("Session") );
|
||||
|
||||
// create groups
|
||||
std::list< SourceList > groups = getMixingGroups();
|
||||
for (auto group_it = groups.begin(); group_it != groups.end(); group_it++)
|
||||
session_->link( *group_it );
|
||||
|
||||
// all good
|
||||
session_->setFilename(filename);
|
||||
}
|
||||
|
||||
void SessionCreator::loadSession(XMLElement *sessionNode)
|
||||
{
|
||||
if (sessionNode != nullptr) {
|
||||
// create a session if not provided
|
||||
if (!session_)
|
||||
session_ = new Session;
|
||||
|
||||
int counter = 0;
|
||||
XMLElement* sourceNode = sessionNode->FirstChildElement("Source");
|
||||
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
|
||||
{
|
||||
xmlCurrent_ = sourceNode;
|
||||
counter++;
|
||||
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if (!pType)
|
||||
continue;
|
||||
if ( std::string(pType) == "MediaSource") {
|
||||
MediaSource *new_media_source = new MediaSource();
|
||||
new_media_source->accept(*this);
|
||||
session_->addSource(new_media_source);
|
||||
}
|
||||
else if ( std::string(pType) == "SessionSource") {
|
||||
SessionSource *new_session_source = new SessionSource();
|
||||
new_session_source->accept(*this);
|
||||
session_->addSource(new_session_source);
|
||||
}
|
||||
else if ( std::string(pType) == "RenderSource") {
|
||||
RenderSource *new_render_source = new RenderSource(session_);
|
||||
new_render_source->accept(*this);
|
||||
session_->addSource(new_render_source);
|
||||
}
|
||||
// TODO : create other types of source
|
||||
|
||||
}
|
||||
|
||||
// create clones after all sources to potentially clone have been created
|
||||
sourceNode = sessionNode->FirstChildElement("Source");
|
||||
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
|
||||
{
|
||||
xmlCurrent_ = sourceNode;
|
||||
counter++;
|
||||
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if (!pType)
|
||||
continue;
|
||||
|
||||
if ( std::string(pType) == "CloneSource") {
|
||||
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
|
||||
if (originNode) {
|
||||
std::string sourcename = std::string ( originNode->GetText() );
|
||||
SourceList::iterator origin = session_->find(sourcename);
|
||||
if (origin != session_->end()) {
|
||||
CloneSource *new_clone_source = (*origin)->clone();
|
||||
new_clone_source->accept(*this);
|
||||
session_->addSource(new_clone_source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
Log::Warning("Session seems empty.");
|
||||
}
|
||||
|
||||
void SessionCreator::loadConfig(XMLElement *viewsNode)
|
||||
{
|
||||
if (viewsNode != nullptr) {
|
||||
// ok, ready to read views
|
||||
SessionCreator::XMLToNode( viewsNode->FirstChildElement("Mixing"), *session_->config(View::MIXING));
|
||||
SessionCreator::XMLToNode( viewsNode->FirstChildElement("Geometry"), *session_->config(View::GEOMETRY));
|
||||
SessionCreator::XMLToNode( viewsNode->FirstChildElement("Layer"), *session_->config(View::LAYER));
|
||||
SessionCreator::XMLToNode( viewsNode->FirstChildElement("Rendering"), *session_->config(View::RENDERING));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Mixing"), *session_->config(View::MIXING));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Geometry"), *session_->config(View::GEOMETRY));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Layer"), *session_->config(View::LAYER));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Texture"), *session_->config(View::TEXTURE));
|
||||
SessionLoader::XMLToNode( viewsNode->FirstChildElement("Rendering"), *session_->config(View::RENDERING));
|
||||
}
|
||||
}
|
||||
|
||||
void SessionCreator::XMLToNode(tinyxml2::XMLElement *xml, Node &n)
|
||||
SessionLoader::SessionLoader(): Visitor(),
|
||||
session_(nullptr), xmlCurrent_(nullptr), recursion_(0)
|
||||
{
|
||||
// impose C locale
|
||||
setlocale(LC_ALL, "C");
|
||||
}
|
||||
|
||||
SessionLoader::SessionLoader(Session *session, int recursion): Visitor(),
|
||||
session_(session), xmlCurrent_(nullptr), recursion_(recursion)
|
||||
{
|
||||
// impose C locale
|
||||
setlocale(LC_ALL, "C");
|
||||
}
|
||||
|
||||
|
||||
std::map< uint64_t, Source* > SessionLoader::getSources() const
|
||||
{
|
||||
return sources_id_;
|
||||
}
|
||||
|
||||
// groups_sources_id_ is parsed in XML and contains list of groups of ids
|
||||
// Here we return the list of groups of newly created sources
|
||||
// based on correspondance map sources_id_
|
||||
// NB: importantly the list is cleared from duplicates
|
||||
std::list< SourceList > SessionLoader::getMixingGroups() const
|
||||
{
|
||||
std::list< SourceList > groups_new_sources_id;
|
||||
|
||||
// perform conversion from xml id to new id
|
||||
for (auto git = groups_sources_id_.begin(); git != groups_sources_id_.end(); git++)
|
||||
{
|
||||
SourceList new_sources;
|
||||
for (auto sit = (*git).begin(); sit != (*git).end(); sit++ ) {
|
||||
if (sources_id_.count(*sit) > 0)
|
||||
new_sources.push_back( sources_id_.at(*sit) );
|
||||
}
|
||||
new_sources.sort();
|
||||
groups_new_sources_id.push_back( new_sources );
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
groups_new_sources_id.unique();
|
||||
|
||||
return groups_new_sources_id;
|
||||
}
|
||||
|
||||
void SessionLoader::load(XMLElement *sessionNode)
|
||||
{
|
||||
sources_id_.clear();
|
||||
|
||||
if (recursion_ > MAX_SESSION_LEVEL) {
|
||||
Log::Warning("Recursive or imbricated sessions detected! Interrupting loading after %d iterations.\n", MAX_SESSION_LEVEL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionNode != nullptr && session_ != nullptr) {
|
||||
|
||||
XMLElement* sourceNode = sessionNode->FirstChildElement("Source");
|
||||
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
|
||||
{
|
||||
xmlCurrent_ = sourceNode;
|
||||
|
||||
// source to load
|
||||
Source *load_source = nullptr;
|
||||
|
||||
// check if a source with the given id exists in the session
|
||||
uint64_t id_xml_ = 0;
|
||||
xmlCurrent_->QueryUnsigned64Attribute("id", &id_xml_);
|
||||
SourceList::iterator sit = session_->find(id_xml_);
|
||||
|
||||
// no source with this id exists
|
||||
if ( sit == session_->end() ) {
|
||||
// create a new source depending on type
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if (!pType)
|
||||
continue;
|
||||
if ( std::string(pType) == "MediaSource") {
|
||||
load_source = new MediaSource;
|
||||
}
|
||||
else if ( std::string(pType) == "SessionSource") {
|
||||
load_source = new SessionFileSource;
|
||||
}
|
||||
else if ( std::string(pType) == "GroupSource") {
|
||||
load_source = new SessionGroupSource;
|
||||
}
|
||||
else if ( std::string(pType) == "RenderSource") {
|
||||
load_source = new RenderSource;
|
||||
}
|
||||
else if ( std::string(pType) == "PatternSource") {
|
||||
load_source = new PatternSource;
|
||||
}
|
||||
else if ( std::string(pType) == "DeviceSource") {
|
||||
load_source = new DeviceSource;
|
||||
}
|
||||
else if ( std::string(pType) == "NetworkSource") {
|
||||
load_source = new NetworkSource;
|
||||
}
|
||||
|
||||
// skip failed (including clones)
|
||||
if (!load_source)
|
||||
continue;
|
||||
|
||||
// add source to session
|
||||
session_->addSource(load_source);
|
||||
}
|
||||
// get reference to the existing source
|
||||
else
|
||||
load_source = *sit;
|
||||
|
||||
// apply config to source
|
||||
load_source->accept(*this);
|
||||
load_source->touch();
|
||||
|
||||
// remember
|
||||
sources_id_[id_xml_] = load_source;
|
||||
}
|
||||
|
||||
// create clones after all sources, to be able to clone a source created above
|
||||
sourceNode = sessionNode->FirstChildElement("Source");
|
||||
for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement())
|
||||
{
|
||||
xmlCurrent_ = sourceNode;
|
||||
|
||||
// verify type of node
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if ( pType && std::string(pType) == "CloneSource") {
|
||||
|
||||
// check if a source with same id exists
|
||||
uint64_t id_xml_ = 0;
|
||||
xmlCurrent_->QueryUnsigned64Attribute("id", &id_xml_);
|
||||
SourceList::iterator sit = session_->find(id_xml_);
|
||||
|
||||
// no source clone with this id exists
|
||||
if ( sit == session_->end() ) {
|
||||
|
||||
// clone from given origin
|
||||
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
|
||||
if (originNode) {
|
||||
std::string sourcename = std::string ( originNode->GetText() );
|
||||
SourceList::iterator origin = session_->find(sourcename);
|
||||
// found the orign source
|
||||
if (origin != session_->end()) {
|
||||
// create a new source of type Clone
|
||||
Source *clone_source = (*origin)->clone();
|
||||
|
||||
// add source to session
|
||||
session_->addSource(clone_source);
|
||||
|
||||
// apply config to source
|
||||
clone_source->accept(*this);
|
||||
clone_source->touch();
|
||||
|
||||
// remember
|
||||
sources_id_[id_xml_] = clone_source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, Mode mode)
|
||||
{
|
||||
xmlCurrent_ = sourceNode;
|
||||
|
||||
// source to load
|
||||
Source *load_source = nullptr;
|
||||
bool is_clone = false;
|
||||
|
||||
SourceList::iterator sit = session_->end();
|
||||
// check if a source with the given id exists in the session
|
||||
if (mode == CLONE) {
|
||||
uint64_t id__ = 0;
|
||||
xmlCurrent_->QueryUnsigned64Attribute("id", &id__);
|
||||
sit = session_->find(id__);
|
||||
}
|
||||
|
||||
// no source with this id exists or Mode DUPLICATE
|
||||
if ( sit == session_->end() ) {
|
||||
// create a new source depending on type
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if (pType) {
|
||||
if ( std::string(pType) == "MediaSource") {
|
||||
load_source = new MediaSource;
|
||||
}
|
||||
else if ( std::string(pType) == "SessionSource") {
|
||||
load_source = new SessionFileSource;
|
||||
}
|
||||
else if ( std::string(pType) == "GroupSource") {
|
||||
load_source = new SessionGroupSource;
|
||||
}
|
||||
else if ( std::string(pType) == "RenderSource") {
|
||||
load_source = new RenderSource;
|
||||
}
|
||||
else if ( std::string(pType) == "PatternSource") {
|
||||
load_source = new PatternSource;
|
||||
}
|
||||
else if ( std::string(pType) == "DeviceSource") {
|
||||
load_source = new DeviceSource;
|
||||
}
|
||||
else if ( std::string(pType) == "NetworkSource") {
|
||||
load_source = new NetworkSource;
|
||||
}
|
||||
else if ( std::string(pType) == "CloneSource") {
|
||||
// clone from given origin
|
||||
XMLElement* originNode = xmlCurrent_->FirstChildElement("origin");
|
||||
if (originNode) {
|
||||
std::string sourcename = std::string ( originNode->GetText() );
|
||||
SourceList::iterator origin = session_->find(sourcename);
|
||||
// found the orign source
|
||||
if (origin != session_->end())
|
||||
load_source = (*origin)->clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// clone existing source
|
||||
else {
|
||||
load_source = (*sit)->clone();
|
||||
is_clone = true;
|
||||
}
|
||||
|
||||
// apply config to source
|
||||
if (load_source) {
|
||||
load_source->accept(*this);
|
||||
// increment depth for clones (avoid supperposition)
|
||||
if (is_clone)
|
||||
load_source->group(View::LAYER)->translation_.z += 0.2f;
|
||||
}
|
||||
|
||||
return load_source;
|
||||
}
|
||||
|
||||
|
||||
bool SessionLoader::isClipboard(std::string clipboard)
|
||||
{
|
||||
if (clipboard.size() > 6 && clipboard.substr(0, 6) == "<" APP_NAME )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement* SessionLoader::firstSourceElement(std::string clipboard, XMLDocument &xmlDoc)
|
||||
{
|
||||
tinyxml2::XMLElement* sourceNode = nullptr;
|
||||
|
||||
if ( !isClipboard(clipboard) )
|
||||
return sourceNode;
|
||||
|
||||
// header
|
||||
tinyxml2::XMLError eResult = xmlDoc.Parse(clipboard.c_str());
|
||||
if ( XMLResultError(eResult))
|
||||
return sourceNode;
|
||||
|
||||
tinyxml2::XMLElement *root = xmlDoc.FirstChildElement(APP_NAME);
|
||||
if ( root == nullptr )
|
||||
return sourceNode;
|
||||
|
||||
// find node
|
||||
sourceNode = root->FirstChildElement("Source");
|
||||
return sourceNode;
|
||||
}
|
||||
|
||||
void SessionLoader::applyImageProcessing(const Source &s, std::string clipboard)
|
||||
{
|
||||
if ( !isClipboard(clipboard) )
|
||||
return;
|
||||
|
||||
// header
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLError eResult = xmlDoc.Parse(clipboard.c_str());
|
||||
if ( XMLResultError(eResult))
|
||||
return;
|
||||
|
||||
tinyxml2::XMLElement *root = xmlDoc.FirstChildElement(APP_NAME);
|
||||
if ( root == nullptr )
|
||||
return;
|
||||
|
||||
// find node
|
||||
tinyxml2::XMLElement* imgprocNode = nullptr;
|
||||
tinyxml2::XMLElement* sourceNode = root->FirstChildElement("Source");
|
||||
if (sourceNode == nullptr)
|
||||
imgprocNode = root->FirstChildElement("ImageProcessing");
|
||||
else
|
||||
imgprocNode = sourceNode->FirstChildElement("ImageProcessing");
|
||||
|
||||
if (imgprocNode == nullptr)
|
||||
return;
|
||||
|
||||
// create session visitor and browse
|
||||
SessionLoader loader;
|
||||
loader.xmlCurrent_ = imgprocNode;
|
||||
s.processingShader()->accept(loader);
|
||||
}
|
||||
|
||||
//void SessionLoader::applyMask(const Source &s, std::string clipboard)
|
||||
//{
|
||||
// if ( !isClipboard(clipboard) )
|
||||
// return;
|
||||
|
||||
// // header
|
||||
// tinyxml2::XMLDocument xmlDoc;
|
||||
// tinyxml2::XMLError eResult = xmlDoc.Parse(clipboard.c_str());
|
||||
// if ( XMLResultError(eResult))
|
||||
// return;
|
||||
|
||||
// tinyxml2::XMLElement *root = xmlDoc.FirstChildElement(APP_NAME);
|
||||
// if ( root == nullptr )
|
||||
// return;
|
||||
|
||||
// // find node
|
||||
// tinyxml2::XMLElement* naskNode = nullptr;
|
||||
// tinyxml2::XMLElement* sourceNode = root->FirstChildElement("Source");
|
||||
// if (sourceNode == nullptr)
|
||||
// naskNode = root->FirstChildElement("Mask");
|
||||
// else
|
||||
// naskNode = sourceNode->FirstChildElement("ImageProcessing");
|
||||
|
||||
// if (naskNode == nullptr)
|
||||
// return;
|
||||
|
||||
// // create session visitor and browse
|
||||
// SessionLoader loader;
|
||||
// loader.xmlCurrent_ = naskNode;
|
||||
//// s.processingShader()->accept(loader);
|
||||
//}
|
||||
|
||||
void SessionLoader::XMLToNode(tinyxml2::XMLElement *xml, Node &n)
|
||||
{
|
||||
if (xml != nullptr){
|
||||
XMLElement *node = xml->FirstChildElement("Node");
|
||||
@@ -167,31 +461,73 @@ void SessionCreator::XMLToNode(tinyxml2::XMLElement *xml, Node &n)
|
||||
XMLElement *rotationNode = node->FirstChildElement("rotation");
|
||||
if (rotationNode)
|
||||
tinyxml2::XMLElementToGLM( rotationNode->FirstChildElement("vec3"), n.rotation_);
|
||||
XMLElement *cropNode = node->FirstChildElement("crop");
|
||||
if (cropNode)
|
||||
tinyxml2::XMLElementToGLM( cropNode->FirstChildElement("vec3"), n.crop_);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionCreator::visit(Node &n)
|
||||
void SessionLoader::visit(Node &n)
|
||||
{
|
||||
XMLToNode(xmlCurrent_, n);
|
||||
}
|
||||
|
||||
void SessionCreator::visit(MediaPlayer &n)
|
||||
void SessionLoader::visit(MediaPlayer &n)
|
||||
{
|
||||
XMLElement* mediaplayerNode = xmlCurrent_->FirstChildElement("MediaPlayer");
|
||||
|
||||
if (mediaplayerNode) {
|
||||
double speed = 1.0;
|
||||
mediaplayerNode->QueryDoubleAttribute("speed", &speed);
|
||||
n.setPlaySpeed(speed);
|
||||
int loop = 1;
|
||||
mediaplayerNode->QueryIntAttribute("loop", &loop);
|
||||
n.setLoop( (MediaPlayer::LoopMode) loop);
|
||||
bool play = true;
|
||||
mediaplayerNode->QueryBoolAttribute("play", &play);
|
||||
n.play(play);
|
||||
uint64_t id__ = -1;
|
||||
mediaplayerNode->QueryUnsigned64Attribute("id", &id__);
|
||||
|
||||
// timeline
|
||||
XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline");
|
||||
if (timelineelement) {
|
||||
Timeline tl;
|
||||
tl.setTiming( n.timeline()->interval(), n.timeline()->step());
|
||||
XMLElement *gapselement = timelineelement->FirstChildElement("Gaps");
|
||||
if (gapselement) {
|
||||
XMLElement* gap = gapselement->FirstChildElement("Interval");
|
||||
for( ; gap ; gap = gap->NextSiblingElement())
|
||||
{
|
||||
GstClockTime a = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime b = GST_CLOCK_TIME_NONE;
|
||||
gap->QueryUnsigned64Attribute("begin", &a);
|
||||
gap->QueryUnsigned64Attribute("end", &b);
|
||||
tl.addGap( a, b );
|
||||
}
|
||||
}
|
||||
XMLElement *fadingselement = timelineelement->FirstChildElement("Fading");
|
||||
if (fadingselement) {
|
||||
XMLElement* array = fadingselement->FirstChildElement("array");
|
||||
XMLElementDecodeArray(array, tl.fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
}
|
||||
n.setTimeline(tl);
|
||||
}
|
||||
|
||||
// change play status only if different id (e.g. new media player)
|
||||
if ( n.id() != id__ ) {
|
||||
|
||||
double speed = 1.0;
|
||||
mediaplayerNode->QueryDoubleAttribute("speed", &speed);
|
||||
n.setPlaySpeed(speed);
|
||||
|
||||
int loop = 1;
|
||||
mediaplayerNode->QueryIntAttribute("loop", &loop);
|
||||
n.setLoop( (MediaPlayer::LoopMode) loop);
|
||||
|
||||
bool gpudisable = false;
|
||||
mediaplayerNode->QueryBoolAttribute("software_decoding", &gpudisable);
|
||||
n.setSoftwareDecodingForced(gpudisable);
|
||||
|
||||
bool play = true;
|
||||
mediaplayerNode->QueryBoolAttribute("play", &play);
|
||||
n.play(play);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionCreator::visit(Shader &n)
|
||||
void SessionLoader::visit(Shader &n)
|
||||
{
|
||||
XMLElement* color = xmlCurrent_->FirstChildElement("color");
|
||||
if ( color ) {
|
||||
@@ -205,7 +541,7 @@ void SessionCreator::visit(Shader &n)
|
||||
}
|
||||
}
|
||||
|
||||
void SessionCreator::visit(ImageShader &n)
|
||||
void SessionLoader::visit(ImageShader &n)
|
||||
{
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if ( std::string(pType) != "ImageShader" )
|
||||
@@ -214,11 +550,29 @@ void SessionCreator::visit(ImageShader &n)
|
||||
XMLElement* uniforms = xmlCurrent_->FirstChildElement("uniforms");
|
||||
if (uniforms) {
|
||||
uniforms->QueryFloatAttribute("stipple", &n.stipple);
|
||||
uniforms->QueryUnsignedAttribute("mask", &n.mask);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionCreator::visit(ImageProcessingShader &n)
|
||||
void SessionLoader::visit(MaskShader &n)
|
||||
{
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if ( std::string(pType) != "MaskShader" )
|
||||
return;
|
||||
|
||||
xmlCurrent_->QueryUnsignedAttribute("mode", &n.mode);
|
||||
xmlCurrent_->QueryUnsignedAttribute("shape", &n.shape);
|
||||
|
||||
XMLElement* uniforms = xmlCurrent_->FirstChildElement("uniforms");
|
||||
if (uniforms) {
|
||||
uniforms->QueryFloatAttribute("blur", &n.blur);
|
||||
uniforms->QueryIntAttribute("option", &n.option);
|
||||
XMLElement* size = uniforms->FirstChildElement("size");
|
||||
if (size)
|
||||
tinyxml2::XMLElementToGLM( size->FirstChildElement("vec2"), n.size);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLoader::visit(ImageProcessingShader &n)
|
||||
{
|
||||
const char *pType = xmlCurrent_->Attribute("type");
|
||||
if ( std::string(pType) != "ImageProcessingShader" )
|
||||
@@ -249,56 +603,195 @@ void SessionCreator::visit(ImageProcessingShader &n)
|
||||
tinyxml2::XMLElementToGLM( chromakey->FirstChildElement("vec4"), n.chromakey);
|
||||
}
|
||||
|
||||
void SessionCreator::visit (Source& s)
|
||||
void SessionLoader::visit (Source& s)
|
||||
{
|
||||
XMLElement* sourceNode = xmlCurrent_;
|
||||
const char *pName = sourceNode->Attribute("name");
|
||||
s.setName(pName);
|
||||
bool l = false;
|
||||
sourceNode->QueryBoolAttribute("locked", &l);
|
||||
s.setLocked(l);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Mixing");
|
||||
s.groupNode(View::MIXING)->accept(*this);
|
||||
if (xmlCurrent_) s.groupNode(View::MIXING)->accept(*this);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Geometry");
|
||||
s.groupNode(View::GEOMETRY)->accept(*this);
|
||||
if (xmlCurrent_) s.groupNode(View::GEOMETRY)->accept(*this);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Layer");
|
||||
s.groupNode(View::LAYER)->accept(*this);
|
||||
if (xmlCurrent_) s.groupNode(View::LAYER)->accept(*this);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Texture");
|
||||
if (xmlCurrent_) {
|
||||
s.groupNode(View::TEXTURE)->accept(*this);
|
||||
bool m = true;
|
||||
xmlCurrent_->QueryBoolAttribute("mirrored", &m);
|
||||
s.setTextureMirrored(m);
|
||||
}
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Blending");
|
||||
s.blendingShader()->accept(*this);
|
||||
if (xmlCurrent_) s.blendingShader()->accept(*this);
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("Mask");
|
||||
if (xmlCurrent_) {
|
||||
// read the mask shader attributes
|
||||
s.maskShader()->accept(*this);
|
||||
// if there is an Image mask stored
|
||||
XMLElement* imageNode = xmlCurrent_->FirstChildElement("Image");
|
||||
if (imageNode) {
|
||||
// if there is an internal array of data
|
||||
XMLElement* array = imageNode->FirstChildElement("array");
|
||||
if (array) {
|
||||
// create a temporary jpeg with size of the array
|
||||
FrameBufferImage::jpegBuffer jpgimg;
|
||||
array->QueryUnsignedAttribute("len", &jpgimg.len);
|
||||
// ok, we got a size of data to load
|
||||
if (jpgimg.len>0) {
|
||||
// allocate jpeg buffer
|
||||
jpgimg.buffer = (unsigned char*) malloc(jpgimg.len);
|
||||
// actual decoding of array
|
||||
if (XMLElementDecodeArray(array, jpgimg.buffer, jpgimg.len) )
|
||||
// create and set the image from jpeg
|
||||
s.setMask(new FrameBufferImage(jpgimg));
|
||||
// free temporary buffer
|
||||
if (jpgimg.buffer)
|
||||
free(jpgimg.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("ImageProcessing");
|
||||
bool on = xmlCurrent_->BoolAttribute("enabled", true);
|
||||
s.processingShader()->accept(*this);
|
||||
s.setImageProcessingEnabled(on);
|
||||
if (xmlCurrent_) {
|
||||
bool on = xmlCurrent_->BoolAttribute("enabled", true);
|
||||
s.processingShader()->accept(*this);
|
||||
s.setImageProcessingEnabled(on);
|
||||
}
|
||||
|
||||
xmlCurrent_ = sourceNode->FirstChildElement("MixingGroup");
|
||||
if (xmlCurrent_) {
|
||||
SourceIdList idlist;
|
||||
XMLElement* mixingSourceNode = xmlCurrent_->FirstChildElement("source");
|
||||
for ( ; mixingSourceNode ; mixingSourceNode = mixingSourceNode->NextSiblingElement()) {
|
||||
uint64_t id__ = 0;
|
||||
mixingSourceNode->QueryUnsigned64Attribute("id", &id__);
|
||||
idlist.push_back(id__);
|
||||
}
|
||||
groups_sources_id_.push_back(idlist);
|
||||
}
|
||||
|
||||
// restore current
|
||||
xmlCurrent_ = sourceNode;
|
||||
}
|
||||
|
||||
void SessionCreator::visit (MediaSource& s)
|
||||
void SessionLoader::visit (MediaSource& s)
|
||||
{
|
||||
// set uri
|
||||
XMLElement* uriNode = xmlCurrent_->FirstChildElement("uri");
|
||||
if (uriNode) {
|
||||
std::string uri = std::string ( uriNode->GetText() );
|
||||
s.setPath(uri);
|
||||
// load only new files
|
||||
if ( uri != s.path() )
|
||||
s.setPath(uri);
|
||||
}
|
||||
|
||||
// set config media player
|
||||
s.mediaplayer()->accept(*this);
|
||||
}
|
||||
|
||||
void SessionCreator::visit (SessionSource& s)
|
||||
void SessionLoader::visit (SessionFileSource& s)
|
||||
{
|
||||
// set fading
|
||||
float f = 0.f;
|
||||
xmlCurrent_->QueryFloatAttribute("fading", &f);
|
||||
s.session()->setFading(f);
|
||||
// set uri
|
||||
XMLElement* pathNode = xmlCurrent_->FirstChildElement("path");
|
||||
if (pathNode) {
|
||||
std::string path = std::string ( pathNode->GetText() );
|
||||
s.load(path);
|
||||
// load only new files
|
||||
if ( path != s.path() )
|
||||
s.load(path, recursion_ + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SessionLoader::visit (SessionGroupSource& s)
|
||||
{
|
||||
// set resolution from host session
|
||||
s.setResolution( session_->config(View::RENDERING)->scale_ );
|
||||
|
||||
// get the inside session
|
||||
XMLElement* sessionGroupNode = xmlCurrent_->FirstChildElement("Session");
|
||||
if (sessionGroupNode) {
|
||||
// only parse if newly created
|
||||
if (s.session()->empty()) {
|
||||
// load session inside group
|
||||
SessionLoader grouploader( s.session(), recursion_ + 1 );
|
||||
grouploader.load( sessionGroupNode );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionLoader::visit (RenderSource& s)
|
||||
{
|
||||
s.setSession( session_ );
|
||||
}
|
||||
|
||||
void SessionLoader::visit (PatternSource& s)
|
||||
{
|
||||
uint t = xmlCurrent_->UnsignedAttribute("pattern");
|
||||
|
||||
glm::ivec2 resolution(800, 600);
|
||||
XMLElement* res = xmlCurrent_->FirstChildElement("resolution");
|
||||
if (res)
|
||||
tinyxml2::XMLElementToGLM( res->FirstChildElement("ivec2"), resolution);
|
||||
|
||||
// change only if different pattern
|
||||
if ( t != s.pattern()->type() )
|
||||
s.setPattern(t, resolution);
|
||||
}
|
||||
|
||||
void SessionLoader::visit (DeviceSource& s)
|
||||
{
|
||||
std::string devname = std::string ( xmlCurrent_->Attribute("device") );
|
||||
|
||||
// change only if different device
|
||||
if ( devname != s.device() )
|
||||
s.setDevice(devname);
|
||||
}
|
||||
|
||||
|
||||
void SessionLoader::visit (NetworkSource& s)
|
||||
{
|
||||
std::string connect = std::string ( xmlCurrent_->Attribute("connection") );
|
||||
|
||||
// change only if different device
|
||||
if ( connect != s.connection() )
|
||||
s.setConnection(connect);
|
||||
}
|
||||
|
||||
// dirty hack wich can be useful ?
|
||||
|
||||
//class DummySource : public Source
|
||||
//{
|
||||
// friend class SessionLoader;
|
||||
//public:
|
||||
// uint texture() const override { return 0; }
|
||||
// bool failed() const override { return true; }
|
||||
// void accept (Visitor& v) override { Source::accept(v); }
|
||||
//protected:
|
||||
// DummySource() : Source() {}
|
||||
// void init() override {}
|
||||
//};
|
||||
|
||||
//Source *SessionLoader::createDummy(tinyxml2::XMLElement *sourceNode)
|
||||
//{
|
||||
// SessionLoader loader;
|
||||
// loader.xmlCurrent_ = sourceNode;
|
||||
// DummySource *dum = new DummySource;
|
||||
// dum->accept(loader);
|
||||
// return dum;
|
||||
//}
|
||||
|
||||
|
||||
|
||||
@@ -1,56 +1,90 @@
|
||||
#ifndef SESSIONCREATOR_H
|
||||
#define SESSIONCREATOR_H
|
||||
|
||||
#include <map>
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include "Visitor.h"
|
||||
#include "tinyxml2Toolkit.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
class Session;
|
||||
|
||||
class SessionCreator : public Visitor {
|
||||
|
||||
tinyxml2::XMLDocument *xmlDoc_;
|
||||
tinyxml2::XMLElement *xmlCurrent_;
|
||||
Session *session_;
|
||||
|
||||
void loadSession(tinyxml2::XMLElement *sessionNode);
|
||||
void loadConfig(tinyxml2::XMLElement *viewsNode);
|
||||
class SessionLoader : public Visitor {
|
||||
|
||||
SessionLoader();
|
||||
|
||||
public:
|
||||
SessionCreator(Session *session = nullptr);
|
||||
|
||||
bool load(const std::string& filename);
|
||||
SessionLoader(Session *session, int recursion = 0);
|
||||
inline Session *session() const { return session_; }
|
||||
|
||||
// Elements of Scene
|
||||
void visit(Node& n) override;
|
||||
void load(tinyxml2::XMLElement *sessionNode);
|
||||
std::map< uint64_t, Source* > getSources() const;
|
||||
std::list< SourceList > getMixingGroups() const;
|
||||
|
||||
void visit(Scene& n) override {}
|
||||
void visit(Group& n) override {}
|
||||
void visit(Switch& n) override {}
|
||||
void visit(Primitive& n) override {}
|
||||
void visit(Surface& n) override {}
|
||||
void visit(ImageSurface& n) override {}
|
||||
void visit(MediaSurface& n) override {}
|
||||
void visit(FrameBufferSurface& n) override {}
|
||||
void visit(LineStrip& n) override {}
|
||||
void visit(LineSquare&) override {}
|
||||
void visit(LineCircle& n) override {}
|
||||
void visit(Mesh& n) override {}
|
||||
typedef enum {
|
||||
CLONE,
|
||||
DUPLICATE
|
||||
} Mode;
|
||||
Source *createSource(tinyxml2::XMLElement *sourceNode, Mode mode = CLONE);
|
||||
|
||||
static bool isClipboard(std::string clipboard);
|
||||
static tinyxml2::XMLElement* firstSourceElement(std::string clipboard, tinyxml2::XMLDocument &xmlDoc);
|
||||
static void applyImageProcessing(const Source &s, std::string clipboard);
|
||||
//TODO static void applyMask(const Source &s, std::string clipboard);
|
||||
|
||||
// Elements of Scene
|
||||
void visit (Node& n) override;
|
||||
void visit (Scene&) override {}
|
||||
void visit (Group&) override {}
|
||||
void visit (Switch&) override {}
|
||||
void visit (Primitive&) override {}
|
||||
|
||||
// Elements with attributes
|
||||
void visit(MediaPlayer& n) override;
|
||||
void visit(Shader& n) override;
|
||||
void visit(ImageShader& n) override;
|
||||
void visit(ImageProcessingShader& n) override;
|
||||
void visit (MediaPlayer& n) override;
|
||||
void visit (Shader& n) override;
|
||||
void visit (ImageShader& n) override;
|
||||
void visit (MaskShader& n) override;
|
||||
void visit (ImageProcessingShader& n) override;
|
||||
|
||||
// Sources
|
||||
void visit (Source& s) override;
|
||||
void visit (MediaSource& s) override;
|
||||
void visit (SessionSource& s) override;
|
||||
void visit (SessionFileSource& s) override;
|
||||
void visit (SessionGroupSource& s) override;
|
||||
void visit (RenderSource& s) override;
|
||||
void visit (PatternSource& s) override;
|
||||
void visit (DeviceSource& s) override;
|
||||
void visit (NetworkSource& s) override;
|
||||
|
||||
protected:
|
||||
// result created session
|
||||
Session *session_;
|
||||
// parsing current xml
|
||||
tinyxml2::XMLElement *xmlCurrent_;
|
||||
// level of loading recursion
|
||||
int recursion_;
|
||||
// map of correspondance from xml source id (key) to new source pointer (value)
|
||||
std::map< uint64_t, Source* > sources_id_;
|
||||
// list of groups (lists of xml source id)
|
||||
std::list< SourceIdList > groups_sources_id_;
|
||||
|
||||
static std::string info(const std::string& filename);
|
||||
static void XMLToNode(tinyxml2::XMLElement *xml, Node &n);
|
||||
};
|
||||
|
||||
class SessionCreator : public SessionLoader {
|
||||
|
||||
tinyxml2::XMLDocument xmlDoc_;
|
||||
|
||||
void loadConfig(tinyxml2::XMLElement *viewsNode);
|
||||
|
||||
public:
|
||||
SessionCreator(int recursion = 0);
|
||||
|
||||
void load(const std::string& filename);
|
||||
|
||||
static std::string info(const std::string& filename);
|
||||
};
|
||||
|
||||
#endif // SESSIONCREATOR_H
|
||||
|
||||
@@ -10,36 +10,87 @@
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "Resource.h"
|
||||
#include "Primitives.h"
|
||||
#include "Mesh.h"
|
||||
#include "Decorations.h"
|
||||
#include "SearchVisitor.h"
|
||||
#include "Session.h"
|
||||
#include "SessionCreator.h"
|
||||
#include "Mixer.h"
|
||||
|
||||
|
||||
void SessionSource::loadSession(const std::string& filename, SessionSource *source)
|
||||
SessionSource::SessionSource() : Source(), failed_(false)
|
||||
{
|
||||
source->loadFinished_ = false;
|
||||
|
||||
// actual loading of xml file
|
||||
SessionCreator creator( source->session_ );
|
||||
|
||||
if (creator.load(filename)) {
|
||||
// all ok, validate session filename
|
||||
source->session_->setFilename(filename);
|
||||
}
|
||||
else {
|
||||
// error loading
|
||||
Log::Notify("Failed to load Session file %s.", filename.c_str());
|
||||
source->loadFailed_ = true;
|
||||
}
|
||||
|
||||
// end thread
|
||||
source->loadFinished_ = true;
|
||||
session_ = new Session;
|
||||
}
|
||||
|
||||
SessionSource::SessionSource() : Source(), path_("")
|
||||
SessionSource::~SessionSource()
|
||||
{
|
||||
// delete session
|
||||
if (session_)
|
||||
delete session_;
|
||||
}
|
||||
|
||||
Session *SessionSource::detach()
|
||||
{
|
||||
// remember pointer to give away
|
||||
Session *giveaway = session_;
|
||||
|
||||
// work on a new session
|
||||
session_ = new Session;
|
||||
|
||||
// make disabled
|
||||
initialized_ = false;
|
||||
|
||||
// ask to delete me
|
||||
failed_ = true;
|
||||
|
||||
// lost ref to previous session: to be deleted elsewhere...
|
||||
return giveaway;
|
||||
}
|
||||
|
||||
bool SessionSource::failed() const
|
||||
{
|
||||
return failed_;
|
||||
}
|
||||
|
||||
uint SessionSource::texture() const
|
||||
{
|
||||
if (session_ && session_->frame())
|
||||
return session_->frame()->texture();
|
||||
else
|
||||
return Resource::getTextureBlack();
|
||||
}
|
||||
|
||||
void SessionSource::setActive (bool on)
|
||||
{
|
||||
Source::setActive(on);
|
||||
|
||||
// change status of session (recursive change of internal sources)
|
||||
if (session_ != nullptr)
|
||||
session_->setActive(active_);
|
||||
}
|
||||
|
||||
void SessionSource::update(float dt)
|
||||
{
|
||||
if (session_ == nullptr)
|
||||
return;
|
||||
|
||||
// update content
|
||||
if (active_)
|
||||
session_->update(dt);
|
||||
|
||||
// delete a source which failed
|
||||
if (session_->failedSource() != nullptr) {
|
||||
session_->deleteSource(session_->failedSource());
|
||||
// fail session if all sources failed
|
||||
if ( session_->numSource() < 1)
|
||||
failed_ = true;
|
||||
}
|
||||
|
||||
Source::update(dt);
|
||||
}
|
||||
|
||||
|
||||
SessionFileSource::SessionFileSource() : SessionSource(), path_("")
|
||||
{
|
||||
// specific node for transition view
|
||||
groups_[View::TRANSITION]->visible_ = false;
|
||||
@@ -49,7 +100,7 @@ SessionSource::SessionSource() : Source(), path_("")
|
||||
frames_[View::TRANSITION] = new Switch;
|
||||
Frame *frame = new Frame(Frame::ROUND, Frame::THIN, Frame::DROP);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.9f);
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
frames_[View::TRANSITION]->attach(frame);
|
||||
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::DROP);
|
||||
frame->translation_.z = 0.01;
|
||||
@@ -65,265 +116,270 @@ SessionSource::SessionSource() : Source(), path_("")
|
||||
loader->scale_ = glm::vec3(2.f, 2.f, 1.f);
|
||||
loader->update_callbacks_.push_back(new InfiniteGlowCallback);
|
||||
overlays_[View::TRANSITION]->attach(loader);
|
||||
Symbol *center = new Symbol(Symbol::POINT, glm::vec3(0.f, -1.05f, 0.1f));
|
||||
Symbol *center = new Symbol(Symbol::CIRCLE_POINT, glm::vec3(0.f, -1.05f, 0.1f));
|
||||
overlays_[View::TRANSITION]->attach(center);
|
||||
groups_[View::TRANSITION]->attach(overlays_[View::TRANSITION]);
|
||||
|
||||
loadFailed_ = false;
|
||||
loadFinished_ = true;
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::SESSION, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
|
||||
wait_for_sources_ = false;
|
||||
|
||||
session_ = new Session;
|
||||
|
||||
// create surface:
|
||||
// - textured with original texture from session
|
||||
// - crop & repeat UV can be managed here
|
||||
// - additional custom shader can be associated
|
||||
sessionsurface_ = new Surface(processingshader_);
|
||||
}
|
||||
|
||||
SessionSource::~SessionSource()
|
||||
{
|
||||
// delete surface
|
||||
delete sessionsurface_;
|
||||
|
||||
// delete session
|
||||
if (session_)
|
||||
delete session_;
|
||||
}
|
||||
|
||||
void SessionSource::load(const std::string &p)
|
||||
void SessionFileSource::load(const std::string &p, uint recursion)
|
||||
{
|
||||
path_ = p;
|
||||
|
||||
// launch a thread to load the session
|
||||
loadFinished_ = false;
|
||||
std::thread ( SessionSource::loadSession, path_, this).detach();
|
||||
// delete session
|
||||
if (session_) {
|
||||
delete session_;
|
||||
session_ = nullptr;
|
||||
}
|
||||
|
||||
Log::Notify("Opening %s", p.c_str());
|
||||
// init session
|
||||
if ( path_.empty() ) {
|
||||
// empty session
|
||||
session_ = new Session;
|
||||
Log::Warning("Empty Session filename provided.");
|
||||
}
|
||||
else {
|
||||
// launch a thread to load the session file
|
||||
sessionLoader_ = std::async(std::launch::async, Session::load, path_, recursion);
|
||||
Log::Notify("Opening %s", p.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Session *SessionSource::detach()
|
||||
void SessionFileSource::init()
|
||||
{
|
||||
// remember pointer to give away
|
||||
Session *giveaway = session_;
|
||||
// init is first about getting the loaded session
|
||||
if (session_ == nullptr) {
|
||||
// did the loader finish ?
|
||||
if (sessionLoader_.wait_for(std::chrono::milliseconds(4)) == std::future_status::ready) {
|
||||
session_ = sessionLoader_.get();
|
||||
if (session_ == nullptr)
|
||||
failed_ = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
// work on a new session
|
||||
session_ = new Session;
|
||||
session_->update(dt_);
|
||||
|
||||
// make disabled
|
||||
initialized_ = false;
|
||||
loadFailed_ = true;
|
||||
if (wait_for_sources_) {
|
||||
|
||||
return giveaway;
|
||||
}
|
||||
// force update of of all sources
|
||||
active_ = true;
|
||||
touch();
|
||||
|
||||
bool SessionSource::failed() const
|
||||
{
|
||||
return loadFailed_;
|
||||
}
|
||||
|
||||
uint SessionSource::texture() const
|
||||
{
|
||||
if (session_ == nullptr)
|
||||
return Resource::getTextureBlack();
|
||||
return session_->frame()->texture();
|
||||
}
|
||||
|
||||
void SessionSource::replaceRenderingShader()
|
||||
{
|
||||
sessionsurface_->replaceShader(renderingshader_);
|
||||
}
|
||||
|
||||
void SessionSource::init()
|
||||
{
|
||||
if (session_ == nullptr)
|
||||
return;
|
||||
|
||||
if (wait_for_sources_) {
|
||||
|
||||
// force update of of all sources
|
||||
active_ = true;
|
||||
touch();
|
||||
|
||||
// check that every source is ready..
|
||||
bool ready = true;
|
||||
for (SourceList::iterator iter = session_->begin(); iter != session_->end(); iter++)
|
||||
{
|
||||
// interrupt if any source is NOT ready
|
||||
if ( !(*iter)->ready() ){
|
||||
ready = false;
|
||||
break;
|
||||
// check that every source is ready..
|
||||
bool ready = true;
|
||||
for (SourceList::iterator iter = session_->begin(); iter != session_->end(); ++iter)
|
||||
{
|
||||
// interrupt if any source is NOT ready
|
||||
if ( !(*iter)->ready() ){
|
||||
ready = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if all sources are ready, done with initialization!
|
||||
if (ready) {
|
||||
// done init
|
||||
wait_for_sources_ = false;
|
||||
initialized_ = true;
|
||||
Log::Info("Source Session %s loaded %d sources.", path_.c_str(), session_->numSource());
|
||||
}
|
||||
}
|
||||
// if all sources are ready, done with initialization!
|
||||
if (ready) {
|
||||
// remove the loading icon
|
||||
Node *loader = overlays_[View::TRANSITION]->back();
|
||||
overlays_[View::TRANSITION]->detatch(loader);
|
||||
delete loader;
|
||||
// done init
|
||||
wait_for_sources_ = false;
|
||||
initialized_ = true;
|
||||
Log::Info("Source Session %s loaded %d sources.", path_.c_str(), session_->numSource());
|
||||
else if ( !failed_ ) {
|
||||
|
||||
// set resolution
|
||||
session_->setResolution( session_->config(View::RENDERING)->scale_ );
|
||||
|
||||
// update to draw framebuffer
|
||||
session_->update(dt_);
|
||||
|
||||
// get the texture index from framebuffer of session, apply it to the surface
|
||||
texturesurface_->setTextureIndex( session_->frame()->texture() );
|
||||
|
||||
// create Frame buffer matching size of session
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( session_->frame()->resolution() );
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// wait for all sources to init
|
||||
if (session_->numSource() > 0)
|
||||
wait_for_sources_ = true;
|
||||
else {
|
||||
initialized_ = true;
|
||||
Log::Info("New Session created (%d x %d).", renderbuffer->width(), renderbuffer->height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( loadFinished_ && !loadFailed_ && session_ != nullptr) {
|
||||
loadFinished_ = false;
|
||||
if (initialized_)
|
||||
{
|
||||
// remove the loading icon
|
||||
Node *loader = overlays_[View::TRANSITION]->back();
|
||||
overlays_[View::TRANSITION]->detach(loader);
|
||||
delete loader;
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
}
|
||||
}
|
||||
|
||||
// set resolution
|
||||
session_->setResolution( session_->config(View::RENDERING)->scale_ );
|
||||
void SessionFileSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
// deep update once to draw framebuffer
|
||||
View::need_deep_update_ = true;
|
||||
session_->update(dt_);
|
||||
|
||||
SessionGroupSource::SessionGroupSource() : SessionSource(), resolution_(glm::vec3(0.f))
|
||||
{
|
||||
// // redo frame for layers view
|
||||
// frames_[View::LAYER]->clear();
|
||||
|
||||
// // Groups in LAYER have an additional border
|
||||
// Group *group = new Group;
|
||||
// Frame *frame = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
|
||||
// frame->translation_.z = 0.1;
|
||||
// frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
// group->attach(frame);
|
||||
// Frame *persp = new Frame(Frame::GROUP, Frame::THIN, Frame::NONE);
|
||||
// persp->translation_.z = 0.1;
|
||||
// persp->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
// group->attach(persp);
|
||||
// frames_[View::LAYER]->attach(group);
|
||||
|
||||
// group = new Group;
|
||||
// frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::PERSPECTIVE);
|
||||
// frame->translation_.z = 0.1;
|
||||
// frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
// group->attach(frame);
|
||||
// persp = new Frame(Frame::GROUP, Frame::LARGE, Frame::NONE);
|
||||
// persp->translation_.z = 0.1;
|
||||
// persp->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
// group->attach(persp);
|
||||
// frames_[View::LAYER]->attach(group);
|
||||
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::GROUP, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
void SessionGroupSource::init()
|
||||
{
|
||||
if ( resolution_.x > 0.f && resolution_.y > 0.f ) {
|
||||
|
||||
session_->setResolution( resolution_ );
|
||||
|
||||
// update to draw framebuffer
|
||||
session_->update( dt_ );
|
||||
|
||||
// get the texture index from framebuffer of session, apply it to the surface
|
||||
sessionsurface_->setTextureIndex( session_->frame()->texture() );
|
||||
texturesurface_->setTextureIndex( session_->frame()->texture() );
|
||||
|
||||
// create Frame buffer matching size of session
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( session_->frame()->resolution());
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( session_->frame()->resolution() );
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// icon in mixing view
|
||||
overlays_[View::MIXING]->attach( new Symbol(Symbol::SESSION, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
overlays_[View::LAYER]->attach( new Symbol(Symbol::SESSION, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// wait for all sources to init
|
||||
wait_for_sources_ = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SessionSource::setActive (bool on)
|
||||
{
|
||||
Source::setActive(on);
|
||||
|
||||
// change status of session (recursive change of internal sources)
|
||||
if (session_ != nullptr)
|
||||
session_->setActive(active_);
|
||||
}
|
||||
|
||||
|
||||
void SessionSource::update(float dt)
|
||||
{
|
||||
if (session_ == nullptr)
|
||||
loadFailed_ = true;
|
||||
|
||||
// update content
|
||||
if (active_)
|
||||
session_->update(dt);
|
||||
|
||||
// delete a source which failed
|
||||
if (session_->failedSource() != nullptr) {
|
||||
session_->deleteSource(session_->failedSource());
|
||||
// fail session if all sources failed
|
||||
if ( session_->numSource() < 1)
|
||||
loadFailed_ = true;
|
||||
}
|
||||
|
||||
Source::update(dt);
|
||||
}
|
||||
|
||||
void SessionSource::render()
|
||||
{
|
||||
if (!initialized_)
|
||||
init();
|
||||
else {
|
||||
// render the sesion into frame buffer
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
|
||||
renderbuffer_->begin();
|
||||
sessionsurface_->draw(glm::identity<glm::mat4>(), projection);
|
||||
renderbuffer_->end();
|
||||
// done init
|
||||
initialized_ = true;
|
||||
Log::Info("Source Group (%d x %d).", int(renderbuffer->resolution().x), int(renderbuffer->resolution().y) );
|
||||
}
|
||||
}
|
||||
|
||||
void SessionSource::accept(Visitor& v)
|
||||
bool SessionGroupSource::import(Source *source)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if ( session_ )
|
||||
{
|
||||
SourceList::iterator its = session_->addSource(source);
|
||||
if (its != session_->end())
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SessionGroupSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
v.visit(*this);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
RenderSource::RenderSource(Session *session) : Source(), session_(session)
|
||||
|
||||
RenderSource::RenderSource() : Source(), session_(nullptr)
|
||||
{
|
||||
// create surface:
|
||||
sessionsurface_ = new Surface(processingshader_);
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::RENDER, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
RenderSource::~RenderSource()
|
||||
{
|
||||
// delete surface
|
||||
delete sessionsurface_;
|
||||
}
|
||||
|
||||
bool RenderSource::failed() const
|
||||
{
|
||||
return session_ == nullptr;
|
||||
if (initialized_ && session_!=nullptr)
|
||||
return renderbuffer_->resolution() != session_->frame()->resolution();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint RenderSource::texture() const
|
||||
{
|
||||
if (session_ == nullptr)
|
||||
return Resource::getTextureBlack();
|
||||
else
|
||||
if (session_ && session_->frame())
|
||||
return session_->frame()->texture();
|
||||
}
|
||||
|
||||
void RenderSource::replaceRenderingShader()
|
||||
{
|
||||
sessionsurface_->replaceShader(renderingshader_);
|
||||
else
|
||||
return Resource::getTextureBlack(); // getTextureTransparent ?
|
||||
}
|
||||
|
||||
void RenderSource::init()
|
||||
{
|
||||
if (session_ == nullptr)
|
||||
return;
|
||||
|
||||
if (session_ && session_->frame()->texture() != Resource::getTextureBlack()) {
|
||||
if (session_ && session_->frame() && session_->frame()->texture() != Resource::getTextureBlack()) {
|
||||
|
||||
FrameBuffer *fb = session_->frame();
|
||||
|
||||
// get the texture index from framebuffer of view, apply it to the surface
|
||||
sessionsurface_->setTextureIndex( fb->texture() );
|
||||
texturesurface_->setTextureIndex( fb->texture() );
|
||||
|
||||
// create Frame buffer matching size of output session
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( fb->resolution());
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( fb->resolution() );
|
||||
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// icon in mixing view
|
||||
overlays_[View::MIXING]->attach( new Symbol(Symbol::RENDER, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
overlays_[View::LAYER]->attach( new Symbol(Symbol::RENDER, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
|
||||
Log::Info("Source Render linked to session (%d x %d).", int(fb->resolution().x), int(fb->resolution().y) );
|
||||
}
|
||||
}
|
||||
|
||||
void RenderSource::render()
|
||||
{
|
||||
if (!initialized_)
|
||||
init();
|
||||
else {
|
||||
// render the view into frame buffer
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
|
||||
renderbuffer_->begin();
|
||||
sessionsurface_->draw(glm::identity<glm::mat4>(), projection);
|
||||
renderbuffer_->end();
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 RenderSource::resolution() const
|
||||
{
|
||||
if (initialized_)
|
||||
return renderbuffer_->resolution();
|
||||
else if (session_ && session_->frame())
|
||||
return session_->frame()->resolution();
|
||||
else
|
||||
return glm::vec3(0.f);
|
||||
}
|
||||
|
||||
void RenderSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
v.visit(*this);
|
||||
// if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
@@ -2,62 +2,96 @@
|
||||
#define SESSIONSOURCE_H
|
||||
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include "Source.h"
|
||||
|
||||
class SessionSource : public Source
|
||||
{
|
||||
public:
|
||||
SessionSource();
|
||||
~SessionSource();
|
||||
virtual ~SessionSource();
|
||||
|
||||
// implementation of source API
|
||||
void update (float dt) override;
|
||||
void setActive (bool on) override;
|
||||
void render() override;
|
||||
bool failed() const override;
|
||||
uint texture() const override;
|
||||
void accept (Visitor& v) override;
|
||||
bool failed () const override;
|
||||
uint texture () const override;
|
||||
|
||||
// Session Source specific interface
|
||||
void load(const std::string &p);
|
||||
Session *detach();
|
||||
|
||||
inline std::string path() const { return path_; }
|
||||
inline Session *session() const { return session_; }
|
||||
|
||||
protected:
|
||||
|
||||
void init() override;
|
||||
void replaceRenderingShader() override;
|
||||
static void loadSession(const std::string& filename, SessionSource *source);
|
||||
|
||||
Surface *sessionsurface_;
|
||||
std::string path_;
|
||||
Session *session_;
|
||||
std::atomic<bool> failed_;
|
||||
};
|
||||
|
||||
std::atomic<bool> loadFailed_;
|
||||
std::atomic<bool> loadFinished_;
|
||||
class SessionFileSource : public SessionSource
|
||||
{
|
||||
public:
|
||||
SessionFileSource();
|
||||
|
||||
// implementation of source API
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
// SessionFile Source specific interface
|
||||
void load(const std::string &p = "", uint recursion = 0);
|
||||
|
||||
inline std::string path() const { return path_; }
|
||||
glm::ivec2 icon() const override { return glm::ivec2(19, 6); }
|
||||
|
||||
protected:
|
||||
|
||||
void init() override;
|
||||
|
||||
std::string path_;
|
||||
std::atomic<bool> wait_for_sources_;
|
||||
std::future<Session *> sessionLoader_;
|
||||
};
|
||||
|
||||
class SessionGroupSource : public SessionSource
|
||||
{
|
||||
public:
|
||||
SessionGroupSource();
|
||||
|
||||
// implementation of source API
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
// SessionGroup Source specific interface
|
||||
inline void setResolution (glm::vec3 v) { resolution_ = v; }
|
||||
|
||||
// import a source
|
||||
bool import(Source *source);
|
||||
|
||||
glm::ivec2 icon() const override { return glm::ivec2(10, 6); }
|
||||
|
||||
protected:
|
||||
|
||||
void init() override;
|
||||
glm::vec3 resolution_;
|
||||
};
|
||||
|
||||
|
||||
class RenderSource : public Source
|
||||
{
|
||||
public:
|
||||
RenderSource(Session *session);
|
||||
~RenderSource();
|
||||
RenderSource();
|
||||
|
||||
// implementation of source API
|
||||
void render() override;
|
||||
bool failed() const override;
|
||||
bool failed () const override;
|
||||
uint texture() const override;
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
inline Session *session () const { return session_; }
|
||||
inline void setSession (Session *se) { session_ = se; }
|
||||
|
||||
glm::vec3 resolution() const;
|
||||
|
||||
glm::ivec2 icon() const override { return glm::ivec2(0, 2); }
|
||||
|
||||
protected:
|
||||
|
||||
void init() override;
|
||||
void replaceRenderingShader() override;
|
||||
Surface *sessionsurface_;
|
||||
Session *session_;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,26 +1,89 @@
|
||||
#include "SessionVisitor.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "defines.h"
|
||||
#include "Scene.h"
|
||||
#include "Primitives.h"
|
||||
#include "Mesh.h"
|
||||
#include "Decorations.h"
|
||||
#include "Source.h"
|
||||
#include "MediaSource.h"
|
||||
#include "Session.h"
|
||||
#include "SessionSource.h"
|
||||
#include "PatternSource.h"
|
||||
#include "DeviceSource.h"
|
||||
#include "NetworkSource.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "MediaPlayer.h"
|
||||
#include "MixingGroup.h"
|
||||
#include "SystemToolkit.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
using namespace tinyxml2;
|
||||
|
||||
|
||||
bool SessionVisitor::saveSession(const std::string& filename, Session *session)
|
||||
{
|
||||
// impose C locale
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
// creation of XML doc
|
||||
XMLDocument xmlDoc;
|
||||
|
||||
XMLElement *rootnode = xmlDoc.NewElement(APP_NAME);
|
||||
rootnode->SetAttribute("major", XML_VERSION_MAJOR);
|
||||
rootnode->SetAttribute("minor", XML_VERSION_MINOR);
|
||||
rootnode->SetAttribute("size", session->numSource());
|
||||
rootnode->SetAttribute("date", SystemToolkit::date_time_string().c_str());
|
||||
rootnode->SetAttribute("resolution", session->frame()->info().c_str());
|
||||
xmlDoc.InsertEndChild(rootnode);
|
||||
|
||||
// 1. list of sources
|
||||
XMLElement *sessionNode = xmlDoc.NewElement("Session");
|
||||
xmlDoc.InsertEndChild(sessionNode);
|
||||
SessionVisitor sv(&xmlDoc, sessionNode);
|
||||
for (auto iter = session->begin(); iter != session->end(); iter++, sv.setRoot(sessionNode) )
|
||||
// source visitor
|
||||
(*iter)->accept(sv);
|
||||
|
||||
// 2. config of views
|
||||
XMLElement *views = xmlDoc.NewElement("Views");
|
||||
xmlDoc.InsertEndChild(views);
|
||||
{
|
||||
XMLElement *mixing = xmlDoc.NewElement( "Mixing" );
|
||||
mixing->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::MIXING), &xmlDoc));
|
||||
views->InsertEndChild(mixing);
|
||||
|
||||
XMLElement *geometry = xmlDoc.NewElement( "Geometry" );
|
||||
geometry->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::GEOMETRY), &xmlDoc));
|
||||
views->InsertEndChild(geometry);
|
||||
|
||||
XMLElement *layer = xmlDoc.NewElement( "Layer" );
|
||||
layer->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::LAYER), &xmlDoc));
|
||||
views->InsertEndChild(layer);
|
||||
|
||||
XMLElement *appearance = xmlDoc.NewElement( "Texture" );
|
||||
appearance->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::TEXTURE), &xmlDoc));
|
||||
views->InsertEndChild(appearance);
|
||||
|
||||
XMLElement *render = xmlDoc.NewElement( "Rendering" );
|
||||
render->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::RENDERING), &xmlDoc));
|
||||
views->InsertEndChild(render);
|
||||
}
|
||||
|
||||
// save file to disk
|
||||
return ( XMLSaveDoc(&xmlDoc, filename) );
|
||||
}
|
||||
|
||||
SessionVisitor::SessionVisitor(tinyxml2::XMLDocument *doc,
|
||||
tinyxml2::XMLElement *root,
|
||||
bool recursive) : Visitor(), xmlCurrent_(root), recursive_(recursive)
|
||||
bool recursive) : Visitor(), recursive_(recursive), xmlCurrent_(root)
|
||||
{
|
||||
// impose C locale
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
if (doc == nullptr)
|
||||
xmlDoc_ = new XMLDocument;
|
||||
else
|
||||
@@ -31,6 +94,7 @@ tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument *
|
||||
{
|
||||
XMLElement *newelement = doc->NewElement("Node");
|
||||
newelement->SetAttribute("visible", n.visible_);
|
||||
newelement->SetAttribute("id", n.id());
|
||||
|
||||
XMLElement *scale = doc->NewElement("scale");
|
||||
scale->InsertEndChild( XMLElementFromGLM(doc, n.scale_) );
|
||||
@@ -44,6 +108,10 @@ tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument *
|
||||
rotation->InsertEndChild( XMLElementFromGLM(doc, n.rotation_) );
|
||||
newelement->InsertEndChild(rotation);
|
||||
|
||||
XMLElement *crop = doc->NewElement("crop");
|
||||
crop->InsertEndChild( XMLElementFromGLM(doc, n.crop_) );
|
||||
newelement->InsertEndChild(crop);
|
||||
|
||||
return newelement;
|
||||
}
|
||||
|
||||
@@ -66,7 +134,7 @@ void SessionVisitor::visit(Group &n)
|
||||
if (recursive_) {
|
||||
// loop over members of a group
|
||||
XMLElement *group = xmlCurrent_;
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); node++) {
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
|
||||
(*node)->accept(*this);
|
||||
// revert to group as current
|
||||
xmlCurrent_ = group;
|
||||
@@ -110,7 +178,7 @@ void SessionVisitor::visit(Primitive &n)
|
||||
}
|
||||
|
||||
|
||||
void SessionVisitor::visit(Surface &n)
|
||||
void SessionVisitor::visit(Surface &)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -126,7 +194,7 @@ void SessionVisitor::visit(ImageSurface &n)
|
||||
xmlCurrent_->InsertEndChild(image);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(FrameBufferSurface &n)
|
||||
void SessionVisitor::visit(FrameBufferSurface &)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "FrameBufferSurface");
|
||||
@@ -143,11 +211,35 @@ void SessionVisitor::visit(MediaSurface &n)
|
||||
void SessionVisitor::visit(MediaPlayer &n)
|
||||
{
|
||||
XMLElement *newelement = xmlDoc_->NewElement("MediaPlayer");
|
||||
newelement->SetAttribute("play", n.isPlaying());
|
||||
newelement->SetAttribute("loop", (int) n.loop());
|
||||
newelement->SetAttribute("speed", n.playSpeed());
|
||||
newelement->SetAttribute("id", n.id());
|
||||
|
||||
// TODO Segments
|
||||
if (!n.isImage()) {
|
||||
newelement->SetAttribute("play", n.isPlaying());
|
||||
newelement->SetAttribute("loop", (int) n.loop());
|
||||
newelement->SetAttribute("speed", n.playSpeed());
|
||||
newelement->SetAttribute("software_decoding", n.softwareDecodingForced());
|
||||
|
||||
// timeline
|
||||
XMLElement *timelineelement = xmlDoc_->NewElement("Timeline");
|
||||
|
||||
// gaps in timeline
|
||||
XMLElement *gapselement = xmlDoc_->NewElement("Gaps");
|
||||
TimeIntervalSet gaps = n.timeline()->gaps();
|
||||
for( auto it = gaps.begin(); it!= gaps.end(); it++) {
|
||||
XMLElement *g = xmlDoc_->NewElement("Interval");
|
||||
g->SetAttribute("begin", (*it).begin);
|
||||
g->SetAttribute("end", (*it).end);
|
||||
gapselement->InsertEndChild(g);
|
||||
}
|
||||
timelineelement->InsertEndChild(gapselement);
|
||||
|
||||
// fading in timeline
|
||||
XMLElement *fadingelement = xmlDoc_->NewElement("Fading");
|
||||
XMLElement *array = XMLElementEncodeArray(xmlDoc_, n.timeline()->fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
fadingelement->InsertEndChild(array);
|
||||
timelineelement->InsertEndChild(fadingelement);
|
||||
newelement->InsertEndChild(timelineelement);
|
||||
}
|
||||
|
||||
xmlCurrent_->InsertEndChild(newelement);
|
||||
}
|
||||
@@ -156,6 +248,7 @@ void SessionVisitor::visit(Shader &n)
|
||||
{
|
||||
// Shader of a simple type
|
||||
xmlCurrent_->SetAttribute("type", "Shader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
|
||||
XMLElement *color = xmlDoc_->NewElement("color");
|
||||
color->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.color) );
|
||||
@@ -171,18 +264,35 @@ void SessionVisitor::visit(ImageShader &n)
|
||||
{
|
||||
// Shader of a textured type
|
||||
xmlCurrent_->SetAttribute("type", "ImageShader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
|
||||
XMLElement *uniforms = xmlDoc_->NewElement("uniforms");
|
||||
uniforms->SetAttribute("stipple", n.stipple);
|
||||
uniforms->SetAttribute("mask", n.mask);
|
||||
xmlCurrent_->InsertEndChild(uniforms);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(MaskShader &n)
|
||||
{
|
||||
// Shader of a mask type
|
||||
xmlCurrent_->SetAttribute("type", "MaskShader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
xmlCurrent_->SetAttribute("mode", n.mode);
|
||||
xmlCurrent_->SetAttribute("shape", n.shape);
|
||||
|
||||
XMLElement *uniforms = xmlDoc_->NewElement("uniforms");
|
||||
uniforms->SetAttribute("blur", n.blur);
|
||||
uniforms->SetAttribute("option", n.option);
|
||||
XMLElement *size = xmlDoc_->NewElement("size");
|
||||
size->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.size) );
|
||||
uniforms->InsertEndChild(size);
|
||||
xmlCurrent_->InsertEndChild(uniforms);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(ImageProcessingShader &n)
|
||||
{
|
||||
// Shader of a textured type
|
||||
xmlCurrent_->SetAttribute("type", "ImageProcessingShader");
|
||||
xmlCurrent_->SetAttribute("id", n.id());
|
||||
|
||||
XMLElement *filter = xmlDoc_->NewElement("uniforms");
|
||||
filter->SetAttribute("brightness", n.brightness);
|
||||
@@ -194,7 +304,7 @@ void SessionVisitor::visit(ImageProcessingShader &n)
|
||||
filter->SetAttribute("nbColors", n.nbColors);
|
||||
filter->SetAttribute("invert", n.invert);
|
||||
filter->SetAttribute("chromadelta", n.chromadelta);
|
||||
filter->SetAttribute("filterid", n.filterid);
|
||||
filter->SetAttribute("filter", n.filterid);
|
||||
xmlCurrent_->InsertEndChild(filter);
|
||||
|
||||
XMLElement *gamma = xmlDoc_->NewElement("gamma");
|
||||
@@ -217,24 +327,14 @@ void SessionVisitor::visit(LineStrip &n)
|
||||
xmlCurrent_->SetAttribute("type", "LineStrip");
|
||||
|
||||
XMLElement *points_node = xmlDoc_->NewElement("points");
|
||||
std::vector<glm::vec3> points = n.getPoints();
|
||||
for(size_t i = 0; i < points.size(); ++i)
|
||||
std::vector<glm::vec2> path = n.path();
|
||||
for(size_t i = 0; i < path.size(); ++i)
|
||||
{
|
||||
XMLElement *p = XMLElementFromGLM(xmlDoc_, points[i]);
|
||||
XMLElement *p = XMLElementFromGLM(xmlDoc_, path[i]);
|
||||
p->SetAttribute("index", (int) i);
|
||||
points_node->InsertEndChild(p);
|
||||
}
|
||||
xmlCurrent_->InsertEndChild(points_node);
|
||||
|
||||
XMLElement *colors_node = xmlDoc_->NewElement("colors");
|
||||
std::vector<glm::vec4> colors = n.getColors();
|
||||
for(size_t i = 0; i < colors.size(); ++i)
|
||||
{
|
||||
XMLElement *p = XMLElementFromGLM(xmlDoc_, colors[i]);
|
||||
p->SetAttribute("index", (int) i);
|
||||
colors_node->InsertEndChild(p);
|
||||
}
|
||||
xmlCurrent_->InsertEndChild(colors_node);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(LineSquare &)
|
||||
@@ -244,16 +344,6 @@ void SessionVisitor::visit(LineSquare &)
|
||||
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(LineCircle &n)
|
||||
{
|
||||
// Node of a different type
|
||||
xmlCurrent_->SetAttribute("type", "LineCircle");
|
||||
|
||||
// XMLElement *color = xmlDoc_->NewElement("color");
|
||||
// color->InsertEndChild( XMLElementFromGLM(xmlDoc_, n.getColor()) );
|
||||
// xmlCurrent_->InsertEndChild(color);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit(Mesh &n)
|
||||
{
|
||||
// Node of a different type
|
||||
@@ -295,7 +385,9 @@ void SessionVisitor::visit(Scene &n)
|
||||
void SessionVisitor::visit (Source& s)
|
||||
{
|
||||
XMLElement *sourceNode = xmlDoc_->NewElement( "Source" );
|
||||
sourceNode->SetAttribute("id", s.id());
|
||||
sourceNode->SetAttribute("name", s.name().c_str() );
|
||||
sourceNode->SetAttribute("locked", s.locked() );
|
||||
|
||||
// insert into hierarchy
|
||||
xmlCurrent_->InsertFirstChild(sourceNode);
|
||||
@@ -312,15 +404,52 @@ void SessionVisitor::visit (Source& s)
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.groupNode(View::LAYER)->accept(*this);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Texture" );
|
||||
xmlCurrent_->SetAttribute("mirrored", s.textureMirrored() );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.groupNode(View::TEXTURE)->accept(*this);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Blending" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.blendingShader()->accept(*this);
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "Mask" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.maskShader()->accept(*this);
|
||||
// if we are saving a pain mask
|
||||
if (s.maskShader()->mode == MaskShader::PAINT) {
|
||||
// get the mask previously stored
|
||||
FrameBufferImage *img = s.getMask();
|
||||
if (img != nullptr) {
|
||||
// get the jpeg encoded buffer
|
||||
FrameBufferImage::jpegBuffer jpgimg = img->getJpeg();
|
||||
if (jpgimg.buffer != nullptr) {
|
||||
// fill the xml array with jpeg buffer
|
||||
XMLElement *array = XMLElementEncodeArray(xmlDoc_, jpgimg.buffer, jpgimg.len);
|
||||
// free the buffer
|
||||
free(jpgimg.buffer);
|
||||
// if we could create the array
|
||||
if (array) {
|
||||
// create an Image node to store the mask image
|
||||
XMLElement *imageelement = xmlDoc_->NewElement("Image");
|
||||
imageelement->InsertEndChild(array);
|
||||
xmlCurrent_->InsertEndChild(imageelement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "ImageProcessing" );
|
||||
xmlCurrent_->SetAttribute("enabled", s.imageProcessingEnabled());
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.processingShader()->accept(*this);
|
||||
|
||||
if (s.mixingGroup()) {
|
||||
xmlCurrent_ = xmlDoc_->NewElement( "MixingGroup" );
|
||||
sourceNode->InsertEndChild(xmlCurrent_);
|
||||
s.mixingGroup()->accept(*this);
|
||||
}
|
||||
|
||||
xmlCurrent_ = sourceNode; // parent for next visits (other subtypes of Source)
|
||||
}
|
||||
|
||||
@@ -336,9 +465,11 @@ void SessionVisitor::visit (MediaSource& s)
|
||||
s.mediaplayer()->accept(*this);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (SessionSource& s)
|
||||
void SessionVisitor::visit (SessionFileSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "SessionSource");
|
||||
if (s.session() != nullptr)
|
||||
xmlCurrent_->SetAttribute("fading", s.session()->fading());
|
||||
|
||||
XMLElement *path = xmlDoc_->NewElement("path");
|
||||
xmlCurrent_->InsertEndChild(path);
|
||||
@@ -346,7 +477,23 @@ void SessionVisitor::visit (SessionSource& s)
|
||||
path->InsertEndChild( text );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (RenderSource& s)
|
||||
void SessionVisitor::visit (SessionGroupSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "GroupSource");
|
||||
|
||||
Session *se = s.session();
|
||||
|
||||
XMLElement *sessionNode = xmlDoc_->NewElement("Session");
|
||||
xmlCurrent_->InsertEndChild(sessionNode);
|
||||
|
||||
for (auto iter = se->begin(); iter != se->end(); iter++){
|
||||
setRoot(sessionNode);
|
||||
(*iter)->accept(*this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (RenderSource&)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "RenderSource");
|
||||
}
|
||||
@@ -360,3 +507,123 @@ void SessionVisitor::visit (CloneSource& s)
|
||||
XMLText *text = xmlDoc_->NewText( s.origin()->name().c_str() );
|
||||
origin->InsertEndChild( text );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (PatternSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "PatternSource");
|
||||
xmlCurrent_->SetAttribute("pattern", s.pattern()->type() );
|
||||
|
||||
XMLElement *resolution = xmlDoc_->NewElement("resolution");
|
||||
resolution->InsertEndChild( XMLElementFromGLM(xmlDoc_, s.pattern()->resolution() ) );
|
||||
xmlCurrent_->InsertEndChild(resolution);
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (DeviceSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "DeviceSource");
|
||||
xmlCurrent_->SetAttribute("device", s.device().c_str() );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (NetworkSource& s)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("type", "NetworkSource");
|
||||
xmlCurrent_->SetAttribute("connection", s.connection().c_str() );
|
||||
}
|
||||
|
||||
void SessionVisitor::visit (MixingGroup& g)
|
||||
{
|
||||
xmlCurrent_->SetAttribute("size", g.size());
|
||||
|
||||
for (auto it = g.begin(); it != g.end(); it++) {
|
||||
XMLElement *sour = xmlDoc_->NewElement("source");
|
||||
sour->SetAttribute("id", (*it)->id());
|
||||
xmlCurrent_->InsertEndChild(sour);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SessionVisitor::getClipboard(SourceList list)
|
||||
{
|
||||
std::string x = "";
|
||||
|
||||
if (!list.empty()) {
|
||||
|
||||
// create xml doc and root node
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
|
||||
selectionNode->SetAttribute("size", (int) list.size());
|
||||
xmlDoc.InsertEndChild(selectionNode);
|
||||
|
||||
// fill doc by visiting sources
|
||||
SourceList selection_clones_;
|
||||
SessionVisitor sv(&xmlDoc, selectionNode);
|
||||
for (auto iter = list.begin(); iter != list.end(); iter++, sv.setRoot(selectionNode) ){
|
||||
// start with clones
|
||||
CloneSource *clone = dynamic_cast<CloneSource *>(*iter);
|
||||
if (clone)
|
||||
(*iter)->accept(sv);
|
||||
else
|
||||
selection_clones_.push_back(*iter);
|
||||
}
|
||||
// add others in front
|
||||
for (auto iter = selection_clones_.begin(); iter != selection_clones_.end(); iter++, sv.setRoot(selectionNode) ){
|
||||
(*iter)->accept(sv);
|
||||
}
|
||||
|
||||
// get compact string
|
||||
tinyxml2::XMLPrinter xmlPrint(0, true);
|
||||
xmlDoc.Print( &xmlPrint );
|
||||
x = xmlPrint.CStr();
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
std::string SessionVisitor::getClipboard(Source *s)
|
||||
{
|
||||
std::string x = "";
|
||||
|
||||
if (s != nullptr) {
|
||||
// create xml doc and root node
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
|
||||
selectionNode->SetAttribute("size", 1);
|
||||
xmlDoc.InsertEndChild(selectionNode);
|
||||
|
||||
// visit source
|
||||
SessionVisitor sv(&xmlDoc, selectionNode);
|
||||
s->accept(sv);
|
||||
|
||||
// get compact string
|
||||
tinyxml2::XMLPrinter xmlPrint(0, true);
|
||||
xmlDoc.Print( &xmlPrint );
|
||||
x = xmlPrint.CStr();
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
std::string SessionVisitor::getClipboard(ImageProcessingShader *s)
|
||||
{
|
||||
std::string x = "";
|
||||
|
||||
if (s != nullptr) {
|
||||
// create xml doc and root node
|
||||
tinyxml2::XMLDocument xmlDoc;
|
||||
tinyxml2::XMLElement *selectionNode = xmlDoc.NewElement(APP_NAME);
|
||||
xmlDoc.InsertEndChild(selectionNode);
|
||||
|
||||
tinyxml2::XMLElement *imgprocNode = xmlDoc.NewElement( "ImageProcessing" );
|
||||
selectionNode->InsertEndChild(imgprocNode);
|
||||
|
||||
// visit source
|
||||
SessionVisitor sv(&xmlDoc, imgprocNode);
|
||||
s->accept(sv);
|
||||
|
||||
// get compact string
|
||||
tinyxml2::XMLPrinter xmlPrint(0, true);
|
||||
xmlDoc.Print( &xmlPrint );
|
||||
x = xmlPrint.CStr();
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
#include "Visitor.h"
|
||||
#include "tinyxml2Toolkit.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
class Session;
|
||||
|
||||
class SessionVisitor : public Visitor {
|
||||
|
||||
@@ -15,7 +18,13 @@ public:
|
||||
tinyxml2::XMLElement *root = nullptr,
|
||||
bool recursive = false);
|
||||
|
||||
inline tinyxml2::XMLDocument *doc() const { return xmlDoc_; }
|
||||
inline void setRoot(tinyxml2::XMLElement *root) { xmlCurrent_ = root; }
|
||||
|
||||
static bool saveSession(const std::string& filename, Session *session);
|
||||
|
||||
static std::string getClipboard(SourceList list);
|
||||
static std::string getClipboard(Source *s);
|
||||
static std::string getClipboard(ImageProcessingShader *s);
|
||||
|
||||
// Elements of Scene
|
||||
void visit(Scene& n) override;
|
||||
@@ -23,13 +32,12 @@ public:
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
void visit(Primitive& n) override;
|
||||
void visit(Surface& n) override;
|
||||
void visit(Surface&) override;
|
||||
void visit(ImageSurface& n) override;
|
||||
void visit(MediaSurface& n) override;
|
||||
void visit(FrameBufferSurface& n) override;
|
||||
void visit(FrameBufferSurface&) 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;
|
||||
|
||||
@@ -37,14 +45,22 @@ public:
|
||||
void visit(MediaPlayer& n) override;
|
||||
void visit(Shader& n) override;
|
||||
void visit(ImageShader& n) override;
|
||||
void visit(MaskShader& n) override;
|
||||
void visit(ImageProcessingShader& n) override;
|
||||
|
||||
// Sources
|
||||
void visit (Source& s) override;
|
||||
void visit (MediaSource& s) override;
|
||||
void visit (SessionSource& s) override;
|
||||
void visit (RenderSource& s) override;
|
||||
void visit (SessionFileSource& s) override;
|
||||
void visit (SessionGroupSource& s) override;
|
||||
void visit (RenderSource&) override;
|
||||
void visit (CloneSource& s) override;
|
||||
void visit (PatternSource& s) override;
|
||||
void visit (DeviceSource& s) override;
|
||||
void visit (NetworkSource& s) override;
|
||||
void visit (MixingGroup& s) override;
|
||||
|
||||
protected:
|
||||
static tinyxml2::XMLElement *NodeToXML(Node &n, tinyxml2::XMLDocument *doc);
|
||||
};
|
||||
|
||||
|
||||
159
Settings.cpp
159
Settings.cpp
@@ -1,5 +1,6 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
using namespace std;
|
||||
|
||||
#include <tinyxml2.h>
|
||||
@@ -12,19 +13,26 @@ using namespace tinyxml2;
|
||||
|
||||
|
||||
Settings::Application Settings::application;
|
||||
|
||||
static string settingsFilename = "";
|
||||
|
||||
void Settings::Save()
|
||||
{
|
||||
// impose C locale for all app
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
XMLDocument xmlDoc;
|
||||
XMLDeclaration *pDec = xmlDoc.NewDeclaration();
|
||||
xmlDoc.InsertFirstChild(pDec);
|
||||
|
||||
XMLElement *pRoot = xmlDoc.NewElement(application.name.c_str());
|
||||
#ifdef VIMIX_VERSION_MAJOR
|
||||
pRoot->SetAttribute("major", VIMIX_VERSION_MAJOR);
|
||||
pRoot->SetAttribute("minor", VIMIX_VERSION_MINOR);
|
||||
xmlDoc.InsertEndChild(pRoot);
|
||||
#endif
|
||||
|
||||
string comment = "Settings for " + application.name;
|
||||
comment += "Version " + std::to_string(APP_VERSION_MAJOR) + "." + std::to_string(APP_VERSION_MINOR);
|
||||
XMLComment *pComment = xmlDoc.NewComment(comment.c_str());
|
||||
pRoot->InsertEndChild(pComment);
|
||||
|
||||
@@ -57,14 +65,19 @@ void Settings::Save()
|
||||
applicationNode->SetAttribute("accent_color", application.accent_color);
|
||||
applicationNode->SetAttribute("pannel_stick", application.pannel_stick);
|
||||
applicationNode->SetAttribute("smooth_transition", application.smooth_transition);
|
||||
applicationNode->SetAttribute("smooth_cursor", application.smooth_cursor);
|
||||
applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view);
|
||||
applicationNode->SetAttribute("accept_connections", application.accept_connections);
|
||||
pRoot->InsertEndChild(applicationNode);
|
||||
|
||||
// Widgets
|
||||
XMLElement *widgetsNode = xmlDoc.NewElement( "Widgets" );
|
||||
widgetsNode->SetAttribute("preview", application.widget.preview);
|
||||
widgetsNode->SetAttribute("history", application.widget.history);
|
||||
widgetsNode->SetAttribute("media_player", application.widget.media_player);
|
||||
widgetsNode->SetAttribute("shader_editor", application.widget.shader_editor);
|
||||
widgetsNode->SetAttribute("stats", application.widget.stats);
|
||||
widgetsNode->SetAttribute("stats_timer", application.widget.stats_timer);
|
||||
widgetsNode->SetAttribute("stats_corner", application.widget.stats_corner);
|
||||
widgetsNode->SetAttribute("logs", application.widget.logs);
|
||||
widgetsNode->SetAttribute("toolbox", application.widget.toolbox);
|
||||
@@ -75,6 +88,7 @@ void Settings::Save()
|
||||
RenderNode->SetAttribute("vsync", application.render.vsync);
|
||||
RenderNode->SetAttribute("multisampling", application.render.multisampling);
|
||||
RenderNode->SetAttribute("blit", application.render.blit);
|
||||
RenderNode->SetAttribute("gpu_decoding", application.render.gpu_decoding);
|
||||
RenderNode->SetAttribute("ratio", application.render.ratio);
|
||||
RenderNode->SetAttribute("res", application.render.res);
|
||||
pRoot->InsertEndChild(RenderNode);
|
||||
@@ -88,34 +102,61 @@ void Settings::Save()
|
||||
|
||||
// Transition
|
||||
XMLElement *TransitionNode = xmlDoc.NewElement( "Transition" );
|
||||
TransitionNode->SetAttribute("auto_open", application.transition.auto_open);
|
||||
TransitionNode->SetAttribute("hide_windows", application.transition.hide_windows);
|
||||
TransitionNode->SetAttribute("cross_fade", application.transition.cross_fade);
|
||||
TransitionNode->SetAttribute("duration", application.transition.duration);
|
||||
TransitionNode->SetAttribute("profile", application.transition.profile);
|
||||
pRoot->InsertEndChild(TransitionNode);
|
||||
|
||||
// Source
|
||||
XMLElement *SourceConfNode = xmlDoc.NewElement( "Source" );
|
||||
SourceConfNode->SetAttribute("new_type", application.source.new_type);
|
||||
SourceConfNode->SetAttribute("ratio", application.source.ratio);
|
||||
SourceConfNode->SetAttribute("res", application.source.res);
|
||||
pRoot->InsertEndChild(SourceConfNode);
|
||||
|
||||
// Brush
|
||||
XMLElement *BrushNode = xmlDoc.NewElement( "Brush" );
|
||||
BrushNode->InsertEndChild( XMLElementFromGLM(&xmlDoc, application.brush) );
|
||||
pRoot->InsertEndChild(BrushNode);
|
||||
|
||||
// bloc connections
|
||||
{
|
||||
XMLElement *connectionsNode = xmlDoc.NewElement( "Connections" );
|
||||
|
||||
// map<int, std::string>::iterator iter;
|
||||
// for (iter=application.instance_names.begin(); iter != application.instance_names.end(); iter++)
|
||||
// {
|
||||
// XMLElement *connection = xmlDoc.NewElement( "Instance" );
|
||||
// connection->SetAttribute("name", iter->second.c_str());
|
||||
// connection->SetAttribute("id", iter->first);
|
||||
// connectionsNode->InsertEndChild(connection);
|
||||
// }
|
||||
pRoot->InsertEndChild(connectionsNode);
|
||||
}
|
||||
|
||||
// bloc views
|
||||
{
|
||||
XMLElement *viewsNode = xmlDoc.NewElement( "Views" );
|
||||
// save current view only if [mixing, geometry or layers]
|
||||
int v = application.current_view > 3 ? 1 : application.current_view;
|
||||
// save current view only if [mixing, geometry, layers, appearance]
|
||||
int v = application.current_view > 4 ? 1 : application.current_view;
|
||||
viewsNode->SetAttribute("current", v);
|
||||
viewsNode->SetAttribute("workspace", application.current_workspace);
|
||||
|
||||
map<int, Settings::ViewConfig>::iterator iter;
|
||||
for (iter=application.views.begin(); iter != application.views.end(); iter++)
|
||||
for (iter=application.views.begin(); iter != application.views.end(); ++iter)
|
||||
{
|
||||
const Settings::ViewConfig& v = iter->second;
|
||||
const Settings::ViewConfig& view_config = iter->second;
|
||||
|
||||
XMLElement *view = xmlDoc.NewElement( "View" );
|
||||
view->SetAttribute("name", v.name.c_str());
|
||||
view->SetAttribute("name", view_config.name.c_str());
|
||||
view->SetAttribute("id", iter->first);
|
||||
|
||||
XMLElement *scale = xmlDoc.NewElement("default_scale");
|
||||
scale->InsertEndChild( XMLElementFromGLM(&xmlDoc, v.default_scale) );
|
||||
scale->InsertEndChild( XMLElementFromGLM(&xmlDoc, view_config.default_scale) );
|
||||
view->InsertEndChild(scale);
|
||||
XMLElement *translation = xmlDoc.NewElement("default_translation");
|
||||
translation->InsertEndChild( XMLElementFromGLM(&xmlDoc, v.default_translation) );
|
||||
translation->InsertEndChild( XMLElementFromGLM(&xmlDoc, view_config.default_translation) );
|
||||
view->InsertEndChild(translation);
|
||||
|
||||
viewsNode->InsertEndChild(view);
|
||||
@@ -132,7 +173,7 @@ void Settings::Save()
|
||||
recentsession->SetAttribute("path", application.recentSessions.path.c_str());
|
||||
recentsession->SetAttribute("autoload", application.recentSessions.load_at_start);
|
||||
recentsession->SetAttribute("autosave", application.recentSessions.save_on_exit);
|
||||
recentsession->SetAttribute("valid", application.recentSessions.valid_file);
|
||||
recentsession->SetAttribute("valid", application.recentSessions.front_is_valid);
|
||||
for(auto it = application.recentSessions.filenames.begin();
|
||||
it != application.recentSessions.filenames.end(); it++) {
|
||||
XMLElement *fileNode = xmlDoc.NewElement("path");
|
||||
@@ -173,10 +214,14 @@ void Settings::Save()
|
||||
|
||||
XMLError eResult = xmlDoc.SaveFile(settingsFilename.c_str());
|
||||
XMLResultError(eResult);
|
||||
|
||||
}
|
||||
|
||||
void Settings::Load()
|
||||
{
|
||||
// impose C locale for all app
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
XMLDocument xmlDoc;
|
||||
if (settingsFilename.empty())
|
||||
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
|
||||
@@ -192,25 +237,39 @@ void Settings::Load()
|
||||
XMLElement *pRoot = xmlDoc.FirstChildElement(application.name.c_str());
|
||||
if (pRoot == nullptr) return;
|
||||
|
||||
if (application.name.compare( string( pRoot->Value() ) ) != 0 )
|
||||
// different root name
|
||||
// cancel on different root name
|
||||
if (application.name.compare( string( pRoot->Value() ) ) != 0 )
|
||||
return;
|
||||
|
||||
#ifdef VIMIX_VERSION_MAJOR
|
||||
// cancel on different version
|
||||
int version_major = -1, version_minor = -1;
|
||||
pRoot->QueryIntAttribute("major", &version_major);
|
||||
pRoot->QueryIntAttribute("minor", &version_minor);
|
||||
if (version_major != VIMIX_VERSION_MAJOR || version_minor != VIMIX_VERSION_MINOR)
|
||||
return;
|
||||
#endif
|
||||
|
||||
XMLElement * applicationNode = pRoot->FirstChildElement("Application");
|
||||
if (applicationNode != nullptr) {
|
||||
applicationNode->QueryFloatAttribute("scale", &application.scale);
|
||||
applicationNode->QueryIntAttribute("accent_color", &application.accent_color);
|
||||
applicationNode->QueryBoolAttribute("pannel_stick", &application.pannel_stick);
|
||||
applicationNode->QueryBoolAttribute("smooth_transition", &application.smooth_transition);
|
||||
applicationNode->QueryBoolAttribute("smooth_cursor", &application.smooth_cursor);
|
||||
applicationNode->QueryBoolAttribute("action_history_follow_view", &application.action_history_follow_view);
|
||||
applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections);
|
||||
}
|
||||
|
||||
// Widgets
|
||||
XMLElement * widgetsNode = pRoot->FirstChildElement("Widgets");
|
||||
if (widgetsNode != nullptr) {
|
||||
widgetsNode->QueryBoolAttribute("preview", &application.widget.preview);
|
||||
widgetsNode->QueryBoolAttribute("history", &application.widget.history);
|
||||
widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player);
|
||||
widgetsNode->QueryBoolAttribute("shader_editor", &application.widget.shader_editor);
|
||||
widgetsNode->QueryBoolAttribute("stats", &application.widget.stats);
|
||||
widgetsNode->QueryBoolAttribute("stats_timer", &application.widget.stats_timer);
|
||||
widgetsNode->QueryIntAttribute("stats_corner", &application.widget.stats_corner);
|
||||
widgetsNode->QueryBoolAttribute("logs", &application.widget.logs);
|
||||
widgetsNode->QueryBoolAttribute("toolbox", &application.widget.toolbox);
|
||||
@@ -222,6 +281,7 @@ void Settings::Load()
|
||||
rendernode->QueryIntAttribute("vsync", &application.render.vsync);
|
||||
rendernode->QueryIntAttribute("multisampling", &application.render.multisampling);
|
||||
rendernode->QueryBoolAttribute("blit", &application.render.blit);
|
||||
rendernode->QueryBoolAttribute("gpu_decoding", &application.render.gpu_decoding);
|
||||
rendernode->QueryIntAttribute("ratio", &application.render.ratio);
|
||||
rendernode->QueryIntAttribute("res", &application.render.res);
|
||||
}
|
||||
@@ -239,11 +299,18 @@ void Settings::Load()
|
||||
application.record.path = SystemToolkit::home_path();
|
||||
}
|
||||
|
||||
// Source
|
||||
XMLElement * sourceconfnode = pRoot->FirstChildElement("Source");
|
||||
if (sourceconfnode != nullptr) {
|
||||
sourceconfnode->QueryIntAttribute("new_type", &application.source.new_type);
|
||||
sourceconfnode->QueryIntAttribute("ratio", &application.source.ratio);
|
||||
sourceconfnode->QueryIntAttribute("res", &application.source.res);
|
||||
}
|
||||
|
||||
// Transition
|
||||
XMLElement * transitionnode = pRoot->FirstChildElement("Transition");
|
||||
if (transitionnode != nullptr) {
|
||||
transitionnode->QueryBoolAttribute("hide_windows", &application.transition.hide_windows);
|
||||
transitionnode->QueryBoolAttribute("auto_open", &application.transition.auto_open);
|
||||
transitionnode->QueryBoolAttribute("cross_fade", &application.transition.cross_fade);
|
||||
transitionnode->QueryFloatAttribute("duration", &application.transition.duration);
|
||||
transitionnode->QueryIntAttribute("profile", &application.transition.profile);
|
||||
@@ -273,13 +340,20 @@ void Settings::Load()
|
||||
}
|
||||
}
|
||||
|
||||
// Brush
|
||||
XMLElement * brushnode = pRoot->FirstChildElement("Brush");
|
||||
if (brushnode != nullptr) {
|
||||
tinyxml2::XMLElementToGLM( brushnode->FirstChildElement("vec3"), application.brush);
|
||||
}
|
||||
|
||||
// bloc views
|
||||
{
|
||||
application.views.clear(); // trash existing list
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Views");
|
||||
if (pElement)
|
||||
{
|
||||
application.views.clear(); // trash existing list
|
||||
pElement->QueryIntAttribute("current", &application.current_view);
|
||||
pElement->QueryIntAttribute("workspace", &application.current_workspace);
|
||||
|
||||
XMLElement* viewNode = pElement->FirstChildElement("View");
|
||||
for( ; viewNode ; viewNode=viewNode->NextSiblingElement())
|
||||
@@ -301,6 +375,22 @@ void Settings::Load()
|
||||
|
||||
}
|
||||
|
||||
// bloc Connections
|
||||
{
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Connections");
|
||||
if (pElement)
|
||||
{
|
||||
// XMLElement* connectionNode = pElement->FirstChildElement("Instance");
|
||||
// for( ; connectionNode ; connectionNode=connectionNode->NextSiblingElement())
|
||||
// {
|
||||
// int id = 0;
|
||||
// connectionNode->QueryIntAttribute("id", &id);
|
||||
// application.instance_names[id] = connectionNode->Attribute("name");
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// bloc history of recent
|
||||
{
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Recent");
|
||||
@@ -325,9 +415,9 @@ void Settings::Load()
|
||||
}
|
||||
pSession->QueryBoolAttribute("autoload", &application.recentSessions.load_at_start);
|
||||
pSession->QueryBoolAttribute("autosave", &application.recentSessions.save_on_exit);
|
||||
pSession->QueryBoolAttribute("valid", &application.recentSessions.valid_file);
|
||||
pSession->QueryBoolAttribute("valid", &application.recentSessions.front_is_valid);
|
||||
}
|
||||
// recent session filenames
|
||||
// recent session folders
|
||||
XMLElement * pFolder = pElement->FirstChildElement("Folder");
|
||||
if (pFolder)
|
||||
{
|
||||
@@ -361,7 +451,43 @@ void Settings::Load()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Settings::Lock()
|
||||
{
|
||||
|
||||
std::string lockfile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "lock");
|
||||
application.fresh_start = false;
|
||||
|
||||
FILE *file = fopen(lockfile.c_str(), "r");
|
||||
int l = 0;
|
||||
if (file) {
|
||||
if ( fscanf(file, "%d", &l) < 1)
|
||||
l = 0;
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
// not locked or file not existing
|
||||
if ( l < 1 ) {
|
||||
file = fopen(lockfile.c_str(), "w");
|
||||
if (file) {
|
||||
fprintf(file, "1");
|
||||
fclose(file);
|
||||
}
|
||||
application.fresh_start = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Settings::Unlock()
|
||||
{
|
||||
std::string lockfile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "lock");
|
||||
FILE *file = fopen(lockfile.c_str(), "w");
|
||||
if (file) {
|
||||
fprintf(file, "0");
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -376,3 +502,4 @@ void Settings::Check()
|
||||
|
||||
xmlDoc.Print();
|
||||
}
|
||||
|
||||
|
||||
75
Settings.h
75
Settings.h
@@ -15,8 +15,10 @@ struct WidgetsConfig
|
||||
{
|
||||
bool stats;
|
||||
int stats_corner;
|
||||
bool stats_timer;
|
||||
bool logs;
|
||||
bool preview;
|
||||
bool history;
|
||||
bool media_player;
|
||||
bool media_player_view;
|
||||
bool shader_editor;
|
||||
@@ -24,9 +26,11 @@ struct WidgetsConfig
|
||||
|
||||
WidgetsConfig() {
|
||||
stats = false;
|
||||
stats_timer = false;
|
||||
stats_corner = 1;
|
||||
logs = false;
|
||||
preview = false;
|
||||
history = false;
|
||||
media_player = false;
|
||||
media_player_view = true;
|
||||
shader_editor = false;
|
||||
@@ -77,40 +81,49 @@ struct History
|
||||
{
|
||||
std::string path;
|
||||
std::list<std::string> filenames;
|
||||
bool valid_file;
|
||||
bool front_is_valid;
|
||||
bool load_at_start;
|
||||
bool save_on_exit;
|
||||
bool changed;
|
||||
|
||||
History() {
|
||||
path = IMGUI_LABEL_RECENT_FILES;
|
||||
valid_file = false;
|
||||
front_is_valid = false;
|
||||
load_at_start = false;
|
||||
save_on_exit = false;
|
||||
changed = false;
|
||||
}
|
||||
void push(std::string filename) {
|
||||
void push(const std::string &filename) {
|
||||
if (filename.empty()) {
|
||||
valid_file = false;
|
||||
front_is_valid = false;
|
||||
return;
|
||||
}
|
||||
filenames.remove(filename);
|
||||
filenames.push_front(filename);
|
||||
if (filenames.size() > MAX_RECENT_HISTORY)
|
||||
filenames.pop_back();
|
||||
valid_file = true;
|
||||
front_is_valid = true;
|
||||
changed = true;
|
||||
}
|
||||
void remove(const std::string &filename) {
|
||||
if (filename.empty())
|
||||
return;
|
||||
if (filenames.front() == filename)
|
||||
front_is_valid = false;
|
||||
filenames.remove(filename);
|
||||
changed = true;
|
||||
}
|
||||
};
|
||||
|
||||
struct TransitionConfig
|
||||
{
|
||||
bool cross_fade;
|
||||
bool auto_open;
|
||||
bool hide_windows;
|
||||
float duration;
|
||||
int profile;
|
||||
|
||||
TransitionConfig() {
|
||||
cross_fade = true;
|
||||
auto_open = true;
|
||||
hide_windows = true;
|
||||
duration = 1.f;
|
||||
profile = 0;
|
||||
@@ -125,19 +138,39 @@ struct RenderConfig
|
||||
int ratio;
|
||||
int res;
|
||||
float fading;
|
||||
bool gpu_decoding;
|
||||
|
||||
RenderConfig() {
|
||||
blit = false;
|
||||
vsync = 1; // todo GUI selection
|
||||
multisampling = 2; // todo GUI selection
|
||||
vsync = 1;
|
||||
multisampling = 2;
|
||||
ratio = 3;
|
||||
res = 1;
|
||||
fading = 0.0;
|
||||
gpu_decoding = true;
|
||||
}
|
||||
};
|
||||
|
||||
struct SourceConfig
|
||||
{
|
||||
int new_type;
|
||||
int ratio;
|
||||
int res;
|
||||
|
||||
SourceConfig() {
|
||||
new_type = 0;
|
||||
ratio = 3;
|
||||
res = 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Application
|
||||
{
|
||||
// instance check
|
||||
bool fresh_start;
|
||||
int instance_id;
|
||||
|
||||
// Verification
|
||||
std::string name;
|
||||
std::string executable;
|
||||
@@ -147,20 +180,33 @@ struct Application
|
||||
int accent_color;
|
||||
bool pannel_stick;
|
||||
bool smooth_transition;
|
||||
bool smooth_cursor;
|
||||
bool action_history_follow_view;
|
||||
|
||||
// connection settings
|
||||
bool accept_connections;
|
||||
// std::map<int, std::string> instance_names;
|
||||
|
||||
// Settings of widgets
|
||||
WidgetsConfig widget;
|
||||
|
||||
// Settings of Views
|
||||
int current_view;
|
||||
int current_workspace;
|
||||
std::map<int, ViewConfig> views;
|
||||
|
||||
// settings brush texture paint
|
||||
glm::vec3 brush;
|
||||
|
||||
// settings render
|
||||
RenderConfig render;
|
||||
|
||||
// settings render
|
||||
// settings exporters
|
||||
RecordConfig record;
|
||||
|
||||
// settings new source
|
||||
SourceConfig source;
|
||||
|
||||
// settings transition
|
||||
TransitionConfig transition;
|
||||
|
||||
@@ -172,12 +218,17 @@ struct Application
|
||||
History recentFolders;
|
||||
History recentImport;
|
||||
|
||||
Application() : name(APP_NAME){
|
||||
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
|
||||
scale = 1.f;
|
||||
accent_color = 0;
|
||||
pannel_stick = false;
|
||||
smooth_transition = true;
|
||||
smooth_cursor = false;
|
||||
action_history_follow_view = false;
|
||||
accept_connections = false;
|
||||
current_view = 1;
|
||||
current_workspace= 1;
|
||||
brush = glm::vec3(0.5f, 0.1f, 0.f);
|
||||
windows = std::vector<WindowConfig>(3);
|
||||
windows[0].name = APP_NAME APP_TITLE;
|
||||
windows[0].w = 1600;
|
||||
@@ -195,6 +246,8 @@ extern Application application;
|
||||
// Save and Load store settings in XML file
|
||||
void Save();
|
||||
void Load();
|
||||
void Lock();
|
||||
void Unlock();
|
||||
void Check();
|
||||
|
||||
}
|
||||
|
||||
88
Shader.cpp
88
Shader.cpp
@@ -1,5 +1,6 @@
|
||||
#include "Shader.h"
|
||||
#include "Shader.h"
|
||||
#include "Resource.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Log.h"
|
||||
#include "Visitor.h"
|
||||
#include "RenderingManager.h"
|
||||
@@ -13,7 +14,7 @@
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include "GlmToolkit.h"
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
@@ -22,17 +23,38 @@
|
||||
ShadingProgram *ShadingProgram::currentProgram_ = nullptr;
|
||||
ShadingProgram simpleShadingProgram("shaders/simple.vs", "shaders/simple.fs");
|
||||
|
||||
// Blending presets for matching with Shader::BlendMode
|
||||
GLenum blending_equation[6] = { GL_FUNC_ADD, GL_FUNC_ADD, GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_ADD, GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_ADD};
|
||||
GLenum blending_source_function[6] = { GL_SRC_ALPHA,GL_SRC_ALPHA,GL_SRC_ALPHA,GL_SRC_ALPHA,GL_SRC_ALPHA,GL_SRC_ALPHA};
|
||||
GLenum blending_destination_function[6] = {GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE, GL_DST_COLOR, GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA};
|
||||
// Blending presets for matching with Shader::BlendModes:
|
||||
GLenum blending_equation[9] = { GL_FUNC_ADD, // normal
|
||||
GL_FUNC_ADD, // screen
|
||||
GL_FUNC_REVERSE_SUBTRACT, // subtract
|
||||
GL_FUNC_ADD, // multiply
|
||||
GL_FUNC_ADD, // soft light
|
||||
GL_FUNC_ADD, // hard light
|
||||
GL_FUNC_REVERSE_SUBTRACT, // soft subtract
|
||||
GL_MAX, // lighten only
|
||||
GL_FUNC_ADD};
|
||||
GLenum blending_source_function[9] = { GL_ONE, // normal
|
||||
GL_ONE, // screen
|
||||
GL_SRC_COLOR, // subtract (can be GL_ONE)
|
||||
GL_DST_COLOR, // multiply : src x dst color
|
||||
GL_DST_COLOR, // soft light : src x dst color
|
||||
GL_SRC_COLOR, // hard light : src x src color
|
||||
GL_DST_COLOR, // soft subtract
|
||||
GL_ONE, // lighten only
|
||||
GL_ONE};
|
||||
GLenum blending_destination_function[9] = {GL_ONE_MINUS_SRC_ALPHA,// normal
|
||||
GL_ONE, // screen
|
||||
GL_ONE, // subtract
|
||||
GL_ONE_MINUS_SRC_ALPHA, // multiply
|
||||
GL_ONE, // soft light
|
||||
GL_ONE, // hard light
|
||||
GL_ONE, // soft subtract
|
||||
GL_ONE, // lighten only
|
||||
GL_ZERO};
|
||||
|
||||
|
||||
|
||||
ShadingProgram::ShadingProgram(const std::string& vertex_file, const std::string& fragment_file) : vertex_id_(0), fragment_id_(0), id_(0)
|
||||
ShadingProgram::ShadingProgram(const std::string& vertex_file, const std::string& fragment_file) :
|
||||
vertex_id_(0), fragment_id_(0), id_(0), vertex_file_(vertex_file), fragment_file_(fragment_file)
|
||||
{
|
||||
vertex_file_ = vertex_file;
|
||||
fragment_file_ = fragment_file;
|
||||
}
|
||||
|
||||
void ShadingProgram::init()
|
||||
@@ -119,14 +141,20 @@ void ShadingProgram::setUniform<float>(const std::string& name, float val1, floa
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<glm::vec4>(const std::string& name, glm::vec4 val) {
|
||||
glm::vec4 v(val);
|
||||
glUniform4fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
|
||||
void ShadingProgram::setUniform<glm::vec2>(const std::string& name, glm::vec2 val) {
|
||||
glm::vec2 v(val);
|
||||
glUniform2fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<glm::vec3>(const std::string& name, glm::vec3 val) {
|
||||
glm::vec3 v(val);
|
||||
glUniform3fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
|
||||
}
|
||||
|
||||
template<>
|
||||
void ShadingProgram::setUniform<glm::vec4>(const std::string& name, glm::vec4 val) {
|
||||
glm::vec4 v(val);
|
||||
glUniform4fv(glGetUniformLocation(id_, name.c_str()), 1, glm::value_ptr(v));
|
||||
}
|
||||
|
||||
@@ -160,10 +188,10 @@ void ShadingProgram::checkCompileErr()
|
||||
|
||||
void ShadingProgram::checkLinkingErr()
|
||||
{
|
||||
int success;
|
||||
char infoLog[1024];
|
||||
int success;
|
||||
glGetProgramiv(id_, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[1024];
|
||||
glGetProgramInfoLog(id_, 1024, NULL, infoLog);
|
||||
Log::Warning("Error linking ShadingProgram:\n%s", infoLog);
|
||||
}
|
||||
@@ -175,8 +203,7 @@ bool Shader::force_blending_opacity = false;
|
||||
Shader::Shader() : blending(BLEND_OPACITY)
|
||||
{
|
||||
// create unique id
|
||||
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
|
||||
id_ = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 100000000;
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
|
||||
program_ = &simpleShadingProgram;
|
||||
reset();
|
||||
@@ -187,6 +214,7 @@ void Shader::operator = (const Shader &S )
|
||||
{
|
||||
color = S.color;
|
||||
blending = S.blending;
|
||||
iTransform = S.iTransform;
|
||||
}
|
||||
|
||||
void Shader::accept(Visitor& v) {
|
||||
@@ -199,33 +227,28 @@ void Shader::use()
|
||||
if (!program_->initialized())
|
||||
program_->init();
|
||||
|
||||
// Use program and set uniforms
|
||||
// Use program
|
||||
program_->use();
|
||||
|
||||
// set uniforms
|
||||
program_->setUniform("projection", projection);
|
||||
program_->setUniform("modelview", modelview);
|
||||
program_->setUniform("iTransform", iTransform);
|
||||
program_->setUniform("color", color);
|
||||
|
||||
iResolution = glm::vec3( Rendering::manager().currentAttrib().viewport, 0.f);
|
||||
glm::vec3 iResolution = glm::vec3( Rendering::manager().currentAttrib().viewport, 0.f);
|
||||
program_->setUniform("iResolution", iResolution);
|
||||
|
||||
// Blending Function
|
||||
if (force_blending_opacity) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquation(blending_equation[BLEND_OPACITY]);
|
||||
glBlendFunc(blending_source_function[BLEND_OPACITY], blending_destination_function[BLEND_OPACITY]);
|
||||
|
||||
glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
else if ( blending != BLEND_CUSTOM ) {
|
||||
else if ( blending < BLEND_NONE ) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquation(blending_equation[blending]);
|
||||
glBlendFunc(blending_source_function[blending], blending_destination_function[blending]);
|
||||
|
||||
// TODO different blending for alpha and color
|
||||
// glBlendEquationSeparate(blending_equation[blending], GL_FUNC_ADD);
|
||||
// glBlendFuncSeparate(blending_source_function[blending], blending_destination_function[blending], GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glBlendEquationSeparate(blending_equation[blending], GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(blending_source_function[blending], blending_destination_function[blending], GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
else
|
||||
glDisable(GL_BLEND);
|
||||
@@ -236,8 +259,9 @@ void Shader::reset()
|
||||
{
|
||||
projection = glm::identity<glm::mat4>();
|
||||
modelview = glm::identity<glm::mat4>();
|
||||
iResolution = glm::vec3(1280.f, 720.f, 0.f);
|
||||
iTransform = glm::identity<glm::mat4>();
|
||||
color = glm::vec4(1.f, 1.f, 1.f, 1.f);
|
||||
blending = BLEND_OPACITY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
36
Shader.h
36
Shader.h
@@ -7,6 +7,7 @@
|
||||
|
||||
// Forward declare classes referenced
|
||||
class Visitor;
|
||||
class FrameBuffer;
|
||||
|
||||
class ShadingProgram
|
||||
{
|
||||
@@ -22,13 +23,13 @@ public:
|
||||
static void enduse();
|
||||
|
||||
private:
|
||||
void checkCompileErr();
|
||||
void checkLinkingErr();
|
||||
void compile();
|
||||
void link();
|
||||
unsigned int vertex_id_, fragment_id_, id_;
|
||||
std::string vertex_code_;
|
||||
std::string fragment_code_;
|
||||
void checkCompileErr();
|
||||
void checkLinkingErr();
|
||||
void compile();
|
||||
void link();
|
||||
unsigned int vertex_id_, fragment_id_, id_;
|
||||
std::string vertex_code_;
|
||||
std::string fragment_code_;
|
||||
std::string vertex_file_;
|
||||
std::string fragment_file_;
|
||||
|
||||
@@ -37,13 +38,14 @@ private:
|
||||
|
||||
class Shader
|
||||
{
|
||||
int id_;
|
||||
uint64_t id_;
|
||||
|
||||
public:
|
||||
Shader();
|
||||
virtual ~Shader() {}
|
||||
|
||||
// unique identifyer generated at instanciation
|
||||
inline int id () const { return id_; }
|
||||
inline uint64_t id () const { return id_; }
|
||||
|
||||
virtual void use();
|
||||
virtual void reset();
|
||||
@@ -53,15 +55,19 @@ public:
|
||||
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 iTransform;
|
||||
glm::vec4 color;
|
||||
|
||||
typedef enum {
|
||||
BLEND_OPACITY = 0,
|
||||
BLEND_ADD,
|
||||
BLEND_SUBSTRACT,
|
||||
BLEND_LAYER_ADD,
|
||||
BLEND_LAYER_SUBSTRACT,
|
||||
BLEND_CUSTOM
|
||||
BLEND_SCREEN,
|
||||
BLEND_SUBTRACT,
|
||||
BLEND_MULTIPLY,
|
||||
BLEND_SOFT_LIGHT,
|
||||
BLEND_HARD_LIGHT,
|
||||
BLEND_SOFT_SUBTRACT,
|
||||
BLEND_LIGHTEN_ONLY,
|
||||
BLEND_NONE
|
||||
} BlendMode;
|
||||
BlendMode blending;
|
||||
|
||||
@@ -69,8 +75,6 @@ public:
|
||||
|
||||
protected:
|
||||
ShadingProgram *program_;
|
||||
glm::vec3 iResolution;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
568
Source.cpp
568
Source.cpp
@@ -1,24 +1,28 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
#include <tinyxml2.h>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "Source.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Primitives.h"
|
||||
#include "Decorations.h"
|
||||
#include "Mesh.h"
|
||||
#include "Resource.h"
|
||||
#include "Session.h"
|
||||
#include "SearchVisitor.h"
|
||||
#include "ImageShader.h"
|
||||
#include "ImageProcessingShader.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "SessionVisitor.h"
|
||||
#include "Log.h"
|
||||
#include "Mixer.h"
|
||||
#include "MixingGroup.h"
|
||||
|
||||
Source::Source() : initialized_(false), active_(true), need_update_(true)
|
||||
#include "Source.h"
|
||||
|
||||
Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_(false), need_update_(true), workspace_(STAGE)
|
||||
{
|
||||
// create unique id
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
|
||||
sprintf(initials_, "__");
|
||||
name_ = "Source";
|
||||
mode_ = Source::UNINITIALIZED;
|
||||
@@ -33,12 +37,12 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
|
||||
groups_[View::MIXING] = new Group;
|
||||
groups_[View::MIXING]->visible_ = false;
|
||||
groups_[View::MIXING]->scale_ = glm::vec3(0.15f, 0.15f, 1.f);
|
||||
groups_[View::MIXING]->translation_ = glm::vec3(-1.f, 1.f, 0.f);
|
||||
groups_[View::MIXING]->translation_ = glm::vec3(DEFAULT_MIXING_TRANSLATION, 0.f);
|
||||
|
||||
frames_[View::MIXING] = new Switch;
|
||||
Frame *frame = new Frame(Frame::ROUND, Frame::THIN, Frame::DROP);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.9f);
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
frames_[View::MIXING]->attach(frame);
|
||||
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::DROP);
|
||||
frame->translation_.z = 0.01;
|
||||
@@ -49,10 +53,22 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
|
||||
overlays_[View::MIXING] = new Group;
|
||||
overlays_[View::MIXING]->translation_.z = 0.1;
|
||||
overlays_[View::MIXING]->visible_ = false;
|
||||
Symbol *center = new Symbol(Symbol::POINT, glm::vec3(0.f, 0.f, 0.1f));
|
||||
Symbol *center = new Symbol(Symbol::CIRCLE_POINT, glm::vec3(0.f, 0.f, 0.1f));
|
||||
overlays_[View::MIXING]->attach(center);
|
||||
groups_[View::MIXING]->attach(overlays_[View::MIXING]);
|
||||
|
||||
overlay_mixinggroup_ = new Switch;
|
||||
overlay_mixinggroup_->translation_.z = 0.1;
|
||||
center = new Symbol(Symbol::CIRCLE_POINT, glm::vec3(0.f, 0.f, 0.1f));
|
||||
center->scale_= glm::vec3(1.6f, 1.6f, 1.f);
|
||||
center->color = glm::vec4( COLOR_MIXING_GROUP, 0.96f);
|
||||
overlay_mixinggroup_->attach(center);
|
||||
rotation_mixingroup_ = new Symbol(Symbol::ROTATION, glm::vec3(0.f, 0.f, 0.1f));
|
||||
rotation_mixingroup_->color = glm::vec4( COLOR_MIXING_GROUP, 0.94f);
|
||||
rotation_mixingroup_->scale_ = glm::vec3(3.f, 3.f, 1.f);
|
||||
overlay_mixinggroup_->attach(rotation_mixingroup_);
|
||||
groups_[View::MIXING]->attach(overlay_mixinggroup_);
|
||||
|
||||
// default geometry nodes
|
||||
groups_[View::GEOMETRY] = new Group;
|
||||
groups_[View::GEOMETRY]->visible_ = false;
|
||||
@@ -60,7 +76,7 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
|
||||
frames_[View::GEOMETRY] = new Switch;
|
||||
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.7f);
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.8f);
|
||||
frames_[View::GEOMETRY]->attach(frame);
|
||||
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::GLOW);
|
||||
frame->translation_.z = 0.1;
|
||||
@@ -71,22 +87,35 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
|
||||
overlays_[View::GEOMETRY] = new Group;
|
||||
overlays_[View::GEOMETRY]->translation_.z = 0.15;
|
||||
overlays_[View::GEOMETRY]->visible_ = false;
|
||||
handle_[Handles::RESIZE] = new Handles(Handles::RESIZE);
|
||||
handle_[Handles::RESIZE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handle_[Handles::RESIZE]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handle_[Handles::RESIZE]);
|
||||
handle_[Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
|
||||
handle_[Handles::RESIZE_H]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handle_[Handles::RESIZE_H]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handle_[Handles::RESIZE_H]);
|
||||
handle_[Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
|
||||
handle_[Handles::RESIZE_V]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handle_[Handles::RESIZE_V]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handle_[Handles::RESIZE_V]);
|
||||
handle_[Handles::ROTATE] = new Handles(Handles::ROTATE);
|
||||
handle_[Handles::ROTATE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handle_[Handles::ROTATE]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handle_[Handles::ROTATE]);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE] = new Handles(Handles::RESIZE);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE]);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_H]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_H]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE_H]);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_V]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::RESIZE_V]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE_V]);
|
||||
handles_[View::GEOMETRY][Handles::ROTATE] = new Handles(Handles::ROTATE);
|
||||
handles_[View::GEOMETRY][Handles::ROTATE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::ROTATE]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::ROTATE]);
|
||||
handles_[View::GEOMETRY][Handles::SCALE] = new Handles(Handles::SCALE);
|
||||
handles_[View::GEOMETRY][Handles::SCALE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::SCALE]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::SCALE]);
|
||||
handles_[View::GEOMETRY][Handles::MENU] = new Handles(Handles::MENU);
|
||||
handles_[View::GEOMETRY][Handles::MENU]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::MENU]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::MENU]);
|
||||
handles_[View::GEOMETRY][Handles::CROP] = new Handles(Handles::CROP);
|
||||
handles_[View::GEOMETRY][Handles::CROP]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
|
||||
handles_[View::GEOMETRY][Handles::CROP]->translation_.z = 0.1;
|
||||
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::CROP]);
|
||||
|
||||
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.7f);
|
||||
@@ -96,11 +125,12 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
|
||||
// default layer nodes
|
||||
groups_[View::LAYER] = new Group;
|
||||
groups_[View::LAYER]->visible_ = false;
|
||||
groups_[View::LAYER]->translation_.z = -1.f;
|
||||
|
||||
frames_[View::LAYER] = new Switch;
|
||||
frame = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.8f);
|
||||
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
|
||||
frames_[View::LAYER]->attach(frame);
|
||||
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::PERSPECTIVE);
|
||||
frame->translation_.z = 0.1;
|
||||
@@ -113,30 +143,112 @@ Source::Source() : initialized_(false), active_(true), need_update_(true)
|
||||
overlays_[View::LAYER]->visible_ = false;
|
||||
groups_[View::LAYER]->attach(overlays_[View::LAYER]);
|
||||
|
||||
// default appearance node
|
||||
groups_[View::TEXTURE] = new Group;
|
||||
groups_[View::TEXTURE]->visible_ = false;
|
||||
|
||||
frames_[View::TEXTURE] = new Switch;
|
||||
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 0.7f);
|
||||
frames_[View::TEXTURE]->attach(frame);
|
||||
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::NONE);
|
||||
frame->translation_.z = 0.1;
|
||||
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
frames_[View::TEXTURE]->attach(frame);
|
||||
groups_[View::TEXTURE]->attach(frames_[View::TEXTURE]);
|
||||
|
||||
overlays_[View::TEXTURE] = new Group;
|
||||
overlays_[View::TEXTURE]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->visible_ = false;
|
||||
handles_[View::TEXTURE][Handles::RESIZE] = new Handles(Handles::RESIZE);
|
||||
handles_[View::TEXTURE][Handles::RESIZE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::RESIZE]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE]);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_H]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_H]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE_H]);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_V]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::RESIZE_V]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE_V]);
|
||||
handles_[View::TEXTURE][Handles::ROTATE] = new Handles(Handles::ROTATE);
|
||||
handles_[View::TEXTURE][Handles::ROTATE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::ROTATE]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::ROTATE]);
|
||||
handles_[View::TEXTURE][Handles::SCALE] = new Handles(Handles::SCALE);
|
||||
handles_[View::TEXTURE][Handles::SCALE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::SCALE]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::SCALE]);
|
||||
handles_[View::TEXTURE][Handles::MENU] = new Handles(Handles::MENU);
|
||||
handles_[View::TEXTURE][Handles::MENU]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
|
||||
handles_[View::TEXTURE][Handles::MENU]->translation_.z = 0.1;
|
||||
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::MENU]);
|
||||
groups_[View::TEXTURE]->attach(overlays_[View::TEXTURE]);
|
||||
|
||||
// empty transition node
|
||||
groups_[View::TRANSITION] = new Group;
|
||||
|
||||
// locker switch button : locked / unlocked icons
|
||||
locker_ = new Switch;
|
||||
lock_ = new Handles(Handles::LOCKED);
|
||||
locker_->attach(lock_);
|
||||
unlock_ = new Handles(Handles::UNLOCKED);
|
||||
locker_->attach(unlock_);
|
||||
|
||||
// create objects
|
||||
stored_status_ = new Group;
|
||||
|
||||
// those will be associated to nodes later
|
||||
// simple image shader (with texturing) for blending
|
||||
blendingshader_ = new ImageShader;
|
||||
processingshader_ = new ImageProcessingShader;
|
||||
// default to image processing enabled
|
||||
renderingshader_ = (Shader *) processingshader_;
|
||||
// mask produced by dedicated shader
|
||||
maskshader_ = new MaskShader;
|
||||
masksurface_ = new Surface(maskshader_);
|
||||
|
||||
// filtered image shader (with texturing and processing) for rendering
|
||||
processingshader_ = new ImageProcessingShader;
|
||||
// default rendering with image processing enabled
|
||||
renderingshader_ = static_cast<Shader *>(processingshader_);
|
||||
|
||||
// for drawing in mixing view
|
||||
mixingshader_ = new ImageShader;
|
||||
mixingshader_->stipple = 1.0;
|
||||
mixinggroup_ = nullptr;
|
||||
|
||||
// create media surface:
|
||||
// - textured with original texture from media player
|
||||
// - crop & repeat UV can be managed here
|
||||
// - additional custom shader can be associated
|
||||
texturesurface_ = new Surface(renderingshader_);
|
||||
|
||||
// will be created at init
|
||||
renderbuffer_ = nullptr;
|
||||
rendersurface_ = nullptr;
|
||||
|
||||
mixingsurface_ = nullptr;
|
||||
maskbuffer_ = nullptr;
|
||||
maskimage_ = nullptr;
|
||||
mask_need_update_ = false;
|
||||
}
|
||||
|
||||
|
||||
Source::~Source()
|
||||
{
|
||||
// inform clones that they lost their origin
|
||||
for (auto it = clones_.begin(); it != clones_.end(); it++)
|
||||
(*it)->detach();
|
||||
clones_.clear();
|
||||
|
||||
// delete objects
|
||||
delete stored_status_;
|
||||
if (renderbuffer_)
|
||||
delete renderbuffer_;
|
||||
if (maskbuffer_)
|
||||
delete maskbuffer_;
|
||||
if (maskimage_)
|
||||
delete maskimage_;
|
||||
if (masksurface_)
|
||||
delete masksurface_; // deletes maskshader_
|
||||
|
||||
// all groups and their children are deleted in the scene
|
||||
// this includes rendersurface_, overlays, blendingshader_ and rendershader_
|
||||
@@ -144,24 +256,27 @@ Source::~Source()
|
||||
delete groups_[View::MIXING];
|
||||
delete groups_[View::GEOMETRY];
|
||||
delete groups_[View::LAYER];
|
||||
delete groups_[View::TEXTURE];
|
||||
delete groups_[View::TRANSITION];
|
||||
|
||||
groups_.clear();
|
||||
frames_.clear();
|
||||
overlays_.clear();
|
||||
|
||||
// inform clones that they lost their origin
|
||||
for (auto it = clones_.begin(); it != clones_.end(); it++)
|
||||
(*it)->origin_ = nullptr;
|
||||
// don't forget that the processing shader
|
||||
// could be created but not used
|
||||
if ( renderingshader_ != processingshader_ )
|
||||
delete processingshader_;
|
||||
|
||||
delete texturesurface_;
|
||||
}
|
||||
|
||||
void Source::setName (const std::string &name)
|
||||
{
|
||||
name_ = name;
|
||||
name_ = SystemToolkit::transliterate(name);
|
||||
|
||||
initials_[0] = std::toupper( name_.front() );
|
||||
initials_[1] = std::toupper( name_.back() );
|
||||
initials_[0] = std::toupper( name_.front(), std::locale("C") );
|
||||
initials_[1] = std::toupper( name_.back(), std::locale("C") );
|
||||
}
|
||||
|
||||
void Source::accept(Visitor& v)
|
||||
@@ -169,7 +284,6 @@ void Source::accept(Visitor& v)
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
Source::Mode Source::mode() const
|
||||
{
|
||||
return mode_;
|
||||
@@ -183,15 +297,25 @@ void Source::setMode(Source::Mode m)
|
||||
(*g).second->visible_ = true;
|
||||
}
|
||||
|
||||
// choose frame if selected
|
||||
// choose frame 0 if visible, 1 if selected
|
||||
uint index_frame = m == Source::VISIBLE ? 0 : 1;
|
||||
for (auto f = frames_.begin(); f != frames_.end(); f++)
|
||||
(*f).second->setActive(index_frame);
|
||||
|
||||
// show overlay if current
|
||||
bool current = m == Source::CURRENT;
|
||||
bool current = m >= Source::CURRENT;
|
||||
for (auto o = overlays_.begin(); o != overlays_.end(); o++)
|
||||
(*o).second->visible_ = current;
|
||||
(*o).second->visible_ = (current && !locked_);
|
||||
|
||||
// the lock icon
|
||||
locker_->setActive( locked_ ? 0 : 1);
|
||||
|
||||
// the mixing group overlay
|
||||
overlay_mixinggroup_->visible_ = mixinggroup_!= nullptr && !locked_;
|
||||
overlay_mixinggroup_->setActive(current);
|
||||
|
||||
// show in appearance view if current
|
||||
groups_[View::TEXTURE]->visible_ = m > Source::VISIBLE;
|
||||
|
||||
mode_ = m;
|
||||
}
|
||||
@@ -207,7 +331,7 @@ void Source::setImageProcessingEnabled (bool on)
|
||||
if (on) {
|
||||
// set the current rendering shader to be the
|
||||
// (previously prepared) processing shader
|
||||
renderingshader_ = (Shader *) processingshader_;
|
||||
renderingshader_ = static_cast<Shader *>(processingshader_);
|
||||
}
|
||||
else {
|
||||
// clone the current Image processing shader
|
||||
@@ -219,13 +343,23 @@ void Source::setImageProcessingEnabled (bool on)
|
||||
// and keep it for later
|
||||
processingshader_ = tmp;
|
||||
// set the current rendering shader to a simple one
|
||||
renderingshader_ = (Shader *) new ImageShader;
|
||||
renderingshader_ = static_cast<Shader *>(new ImageShader);
|
||||
}
|
||||
|
||||
// apply to nodes in subclasses
|
||||
// this calls replaceShader() on the Primitive and
|
||||
// will delete the previously attached shader
|
||||
replaceRenderingShader();
|
||||
texturesurface_->replaceShader(renderingshader_);
|
||||
}
|
||||
|
||||
void Source::setTextureMirrored (bool on)
|
||||
{
|
||||
texturesurface_->setMirrorTexture(on);
|
||||
}
|
||||
|
||||
bool Source::textureMirrored ()
|
||||
{
|
||||
return texturesurface_->mirrorTexture();
|
||||
}
|
||||
|
||||
bool Source::imageProcessingEnabled()
|
||||
@@ -233,80 +367,170 @@ bool Source::imageProcessingEnabled()
|
||||
return ( renderingshader_ == processingshader_ );
|
||||
}
|
||||
|
||||
void Source::render()
|
||||
{
|
||||
if (!initialized_)
|
||||
init();
|
||||
else {
|
||||
// render the view into frame buffer
|
||||
renderbuffer_->begin();
|
||||
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
|
||||
renderbuffer_->end();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Source::attach(FrameBuffer *renderbuffer)
|
||||
{
|
||||
// invalid argument
|
||||
if (renderbuffer == nullptr)
|
||||
return;
|
||||
|
||||
// replace renderbuffer_
|
||||
if (renderbuffer_)
|
||||
delete renderbuffer_;
|
||||
renderbuffer_ = renderbuffer;
|
||||
|
||||
// if a symbol is available, add it to overlay
|
||||
if (symbol_) {
|
||||
overlays_[View::MIXING]->attach( symbol_ );
|
||||
overlays_[View::LAYER]->attach( symbol_ );
|
||||
}
|
||||
|
||||
// create the surfaces to draw the frame buffer in the views
|
||||
rendersurface_ = new FrameBufferSurface(renderbuffer_, blendingshader_);
|
||||
groups_[View::RENDERING]->attach(rendersurface_);
|
||||
groups_[View::GEOMETRY]->attach(rendersurface_);
|
||||
groups_[View::MIXING]->attach(rendersurface_);
|
||||
// groups_[View::LAYER]->attach(rendersurface_);
|
||||
|
||||
// for mixing and layer views, add another surface to overlay
|
||||
// (stippled view on top with transparency)
|
||||
Surface *surfacemix = new FrameBufferSurface(renderbuffer_);
|
||||
ImageShader *is = static_cast<ImageShader *>(surfacemix->shader());
|
||||
if (is) is->stipple = 1.0;
|
||||
groups_[View::MIXING]->attach(surfacemix);
|
||||
groups_[View::LAYER]->attach(surfacemix);
|
||||
mixingsurface_ = new FrameBufferSurface(renderbuffer_, mixingshader_);
|
||||
groups_[View::MIXING]->attach(mixingsurface_);
|
||||
groups_[View::LAYER]->attach(mixingsurface_);
|
||||
|
||||
// scale all icon nodes to match aspect ratio of the media
|
||||
NodeSet::iterator node;
|
||||
for (node = groups_[View::MIXING]->begin();
|
||||
node != groups_[View::MIXING]->end(); node++) {
|
||||
(*node)->scale_.x = renderbuffer_->aspectRatio();
|
||||
}
|
||||
for (node = groups_[View::GEOMETRY]->begin();
|
||||
node != groups_[View::GEOMETRY]->end(); node++) {
|
||||
(*node)->scale_.x = renderbuffer_->aspectRatio();
|
||||
}
|
||||
for (node = groups_[View::LAYER]->begin();
|
||||
node != groups_[View::LAYER]->end(); node++) {
|
||||
(*node)->scale_.x = renderbuffer_->aspectRatio();
|
||||
}
|
||||
// for views showing a scaled mixing surface, a dedicated transparent surface allows grabbing
|
||||
Surface *surfacetmp = new Surface();
|
||||
surfacetmp->setTextureIndex(Resource::getTextureTransparent());
|
||||
groups_[View::TEXTURE]->attach(surfacetmp);
|
||||
groups_[View::MIXING]->attach(surfacetmp);
|
||||
groups_[View::LAYER]->attach(surfacetmp);
|
||||
|
||||
// Transition group node is optionnal
|
||||
if ( groups_[View::TRANSITION]->numChildren() > 0 ) {
|
||||
groups_[View::TRANSITION]->attach(rendersurface_);
|
||||
groups_[View::TRANSITION]->attach(surfacemix);
|
||||
for (NodeSet::iterator node = groups_[View::TRANSITION]->begin();
|
||||
node != groups_[View::TRANSITION]->end(); node++) {
|
||||
if (groups_[View::TRANSITION]->numChildren() > 0)
|
||||
groups_[View::TRANSITION]->attach(mixingsurface_);
|
||||
|
||||
// hack to place the symbols in the corner independently of aspect ratio
|
||||
symbol_->translation_.x += 0.1f * (renderbuffer_->aspectRatio()-1.f);
|
||||
|
||||
// add lock icon to views (displayed on front)
|
||||
groups_[View::LAYER]->attach( locker_ );
|
||||
groups_[View::MIXING]->attach( locker_ );
|
||||
groups_[View::GEOMETRY]->attach( locker_ );
|
||||
groups_[View::TEXTURE]->attach( locker_ );
|
||||
|
||||
// scale all icon nodes to match aspect ratio
|
||||
for (int v = View::MIXING; v < View::INVALID; v++) {
|
||||
NodeSet::iterator node;
|
||||
for (node = groups_[(View::Mode) v]->begin();
|
||||
node != groups_[(View::Mode) v]->end(); ++node) {
|
||||
(*node)->scale_.x = renderbuffer_->aspectRatio();
|
||||
}
|
||||
}
|
||||
|
||||
// (re) create the masking buffer
|
||||
if (maskbuffer_)
|
||||
delete maskbuffer_;
|
||||
maskbuffer_ = new FrameBuffer( glm::vec3(0.5) * renderbuffer->resolution() );
|
||||
|
||||
// make the source visible
|
||||
if ( mode_ == UNINITIALIZED )
|
||||
setMode(VISIBLE);
|
||||
|
||||
// request update
|
||||
need_update_ = true;
|
||||
}
|
||||
|
||||
|
||||
void Source::setActive (bool on)
|
||||
{
|
||||
active_ = on;
|
||||
|
||||
// do not disactivate if a clone depends on it
|
||||
for(auto clone = clones_.begin(); clone != clones_.end(); clone++) {
|
||||
if ( (*clone)->active() )
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
// an inactive source is visible only in the MIXING view
|
||||
groups_[View::RENDERING]->visible_ = active_;
|
||||
groups_[View::GEOMETRY]->visible_ = active_;
|
||||
groups_[View::LAYER]->visible_ = active_;
|
||||
|
||||
}
|
||||
|
||||
void Source::setLocked (bool on)
|
||||
{
|
||||
locked_ = on;
|
||||
|
||||
setMode(mode_);
|
||||
}
|
||||
|
||||
// Transfer functions from coordinates to alpha (1 - transparency)
|
||||
|
||||
// linear distance
|
||||
float linear_(float x, float y) {
|
||||
return 1.f - CLAMP( sqrt( ( x * x ) + ( y * y ) ), 0.f, 1.f );
|
||||
}
|
||||
|
||||
// quadratic distance
|
||||
float quad_(float x, float y) {
|
||||
return 1.f - CLAMP( ( x * x ) + ( y * y ), 0.f, 1.f );
|
||||
}
|
||||
|
||||
float sin_quad(float x, float y) {
|
||||
return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ), 0.f, 1.f ) );
|
||||
// best alpha transfer function: quadratic sinusoidal shape
|
||||
float sin_quad_(float x, float y) {
|
||||
float D = sqrt( ( x * x ) + ( y * y ) );
|
||||
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
|
||||
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ), 0.f, 1.f ) );
|
||||
}
|
||||
|
||||
|
||||
float Source::depth() const
|
||||
{
|
||||
return group(View::RENDERING)->translation_.z;
|
||||
}
|
||||
|
||||
void Source::setDepth(float d)
|
||||
{
|
||||
groups_[View::LAYER]->translation_.z = CLAMP(d, MIN_DEPTH, MAX_DEPTH);
|
||||
touch();
|
||||
}
|
||||
|
||||
float Source::alpha() const
|
||||
{
|
||||
return blendingShader()->color.a;
|
||||
}
|
||||
|
||||
void Source::setAlpha(float a)
|
||||
{
|
||||
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
|
||||
glm::vec2 step = glm::normalize(glm::vec2(1.f, 1.f));// step in diagonal by default
|
||||
|
||||
// step in direction of source translation if possible
|
||||
if ( glm::length(dist) > DELTA_ALPHA)
|
||||
step = glm::normalize(dist);
|
||||
|
||||
// converge to reduce the difference of alpha
|
||||
// using dichotomic algorithm
|
||||
float delta = sin_quad_(dist.x, dist.y) - CLAMP(a, 0.f, 1.f);
|
||||
while ( glm::abs(delta) > DELTA_ALPHA ){
|
||||
dist += step * (delta / 2.f);
|
||||
delta = sin_quad_(dist.x, dist.y) - CLAMP(a, 0.f, 1.f);
|
||||
}
|
||||
|
||||
// apply new mixing coordinates
|
||||
groups_[View::MIXING]->translation_.x = dist.x;
|
||||
groups_[View::MIXING]->translation_.y = dist.y;
|
||||
touch();
|
||||
}
|
||||
|
||||
void Source::update(float dt)
|
||||
@@ -315,35 +539,116 @@ void Source::update(float dt)
|
||||
dt_ = dt;
|
||||
|
||||
// update nodes if needed
|
||||
if (need_update_)
|
||||
if (renderbuffer_ && mixingsurface_ && maskbuffer_ && need_update_)
|
||||
{
|
||||
// ADJUST alpha based on MIXING node
|
||||
// read position of the mixing node and interpret this as transparency of render output
|
||||
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
|
||||
// use the prefered transfer function
|
||||
blendingshader_->color.a = sin_quad( dist.x, dist.y );
|
||||
// use the sinusoidal transfer function
|
||||
blendingshader_->color = glm::vec4(1.f, 1.f, 1.f, sin_quad_( dist.x, dist.y ));
|
||||
mixingshader_->color = blendingshader_->color;
|
||||
|
||||
// CHANGE update status based on limbo
|
||||
setActive( glm::length(dist) < 1.3f );
|
||||
bool a = glm::length(dist) < MIXING_LIMBO_SCALE;
|
||||
setActive( a );
|
||||
// adjust scale of mixing icon : smaller if not active
|
||||
groups_[View::MIXING]->scale_ = glm::vec3(MIXING_ICON_SCALE) - ( a ? glm::vec3(0.f, 0.f, 0.f) : glm::vec3(0.03f, 0.03f, 0.f) );
|
||||
|
||||
// MODIFY geometry based on GEOMETRY node
|
||||
groups_[View::RENDERING]->translation_ = groups_[View::GEOMETRY]->translation_;
|
||||
groups_[View::RENDERING]->rotation_ = groups_[View::GEOMETRY]->rotation_;
|
||||
// avoid any null scale
|
||||
glm::vec3 s = groups_[View::GEOMETRY]->scale_;
|
||||
// avoid any null scale
|
||||
s.x = CLAMP_SCALE(s.x);
|
||||
s.y = CLAMP_SCALE(s.y);
|
||||
s.z = 1.f;
|
||||
groups_[View::GEOMETRY]->scale_ = s;
|
||||
groups_[View::RENDERING]->scale_ = s;
|
||||
|
||||
// MODIFY CROP projection based on GEOMETRY crop
|
||||
renderbuffer_->setProjectionArea( glm::vec2(groups_[View::GEOMETRY]->crop_) );
|
||||
|
||||
// Mixing and layer icons scaled based on GEOMETRY crop
|
||||
mixingsurface_->scale_ = groups_[View::GEOMETRY]->crop_;
|
||||
mixingsurface_->scale_.x *= renderbuffer_->aspectRatio();
|
||||
mixingsurface_->update(dt_);
|
||||
|
||||
// Layers icons are displayed in Perspective (diagonal)
|
||||
groups_[View::LAYER]->translation_.x = -groups_[View::LAYER]->translation_.z;
|
||||
groups_[View::LAYER]->translation_.y = groups_[View::LAYER]->translation_.x / LAYER_PERSPECTIVE;
|
||||
|
||||
// Update workspace based on depth, and
|
||||
// adjust vertical position of icon depending on workspace
|
||||
if (groups_[View::LAYER]->translation_.x < -LAYER_FOREGROUND) {
|
||||
groups_[View::LAYER]->translation_.y -= 0.3f;
|
||||
workspace_ = Source::FOREGROUND;
|
||||
}
|
||||
else if (groups_[View::LAYER]->translation_.x < -LAYER_BACKGROUND) {
|
||||
groups_[View::LAYER]->translation_.y -= 0.15f;
|
||||
workspace_ = Source::STAGE;
|
||||
}
|
||||
else
|
||||
workspace_ = Source::BACKGROUND;
|
||||
|
||||
// MODIFY depth based on LAYER node
|
||||
groups_[View::MIXING]->translation_.z = groups_[View::LAYER]->translation_.z;
|
||||
groups_[View::GEOMETRY]->translation_.z = groups_[View::LAYER]->translation_.z;
|
||||
groups_[View::RENDERING]->translation_.z = groups_[View::LAYER]->translation_.z;
|
||||
|
||||
// MODIFY texture projection based on APPEARANCE node
|
||||
// UV to node coordinates
|
||||
static glm::mat4 UVtoScene = GlmToolkit::transform(glm::vec3(1.f, -1.f, 0.f),
|
||||
glm::vec3(0.f, 0.f, 0.f),
|
||||
glm::vec3(-2.f, 2.f, 1.f));
|
||||
// Aspect Ratio correction transform : coordinates of Appearance Frame are scaled by render buffer width
|
||||
glm::mat4 Ar = glm::scale(glm::identity<glm::mat4>(), glm::vec3(renderbuffer_->aspectRatio(), 1.f, 1.f) );
|
||||
// Translation : same as Appearance Frame (modified by Ar)
|
||||
glm::mat4 Tra = glm::translate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->translation_);
|
||||
// Scaling : inverse scaling (larger UV when smaller Appearance Frame)
|
||||
glm::vec2 scale = glm::vec2(groups_[View::TEXTURE]->scale_.x,groups_[View::TEXTURE]->scale_.y);
|
||||
scale = glm::sign(scale) * glm::max( glm::vec2(glm::epsilon<float>()), glm::abs(scale));
|
||||
glm::mat4 Sca = glm::scale(glm::identity<glm::mat4>(), glm::vec3(scale, 1.f));
|
||||
// Rotation : same angle than Appearance Frame, inverted axis
|
||||
glm::mat4 Rot = glm::rotate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->rotation_.z, glm::vec3(0.f, 0.f, -1.f) );
|
||||
// Combine transformations (non transitive) in this order:
|
||||
// 1. switch to Scene coordinate system
|
||||
// 2. Apply the aspect ratio correction
|
||||
// 3. Apply the translation
|
||||
// 4. Apply the rotation (centered after translation)
|
||||
// 5. Revert aspect ration correction
|
||||
// 6. Apply the Scaling (independent of aspect ratio)
|
||||
// 7. switch back to UV coordinate system
|
||||
texturesurface_->shader()->iTransform = glm::inverse(UVtoScene) * glm::inverse(Sca) * glm::inverse(Ar) * Rot * Tra * Ar * UVtoScene;
|
||||
|
||||
// if a mask image was given to be updated
|
||||
if (mask_need_update_) {
|
||||
// fill the mask buffer (once)
|
||||
if (maskbuffer_->fill(maskimage_) )
|
||||
mask_need_update_ = false;
|
||||
}
|
||||
// otherwise, render the mask buffer
|
||||
else
|
||||
{
|
||||
// draw mask in mask frame buffer
|
||||
maskbuffer_->begin(false);
|
||||
// loopback maskbuffer texture for painting
|
||||
masksurface_->setTextureIndex(maskbuffer_->texture());
|
||||
// fill surface with mask texture
|
||||
masksurface_->draw(glm::identity<glm::mat4>(), maskbuffer_->projection());
|
||||
maskbuffer_->end();
|
||||
}
|
||||
|
||||
// set the rendered mask as mask for blending
|
||||
blendingshader_->mask_texture = maskbuffer_->texture();
|
||||
|
||||
// inform mixing group
|
||||
if (mixinggroup_)
|
||||
mixinggroup_->setAction(MixingGroup::ACTION_UPDATE);
|
||||
|
||||
// do not update next frame
|
||||
need_update_ = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FrameBuffer *Source::frame() const
|
||||
@@ -368,6 +673,48 @@ bool Source::contains(Node *node) const
|
||||
}
|
||||
|
||||
|
||||
void Source::storeMask(FrameBufferImage *img)
|
||||
{
|
||||
// free the output mask storage
|
||||
if (maskimage_ != nullptr) {
|
||||
delete maskimage_;
|
||||
maskimage_ = nullptr;
|
||||
}
|
||||
|
||||
// if no image is provided
|
||||
if (img == nullptr) {
|
||||
// if ready
|
||||
if (maskbuffer_!=nullptr) {
|
||||
// get & store image from mask buffer
|
||||
maskimage_ = maskbuffer_->image();
|
||||
}
|
||||
}
|
||||
else
|
||||
// store the given image
|
||||
maskimage_ = img;
|
||||
|
||||
// maskimage_ can now be accessed with Source::getStoredMask
|
||||
}
|
||||
|
||||
void Source::setMask(FrameBufferImage *img)
|
||||
{
|
||||
// if a valid image is given
|
||||
if (img != nullptr && img->width>0 && img->height>0) {
|
||||
|
||||
// remember this new image as the current mask
|
||||
// NB: will be freed when replaced
|
||||
storeMask(img);
|
||||
|
||||
// ask Source::update to use it at next update for filling mask buffer
|
||||
mask_need_update_ = true;
|
||||
|
||||
// ask to update the source
|
||||
touch();
|
||||
}
|
||||
else
|
||||
mask_need_update_ = false;
|
||||
}
|
||||
|
||||
bool Source::hasNode::operator()(const Source* elem) const
|
||||
{
|
||||
if (_n && elem)
|
||||
@@ -378,16 +725,30 @@ bool Source::hasNode::operator()(const Source* elem) const
|
||||
|
||||
// general case: traverse tree of all Groups recursively using a SearchVisitor
|
||||
SearchVisitor sv(_n);
|
||||
// search in groups for all views
|
||||
for (auto g = elem->groups_.begin(); g != elem->groups_.end(); g++) {
|
||||
(*g).second->accept(sv);
|
||||
if (sv.found())
|
||||
return true;
|
||||
}
|
||||
// search in overlays for all views
|
||||
for (auto g = elem->overlays_.begin(); g != elem->overlays_.end(); g++) {
|
||||
(*g).second->accept(sv);
|
||||
if (sv.found())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void Source::clearMixingGroup()
|
||||
{
|
||||
mixinggroup_ = nullptr;
|
||||
overlay_mixinggroup_->visible_ = false;
|
||||
}
|
||||
|
||||
CloneSource *Source::clone()
|
||||
{
|
||||
CloneSource *s = new CloneSource(this);
|
||||
@@ -397,16 +758,18 @@ CloneSource *Source::clone()
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
CloneSource::CloneSource(Source *origin) : Source(), origin_(origin)
|
||||
{
|
||||
// create surface:
|
||||
clonesurface_ = new Surface(renderingshader_);
|
||||
// set symbol
|
||||
symbol_ = new Symbol(Symbol::CLONE, glm::vec3(0.75f, 0.75f, 0.01f));
|
||||
symbol_->scale_.y = 1.5f;
|
||||
}
|
||||
|
||||
CloneSource::~CloneSource()
|
||||
{
|
||||
// delete surface
|
||||
delete clonesurface_;
|
||||
if (origin_)
|
||||
origin_->clones_.remove(this);
|
||||
}
|
||||
|
||||
CloneSource *CloneSource::clone()
|
||||
@@ -418,17 +781,12 @@ CloneSource *CloneSource::clone()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CloneSource::replaceRenderingShader()
|
||||
{
|
||||
clonesurface_->replaceShader(renderingshader_);
|
||||
}
|
||||
|
||||
void CloneSource::init()
|
||||
{
|
||||
if (origin_ && origin_->ready()) {
|
||||
|
||||
// get the texture index from framebuffer of view, apply it to the surface
|
||||
clonesurface_->setTextureIndex( origin_->texture() );
|
||||
texturesurface_->setTextureIndex( origin_->texture() );
|
||||
|
||||
// create Frame buffer matching size of session
|
||||
FrameBuffer *renderbuffer = new FrameBuffer( origin_->frame()->resolution(), true);
|
||||
@@ -436,14 +794,12 @@ void CloneSource::init()
|
||||
// set the renderbuffer of the source and attach rendering nodes
|
||||
attach(renderbuffer);
|
||||
|
||||
// icon in mixing view
|
||||
overlays_[View::MIXING]->attach( new Symbol(Symbol::CLONE, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
overlays_[View::LAYER]->attach( new Symbol(Symbol::CLONE, glm::vec3(0.8f, 0.8f, 0.01f)) );
|
||||
// deep update to reorder
|
||||
View::need_deep_update_++;
|
||||
|
||||
// done init
|
||||
initialized_ = true;
|
||||
|
||||
Log::Info("Source Clone linked to source %s).", origin_->name().c_str() );
|
||||
Log::Info("Source %s cloning source %s.", name().c_str(), origin_->name().c_str() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,35 +811,23 @@ void CloneSource::setActive (bool on)
|
||||
groups_[View::GEOMETRY]->visible_ = active_;
|
||||
groups_[View::LAYER]->visible_ = active_;
|
||||
|
||||
if (origin_)
|
||||
if (initialized_ && origin_ != nullptr)
|
||||
origin_->touch();
|
||||
}
|
||||
|
||||
|
||||
uint CloneSource::texture() const
|
||||
{
|
||||
if (origin_)
|
||||
if (initialized_ && origin_ != nullptr)
|
||||
return origin_->texture();
|
||||
else
|
||||
return Resource::getTextureBlack();
|
||||
}
|
||||
|
||||
void CloneSource::render()
|
||||
{
|
||||
if (!initialized_)
|
||||
init();
|
||||
else {
|
||||
// render the view into frame buffer
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
|
||||
renderbuffer_->begin();
|
||||
clonesurface_->draw(glm::identity<glm::mat4>(), projection);
|
||||
renderbuffer_->end();
|
||||
}
|
||||
}
|
||||
|
||||
void CloneSource::accept(Visitor& v)
|
||||
{
|
||||
Source::accept(v);
|
||||
v.visit(*this);
|
||||
if (!failed())
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
|
||||
147
Source.h
147
Source.h
@@ -3,38 +3,46 @@
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
#include <list>
|
||||
|
||||
#include "View.h"
|
||||
#include "Decorations.h"
|
||||
|
||||
#define DEFAULT_MIXING_TRANSLATION -1.f, 1.f
|
||||
|
||||
class ImageShader;
|
||||
class MaskShader;
|
||||
class ImageProcessingShader;
|
||||
class FrameBuffer;
|
||||
class FrameBufferSurface;
|
||||
class MediaPlayer;
|
||||
class Surface;
|
||||
class Session;
|
||||
class Frame;
|
||||
class Source;
|
||||
class Handles;
|
||||
class Symbol;
|
||||
class CloneSource;
|
||||
class MixingGroup;
|
||||
|
||||
typedef std::list<Source *> SourceList;
|
||||
typedef std::list<CloneSource *> CloneList;
|
||||
|
||||
class Source
|
||||
{
|
||||
friend class CloneSource;
|
||||
friend class View;
|
||||
friend class MixingView;
|
||||
friend class MixingGroup;
|
||||
friend class GeometryView;
|
||||
friend class LayerView;
|
||||
friend class TextureView;
|
||||
friend class TransitionView;
|
||||
|
||||
public:
|
||||
// create a source and add it to the list
|
||||
// only subclasses of sources can actually be instanciated
|
||||
Source();
|
||||
virtual ~Source();
|
||||
Source ();
|
||||
virtual ~Source ();
|
||||
|
||||
// Get unique id
|
||||
inline uint64_t id () const { return id_; }
|
||||
|
||||
// manipulate name of source
|
||||
void setName (const std::string &name);
|
||||
@@ -43,12 +51,11 @@ public:
|
||||
|
||||
// cloning mechanism
|
||||
virtual CloneSource *clone ();
|
||||
inline size_t numClones() const { return clones_.size(); }
|
||||
|
||||
// Display mode
|
||||
typedef enum {
|
||||
UNINITIALIZED = 0,
|
||||
VISIBLE = 1,
|
||||
UNINITIALIZED = 0,
|
||||
VISIBLE = 1,
|
||||
SELECTED = 2,
|
||||
CURRENT = 3
|
||||
} Mode;
|
||||
@@ -62,48 +69,82 @@ public:
|
||||
// tests if a given node is part of the source
|
||||
bool contains (Node *node) const;
|
||||
|
||||
// a Source has a shader used to render in fbo
|
||||
inline Shader *renderingShader() const { return renderingshader_; }
|
||||
|
||||
// the rendering shader always have an image processing shader
|
||||
inline ImageProcessingShader *processingShader () const { return processingshader_; }
|
||||
|
||||
// the image processing shader can be enabled or disabled
|
||||
// (NB: when disabled, a simple ImageShader is applied)
|
||||
void setImageProcessingEnabled (bool on);
|
||||
bool imageProcessingEnabled();
|
||||
bool imageProcessingEnabled ();
|
||||
|
||||
// a Source has a shader to control mixing effects
|
||||
inline ImageShader *blendingShader () const { return blendingshader_; }
|
||||
|
||||
// a Source has a shader used to render in fbo
|
||||
inline Shader *renderingShader () const { return renderingshader_; }
|
||||
|
||||
// every Source has a frame buffer from the renderbuffer
|
||||
virtual FrameBuffer *frame () const;
|
||||
|
||||
// a Source has a shader used to render mask
|
||||
inline MaskShader *maskShader () const { return maskshader_; }
|
||||
|
||||
// touch to request update
|
||||
inline void touch () { need_update_ = true; }
|
||||
|
||||
// informs if its ready (i.e. initialized)
|
||||
inline bool ready() const { return initialized_; }
|
||||
inline bool ready () const { return initialized_; }
|
||||
|
||||
// a Source shall be updated before displayed (Mixing, Geometry and Layer)
|
||||
virtual void update (float dt);
|
||||
|
||||
// update mode
|
||||
inline bool active () const { return active_; }
|
||||
virtual void setActive (bool on);
|
||||
inline bool active () { return active_; }
|
||||
|
||||
// lock mode
|
||||
inline bool locked () const { return locked_; }
|
||||
virtual void setLocked (bool on);
|
||||
|
||||
// Workspace
|
||||
typedef enum {
|
||||
BACKGROUND = 0,
|
||||
STAGE = 1,
|
||||
FOREGROUND = 2
|
||||
} Workspace;
|
||||
inline Workspace workspace () const { return workspace_; }
|
||||
|
||||
// a Source shall informs if the source failed (i.e. shall be deleted)
|
||||
virtual bool failed() const = 0;
|
||||
virtual bool failed () const = 0;
|
||||
|
||||
// a Source shall define a way to get a texture
|
||||
virtual uint texture() const = 0;
|
||||
virtual uint texture () const = 0;
|
||||
void setTextureMirrored (bool on);
|
||||
bool textureMirrored ();
|
||||
|
||||
// a Source shall define how to render into the frame buffer
|
||||
virtual void render() = 0;
|
||||
virtual void render ();
|
||||
|
||||
// accept all kind of visitors
|
||||
virtual void accept (Visitor& v);
|
||||
|
||||
// operations on mask
|
||||
inline FrameBufferImage *getMask () const { return maskimage_; }
|
||||
void setMask (FrameBufferImage *img);
|
||||
void storeMask (FrameBufferImage *img = nullptr);
|
||||
|
||||
// operations on depth
|
||||
float depth () const;
|
||||
void setDepth (float d);
|
||||
|
||||
// operations on alpha
|
||||
float alpha () const;
|
||||
void setAlpha (float a);
|
||||
|
||||
// groups for mixing
|
||||
MixingGroup *mixingGroup() const { return mixinggroup_; }
|
||||
void clearMixingGroup();
|
||||
|
||||
struct hasNode: public std::unary_function<Source*, bool>
|
||||
{
|
||||
bool operator()(const Source* elem) const;
|
||||
@@ -122,11 +163,46 @@ public:
|
||||
std::string _n;
|
||||
};
|
||||
|
||||
struct hasId: public std::unary_function<Source*, bool>
|
||||
{
|
||||
inline bool operator()(const Source* elem) const {
|
||||
return (elem && elem->id() == _id);
|
||||
}
|
||||
hasId(uint64_t id) : _id(id) { }
|
||||
private:
|
||||
uint64_t _id;
|
||||
};
|
||||
|
||||
struct hasDepth: public std::unary_function<Source*, bool>
|
||||
{
|
||||
inline bool operator()(const Source* elem) const {
|
||||
return (elem && elem->depth()>_from && elem->depth()<_to );
|
||||
}
|
||||
hasDepth(float d1, float d2) {
|
||||
_from = MIN(d1, d2);
|
||||
_to = MAX(d1, d2);
|
||||
}
|
||||
private:
|
||||
float _from;
|
||||
float _to;
|
||||
};
|
||||
|
||||
static bool isCurrent (const Source* elem) {
|
||||
return (elem && elem->mode_ == Source::CURRENT);
|
||||
}
|
||||
|
||||
static bool isInitialized (const Source* elem) {
|
||||
return (elem && elem->mode_ > Source::UNINITIALIZED);
|
||||
}
|
||||
|
||||
// class-dependent icon
|
||||
virtual glm::ivec2 icon () const { return glm::ivec2(12, 11); }
|
||||
|
||||
protected:
|
||||
// name
|
||||
std::string name_;
|
||||
char initials_[3];
|
||||
uint64_t id_;
|
||||
|
||||
// every Source shall be initialized on first draw
|
||||
bool initialized_;
|
||||
@@ -143,17 +219,27 @@ protected:
|
||||
// the rendersurface draws the renderbuffer in the scene
|
||||
// It is associated to the rendershader for mixing effects
|
||||
FrameBufferSurface *rendersurface_;
|
||||
FrameBufferSurface *mixingsurface_;
|
||||
|
||||
// image processing shaders
|
||||
ImageProcessingShader *processingshader_;
|
||||
// pointer to the currently attached shader
|
||||
// (will be processingshader_ if image processing is enabled)
|
||||
Shader *renderingshader_;
|
||||
// every sub class will attach the shader to a different node / hierarchy
|
||||
virtual void replaceRenderingShader() = 0;
|
||||
|
||||
// blendingshader provides mixing controls
|
||||
ImageShader *blendingshader_;
|
||||
ImageShader *mixingshader_;
|
||||
|
||||
// shader and buffer to draw mask
|
||||
MaskShader *maskshader_;
|
||||
FrameBuffer *maskbuffer_;
|
||||
Surface *masksurface_;
|
||||
bool mask_need_update_;
|
||||
FrameBufferImage *maskimage_;
|
||||
|
||||
// surface to draw on
|
||||
Surface *texturesurface_;
|
||||
|
||||
// mode for display
|
||||
Mode mode_;
|
||||
@@ -161,18 +247,27 @@ protected:
|
||||
// overlays and frames to be displayed on top of source
|
||||
std::map<View::Mode, Group*> overlays_;
|
||||
std::map<View::Mode, Switch*> frames_;
|
||||
Handles *handle_[4];
|
||||
std::map<View::Mode, Handles*[7]> handles_;
|
||||
Handles *lock_, *unlock_;
|
||||
Switch *locker_;
|
||||
Symbol *symbol_;
|
||||
|
||||
// update
|
||||
bool active_;
|
||||
bool locked_;
|
||||
bool need_update_;
|
||||
float dt_;
|
||||
Group *stored_status_;
|
||||
Workspace workspace_;
|
||||
|
||||
// clones
|
||||
CloneList clones_;
|
||||
};
|
||||
|
||||
// Mixing
|
||||
MixingGroup *mixinggroup_;
|
||||
Switch *overlay_mixinggroup_;
|
||||
Symbol *rotation_mixingroup_;
|
||||
};
|
||||
|
||||
|
||||
class CloneSource : public Source
|
||||
@@ -184,21 +279,21 @@ public:
|
||||
|
||||
// implementation of source API
|
||||
void setActive (bool on) override;
|
||||
void render() override;
|
||||
uint texture() const override;
|
||||
bool failed() const override { return origin_ == nullptr; }
|
||||
void accept (Visitor& v) override;
|
||||
|
||||
CloneSource *clone() override;
|
||||
inline void detach() { origin_ = nullptr; }
|
||||
inline Source *origin() const { return origin_; }
|
||||
|
||||
glm::ivec2 icon() const override { return glm::ivec2(9, 2); }
|
||||
|
||||
protected:
|
||||
// only Source class can create new CloneSource via clone();
|
||||
CloneSource(Source *origin);
|
||||
|
||||
void init() override;
|
||||
void replaceRenderingShader() override;
|
||||
Surface *clonesurface_;
|
||||
Source *origin_;
|
||||
};
|
||||
|
||||
|
||||
120
SourceList.cpp
Normal file
120
SourceList.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <glm/gtx/rotate_vector.hpp>
|
||||
|
||||
#include "Source.h"
|
||||
#include "SourceList.h"
|
||||
|
||||
// utility to sort Sources by depth
|
||||
bool compare_depth (Source * first, Source * second)
|
||||
{
|
||||
return ( first->depth() < second->depth() );
|
||||
}
|
||||
|
||||
SourceList depth_sorted(const SourceList &list)
|
||||
{
|
||||
SourceList sl = list;
|
||||
sl.sort(compare_depth);
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
// utility to sort Sources in MixingView in a clockwise order
|
||||
// in reference to a center point
|
||||
struct clockwise_centered {
|
||||
explicit clockwise_centered(glm::vec2 c) : center(c) { }
|
||||
bool operator() (Source * first, Source * second) {
|
||||
glm::vec2 pos_first = glm::vec2(first->group(View::MIXING)->translation_)-center;
|
||||
float angle_first = glm::orientedAngle( glm::normalize(pos_first), glm::vec2(1.f, 0.f) );
|
||||
glm::vec2 pos_second = glm::vec2(second->group(View::MIXING)->translation_)-center;
|
||||
float angle_second = glm::orientedAngle( glm::normalize(pos_second), glm::vec2(1.f, 0.f) );
|
||||
return (angle_first < angle_second);
|
||||
}
|
||||
glm::vec2 center;
|
||||
};
|
||||
|
||||
SourceList mixing_sorted(const SourceList &list, glm::vec2 center)
|
||||
{
|
||||
SourceList sl = list;
|
||||
sl.sort(clockwise_centered(center));
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
|
||||
SourceIdList ids (const SourceList &list)
|
||||
{
|
||||
SourceIdList idlist;
|
||||
|
||||
for( auto sit = list.begin(); sit != list.end(); sit++)
|
||||
idlist.push_back( (*sit)->id() );
|
||||
|
||||
// make sure no duplicate
|
||||
idlist.unique();
|
||||
|
||||
return idlist;
|
||||
}
|
||||
|
||||
|
||||
SourceListCompare compare (const SourceList &first, const SourceList &second)
|
||||
{
|
||||
SourceListCompare ret = SOURCELIST_DISTINCT;
|
||||
if (first.empty() || second.empty())
|
||||
return ret;
|
||||
|
||||
// a new test list: start with the second list and remove all commons with first list
|
||||
SourceList test = second;
|
||||
for (auto it = first.begin(); it != first.end(); it++){
|
||||
test.remove(*it);
|
||||
}
|
||||
|
||||
// all sources of the second list were in the first list
|
||||
if (test.empty()) {
|
||||
// same size, therefore they are the same!
|
||||
if (first.size() == second.size())
|
||||
ret = SOURCELIST_EQUAL;
|
||||
// otherwise, first list contains all sources of the second list.
|
||||
else
|
||||
ret = SOURCELIST_SECOND_IN_FIRST;
|
||||
}
|
||||
// some sources of the second list were in the first
|
||||
else if ( second.size() != test.size() ){
|
||||
// if the number of sources removed from second is the number of sources in the first
|
||||
if (second.size() - test.size() == first.size())
|
||||
ret = SOURCELIST_FIRST_IN_SECOND;
|
||||
// else, there is a patrial intersection
|
||||
else
|
||||
ret = SOURCELIST_INTERSECT;
|
||||
}
|
||||
// else no intersection, lists are distinct (return detault)
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
SourceList intersect (const SourceList &first, const SourceList &second)
|
||||
{
|
||||
// take second list and remove all elements also in first list
|
||||
// -> builds the list of what remains in second list
|
||||
SourceList l1 = second;
|
||||
for (auto it = first.begin(); it != first.end(); it++)
|
||||
l1.remove(*it);
|
||||
// take second list and remove all elements in the remainer list
|
||||
// -> builds the list of what is in second list and was part of the first list
|
||||
SourceList l2 = second;
|
||||
for (auto it = l1.begin(); it != l1.end(); it++)
|
||||
l2.remove(*it);
|
||||
return l2;
|
||||
}
|
||||
|
||||
|
||||
SourceList join (const SourceList &first, const SourceList &second)
|
||||
{
|
||||
SourceList l = second;
|
||||
for (auto it = first.begin(); it != first.end(); it++)
|
||||
l.push_back(*it);
|
||||
l.unique();
|
||||
return l;
|
||||
}
|
||||
|
||||
28
SourceList.h
Normal file
28
SourceList.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef SOURCELIST_H
|
||||
#define SOURCELIST_H
|
||||
|
||||
#include <list>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class Source;
|
||||
|
||||
typedef std::list<Source *> SourceList;
|
||||
|
||||
SourceList depth_sorted (const SourceList &list);
|
||||
SourceList mixing_sorted (const SourceList &list, glm::vec2 center = glm::vec2(0.f, 0.f));
|
||||
SourceList intersect (const SourceList &first, const SourceList &second);
|
||||
SourceList join (const SourceList &first, const SourceList &second);
|
||||
|
||||
typedef enum {
|
||||
SOURCELIST_DISTINCT = 0,
|
||||
SOURCELIST_INTERSECT = 1,
|
||||
SOURCELIST_EQUAL = 2,
|
||||
SOURCELIST_FIRST_IN_SECOND = 3,
|
||||
SOURCELIST_SECOND_IN_FIRST = 4
|
||||
} SourceListCompare;
|
||||
SourceListCompare compare (const SourceList &first, const SourceList &second);
|
||||
|
||||
typedef std::list<uint64_t> SourceIdList;
|
||||
SourceIdList ids (const SourceList &list);
|
||||
|
||||
#endif // SOURCELIST_H
|
||||
751
Stream.cpp
Normal file
751
Stream.cpp
Normal file
@@ -0,0 +1,751 @@
|
||||
#include <thread>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
|
||||
// vmix
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "Resource.h"
|
||||
#include "Visitor.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "GlmToolkit.h"
|
||||
|
||||
#include "Stream.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define STREAM_DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
Stream::Stream()
|
||||
{
|
||||
// create unique id
|
||||
id_ = GlmToolkit::uniqueId();
|
||||
|
||||
description_ = "undefined";
|
||||
pipeline_ = nullptr;
|
||||
|
||||
width_ = -1;
|
||||
height_ = -1;
|
||||
single_frame_ = false;
|
||||
live_ = false;
|
||||
ready_ = false;
|
||||
failed_ = false;
|
||||
enabled_ = true;
|
||||
desired_state_ = GST_STATE_PAUSED;
|
||||
|
||||
// start index in frame_ stack
|
||||
write_index_ = 0;
|
||||
last_index_ = 0;
|
||||
|
||||
// no PBO by default
|
||||
pbo_[0] = pbo_[1] = 0;
|
||||
pbo_size_ = 0;
|
||||
pbo_index_ = 0;
|
||||
pbo_next_index_ = 0;
|
||||
|
||||
// OpenGL texture
|
||||
textureindex_ = 0;
|
||||
}
|
||||
|
||||
Stream::~Stream()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void Stream::accept(Visitor& v) {
|
||||
v.visit(*this);
|
||||
}
|
||||
|
||||
guint Stream::texture() const
|
||||
{
|
||||
if (textureindex_ == 0)
|
||||
return Resource::getTextureBlack();
|
||||
|
||||
return textureindex_;
|
||||
}
|
||||
|
||||
|
||||
void Stream::open(const std::string &gstreamer_description, int w, int h)
|
||||
{
|
||||
// set gstreamer pipeline source
|
||||
description_ = gstreamer_description;
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
// close before re-openning
|
||||
if (isOpen())
|
||||
close();
|
||||
|
||||
execute_open();
|
||||
}
|
||||
|
||||
|
||||
std::string Stream::description() const
|
||||
{
|
||||
return description_;
|
||||
}
|
||||
|
||||
void Stream::execute_open()
|
||||
{
|
||||
// reset
|
||||
ready_ = false;
|
||||
|
||||
// Add custom app sink to the gstreamer pipeline
|
||||
string description = description_;
|
||||
description += " ! appsink name=sink";
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("Stream %s Could not construct pipeline %s:\n%s", std::to_string(id_).c_str(), description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL);
|
||||
|
||||
// GstCaps *caps = gst_static_caps_get (&frame_render_caps);
|
||||
string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(width_) +
|
||||
",height=" + std::to_string(height_);
|
||||
GstCaps *caps = gst_caps_from_string(capstring.c_str());
|
||||
if (!caps || !gst_video_info_from_caps (&v_frame_video_info_, caps)) {
|
||||
Log::Warning("Stream %d Could not configure video frame info", id_);
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// setup appsink
|
||||
GstElement *sink = gst_bin_get_by_name (GST_BIN (pipeline_), "sink");
|
||||
if (!sink) {
|
||||
Log::Warning("Stream %s Could not configure sink", std::to_string(id_).c_str());
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// instruct sink to use the required caps
|
||||
gst_app_sink_set_caps (GST_APP_SINK(sink), caps);
|
||||
|
||||
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached.
|
||||
gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 30);
|
||||
gst_app_sink_set_drop (GST_APP_SINK(sink), true);
|
||||
|
||||
#ifdef USE_GST_APPSINK_CALLBACKS
|
||||
// set the callbacks
|
||||
GstAppSinkCallbacks callbacks;
|
||||
if (single_frame_) {
|
||||
callbacks.new_preroll = callback_new_preroll;
|
||||
callbacks.eos = NULL;
|
||||
callbacks.new_sample = NULL;
|
||||
Log::Info("Stream %s contains a single frame", std::to_string(id_).c_str());
|
||||
}
|
||||
else {
|
||||
callbacks.new_preroll = callback_new_preroll;
|
||||
callbacks.eos = callback_end_of_stream;
|
||||
callbacks.new_sample = callback_new_sample;
|
||||
}
|
||||
gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, this, NULL);
|
||||
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), false);
|
||||
#else
|
||||
// connect signals callbacks
|
||||
g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (callback_new_preroll), this);
|
||||
if (!single_frame_) {
|
||||
g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (callback_new_sample), this);
|
||||
g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (callback_end_of_stream), this);
|
||||
}
|
||||
gst_app_sink_set_emit_signals (GST_APP_SINK(sink), true);
|
||||
#endif
|
||||
|
||||
// set to desired state (PLAY or PAUSE)
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("Stream %s Could not open '%s'", std::to_string(id_).c_str(), description_.c_str());
|
||||
failed_ = true;
|
||||
return;
|
||||
}
|
||||
else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
|
||||
Log::Info("Stream %s is a live stream", std::to_string(id_).c_str());
|
||||
live_ = true;
|
||||
}
|
||||
|
||||
// instruct the sink to send samples synched in time if not live source
|
||||
gst_base_sink_set_sync (GST_BASE_SINK(sink), !live_);
|
||||
|
||||
// all good
|
||||
Log::Info("Stream %s Opened '%s' (%d x %d)", std::to_string(id_).c_str(), description.c_str(), width_, height_);
|
||||
ready_ = true;
|
||||
|
||||
// done with refs
|
||||
gst_object_unref (sink);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
bool Stream::isOpen() const
|
||||
{
|
||||
return ready_;
|
||||
}
|
||||
|
||||
bool Stream::failed() const
|
||||
{
|
||||
return failed_;
|
||||
}
|
||||
|
||||
void Stream::Frame::unmap()
|
||||
{
|
||||
if ( full ) {
|
||||
gst_video_frame_unmap(&vframe);
|
||||
full = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::close()
|
||||
{
|
||||
// not openned?
|
||||
if (!ready_) {
|
||||
// nothing else to change
|
||||
return;
|
||||
}
|
||||
|
||||
// un-ready
|
||||
ready_ = false;
|
||||
|
||||
// clean up GST
|
||||
if (pipeline_ != nullptr) {
|
||||
// force flush
|
||||
GstState state;
|
||||
gst_element_send_event(pipeline_, gst_event_new_seek (1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
|
||||
GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_END, 0) );
|
||||
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
|
||||
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
if (ret == GST_STATE_CHANGE_ASYNC) {
|
||||
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
|
||||
}
|
||||
gst_object_unref (pipeline_);
|
||||
pipeline_ = nullptr;
|
||||
}
|
||||
desired_state_ = GST_STATE_PAUSED;
|
||||
|
||||
// cleanup eventual remaining frame memory
|
||||
for(guint i = 0; i < N_FRAME; i++){
|
||||
frame_[i].access.lock();
|
||||
frame_[i].unmap();
|
||||
frame_[i].access.unlock();
|
||||
}
|
||||
write_index_ = 0;
|
||||
last_index_ = 0;
|
||||
|
||||
// cleanup opengl texture
|
||||
if (textureindex_)
|
||||
glDeleteTextures(1, &textureindex_);
|
||||
textureindex_ = 0;
|
||||
|
||||
// cleanup picture buffer
|
||||
if (pbo_[0])
|
||||
glDeleteBuffers(2, pbo_);
|
||||
pbo_size_ = 0;
|
||||
}
|
||||
|
||||
|
||||
guint Stream::width() const
|
||||
{
|
||||
return width_;
|
||||
}
|
||||
|
||||
guint Stream::height() const
|
||||
{
|
||||
return height_;
|
||||
}
|
||||
|
||||
float Stream::aspectRatio() const
|
||||
{
|
||||
return static_cast<float>(width_) / static_cast<float>(height_);
|
||||
}
|
||||
|
||||
void Stream::enable(bool on)
|
||||
{
|
||||
if ( !ready_ || pipeline_ == nullptr)
|
||||
return;
|
||||
|
||||
if ( enabled_ != on ) {
|
||||
|
||||
enabled_ = on;
|
||||
|
||||
// default to pause
|
||||
GstState requested_state = GST_STATE_PAUSED;
|
||||
|
||||
// unpause only if enabled
|
||||
if (enabled_) {
|
||||
requested_state = desired_state_;
|
||||
}
|
||||
|
||||
// apply state change
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, requested_state);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("Stream %s Failed to enable", std::to_string(id_).c_str());
|
||||
failed_ = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool Stream::enabled() const
|
||||
{
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
bool Stream::singleFrame() const
|
||||
{
|
||||
return single_frame_;
|
||||
}
|
||||
|
||||
bool Stream::live() const
|
||||
{
|
||||
return live_;
|
||||
}
|
||||
|
||||
void Stream::play(bool on)
|
||||
{
|
||||
// ignore if disabled, and cannot play an image
|
||||
if (!enabled_)
|
||||
return;
|
||||
|
||||
// request state
|
||||
GstState requested_state = on ? GST_STATE_PLAYING : GST_STATE_PAUSED;
|
||||
|
||||
// ignore if requesting twice same state
|
||||
if (desired_state_ == requested_state)
|
||||
return;
|
||||
|
||||
// accept request to the desired state
|
||||
desired_state_ = requested_state;
|
||||
|
||||
// if not ready yet, the requested state will be handled later
|
||||
if ( pipeline_ == nullptr )
|
||||
return;
|
||||
|
||||
// all ready, apply state change immediately
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, desired_state_);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("Stream %s Failed to play", std::to_string(id_).c_str());
|
||||
failed_ = true;
|
||||
}
|
||||
#ifdef STREAM_DEBUG
|
||||
else if (on)
|
||||
Log::Info("Stream %s Start", std::to_string(id_).c_str());
|
||||
else
|
||||
Log::Info("Stream %s Stop", std::to_string(id_).c_str());
|
||||
#endif
|
||||
|
||||
// activate live-source
|
||||
if (live_)
|
||||
gst_element_get_state (pipeline_, NULL, NULL, GST_CLOCK_TIME_NONE);
|
||||
|
||||
// reset time counter
|
||||
timecount_.reset();
|
||||
|
||||
}
|
||||
|
||||
bool Stream::isPlaying(bool testpipeline) const
|
||||
{
|
||||
// if not ready yet, answer with requested state
|
||||
if ( !testpipeline || pipeline_ == nullptr || !enabled_)
|
||||
return desired_state_ == GST_STATE_PLAYING;
|
||||
|
||||
// if ready, answer with actual state
|
||||
GstState state;
|
||||
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
|
||||
return state == GST_STATE_PLAYING;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Stream::init_texture(guint index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glGenTextures(1, &textureindex_);
|
||||
glBindTexture(GL_TEXTURE_2D, textureindex_);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width_, height_);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
// set pbo image size
|
||||
pbo_size_ = height_ * width_ * 4;
|
||||
|
||||
// create pixel buffer objects,
|
||||
if (pbo_[0])
|
||||
glDeleteBuffers(2, pbo_);
|
||||
glGenBuffers(2, pbo_);
|
||||
|
||||
for(int i = 0; i < 2; i++ ) {
|
||||
// create 2 PBOs
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[i]);
|
||||
// glBufferDataARB with NULL pointer reserves only memory space.
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW);
|
||||
// fill in with reset picture
|
||||
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
|
||||
if (ptr) {
|
||||
// update data directly on the mapped buffer
|
||||
memmove(ptr, frame_[index].vframe.data[0], pbo_size_);
|
||||
// release pointer to mapping buffer
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
else {
|
||||
// did not work, disable PBO
|
||||
glDeleteBuffers(2, pbo_);
|
||||
pbo_[0] = pbo_[1] = 0;
|
||||
pbo_size_ = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// should be good to go, wrap it up
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
pbo_index_ = 0;
|
||||
pbo_next_index_ = 1;
|
||||
|
||||
#ifdef STREAM_DEBUG
|
||||
Log::Info("Stream %s Use Pixel Buffer Object texturing.", std::to_string(id_).c_str());
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Stream::fill_texture(guint index)
|
||||
{
|
||||
// is this the first frame ?
|
||||
if (textureindex_ < 1)
|
||||
{
|
||||
// initialize texture
|
||||
init_texture(index);
|
||||
|
||||
}
|
||||
else {
|
||||
glBindTexture(GL_TEXTURE_2D, textureindex_);
|
||||
|
||||
// use dual Pixel Buffer Object
|
||||
if (pbo_size_ > 0) {
|
||||
// In dual PBO mode, increment current index first then get the next index
|
||||
pbo_index_ = (pbo_index_ + 1) % 2;
|
||||
pbo_next_index_ = (pbo_index_ + 1) % 2;
|
||||
|
||||
// bind PBO to read pixels
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_index_]);
|
||||
// copy pixels from PBO to texture object
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
// bind the next PBO to write pixels
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[pbo_next_index_]);
|
||||
// See http://www.songho.ca/opengl/gl_pbo.html#map for more details
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW);
|
||||
// map the buffer object into client's memory
|
||||
GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
|
||||
if (ptr) {
|
||||
// update data directly on the mapped buffer
|
||||
// NB : equivalent but faster (memmove instead of memcpy ?) than
|
||||
// glNamedBufferSubData(pboIds[nextIndex], 0, imgsize, vp->getBuffer())
|
||||
memmove(ptr, frame_[index].vframe.data[0], pbo_size_);
|
||||
|
||||
// release pointer to mapping buffer
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
// done with PBO
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
else {
|
||||
// without PBO, use standard opengl (slower)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::update()
|
||||
{
|
||||
// discard
|
||||
if (failed_)
|
||||
return;
|
||||
|
||||
// not ready yet
|
||||
if (!ready_)
|
||||
return;
|
||||
|
||||
// // prevent unnecessary updates: disabled or already filled image
|
||||
// if (!enabled_)
|
||||
// return;
|
||||
|
||||
// local variables before trying to update
|
||||
guint read_index = 0;
|
||||
bool need_loop = false;
|
||||
|
||||
// locked access to current index
|
||||
index_lock_.lock();
|
||||
// get the last frame filled from fill_frame()
|
||||
read_index = last_index_;
|
||||
// Do NOT miss and jump directly to a pre-roll
|
||||
for (guint i = 0; i < N_FRAME; ++i) {
|
||||
if (frame_[i].status == PREROLL) {
|
||||
read_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// unlock access to index change
|
||||
index_lock_.unlock();
|
||||
|
||||
// lock frame while reading it
|
||||
frame_[read_index].access.lock();
|
||||
|
||||
// do not fill a frame twice
|
||||
if (frame_[read_index].status != INVALID ) {
|
||||
|
||||
// is this an End-of-Stream frame ?
|
||||
if (frame_[read_index].status == EOS )
|
||||
{
|
||||
// will execute seek command below (after unlock)
|
||||
need_loop = true;
|
||||
}
|
||||
// otherwise just fill non-empty SAMPLE or PREROLL
|
||||
else if (frame_[read_index].full)
|
||||
{
|
||||
// fill the texture with the frame at reading index
|
||||
fill_texture(read_index);
|
||||
|
||||
// double update for pre-roll frame and dual PBO (ensure frame is displayed now)
|
||||
if (frame_[read_index].status == PREROLL && pbo_size_ > 0)
|
||||
fill_texture(read_index);
|
||||
|
||||
// free frame
|
||||
frame_[read_index].unmap();
|
||||
}
|
||||
|
||||
// avoid reading it again
|
||||
frame_[read_index].status = INVALID;
|
||||
}
|
||||
|
||||
// unkock frame after reading it
|
||||
frame_[read_index].access.unlock();
|
||||
|
||||
if (need_loop) {
|
||||
// stop on end of stream
|
||||
play(false);
|
||||
}
|
||||
}
|
||||
|
||||
double Stream::updateFrameRate() const
|
||||
{
|
||||
return timecount_.frameRate();
|
||||
}
|
||||
|
||||
|
||||
// CALLBACKS
|
||||
|
||||
bool Stream::fill_frame(GstBuffer *buf, FrameStatus status)
|
||||
{
|
||||
// Log::Info("Stream fill frame");
|
||||
|
||||
// Do NOT overwrite an unread EOS
|
||||
if ( frame_[write_index_].status == EOS )
|
||||
write_index_ = (write_index_ + 1) % N_FRAME;
|
||||
|
||||
// lock access to frame
|
||||
frame_[write_index_].access.lock();
|
||||
|
||||
// always empty frame before filling it again
|
||||
frame_[write_index_].unmap();
|
||||
|
||||
// accept status of frame received
|
||||
frame_[write_index_].status = status;
|
||||
|
||||
// a buffer is given (not EOS)
|
||||
if (buf != NULL) {
|
||||
// get the frame from buffer
|
||||
if ( !gst_video_frame_map (&frame_[write_index_].vframe, &v_frame_video_info_, buf, GST_MAP_READ ) )
|
||||
{
|
||||
Log::Info("Stream %s Failed to map the video buffer", std::to_string(id_).c_str());
|
||||
// free access to frame & exit
|
||||
frame_[write_index_].status = INVALID;
|
||||
frame_[write_index_].access.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
// successfully filled the frame
|
||||
frame_[write_index_].full = true;
|
||||
|
||||
// validate frame format
|
||||
if( GST_VIDEO_INFO_IS_RGB(&(frame_[write_index_].vframe).info) && GST_VIDEO_INFO_N_PLANES(&(frame_[write_index_].vframe).info) == 1)
|
||||
{
|
||||
// set presentation time stamp
|
||||
frame_[write_index_].position = buf->pts;
|
||||
|
||||
}
|
||||
// full but invalid frame : will be deleted next iteration
|
||||
// (should never happen)
|
||||
else {
|
||||
#ifdef STREAM_DEBUG
|
||||
Log::Info("Stream %s Received an Invalid frame", std::to_string(id_).c_str());
|
||||
#endif
|
||||
frame_[write_index_].status = INVALID;
|
||||
frame_[write_index_].access.unlock();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// else; null buffer for EOS: give a position
|
||||
else {
|
||||
frame_[write_index_].status = EOS;
|
||||
#ifdef STREAM_DEBUG
|
||||
Log::Info("Stream %s Reached End Of Stream", std::to_string(id_).c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
// unlock access to frame
|
||||
frame_[write_index_].access.unlock();
|
||||
|
||||
// lock access to change current index (very quick)
|
||||
index_lock_.lock();
|
||||
// indicate update() that this is the last frame filled (and unlocked)
|
||||
last_index_ = write_index_;
|
||||
// unlock access to index change
|
||||
index_lock_.unlock();
|
||||
// for writing, we will access the next in stack
|
||||
write_index_ = (write_index_ + 1) % N_FRAME;
|
||||
|
||||
// calculate actual FPS of update
|
||||
timecount_.tic();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stream::callback_end_of_stream (GstAppSink *, gpointer p)
|
||||
{
|
||||
Stream *m = static_cast<Stream *>(p);
|
||||
if (m && m->ready_) {
|
||||
m->fill_frame(NULL, Stream::EOS);
|
||||
}
|
||||
}
|
||||
|
||||
GstFlowReturn Stream::callback_new_preroll (GstAppSink *sink, gpointer p)
|
||||
{
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
|
||||
// blocking read pre-roll samples
|
||||
GstSample *sample = gst_app_sink_pull_preroll(sink);
|
||||
|
||||
// if got a valid sample
|
||||
if (sample != NULL) {
|
||||
// send frames to media player only if ready
|
||||
Stream *m = static_cast<Stream *>(p);
|
||||
if (m && m->ready_) {
|
||||
|
||||
// get buffer from sample
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample);
|
||||
|
||||
// fill frame from buffer
|
||||
if ( !m->fill_frame(buf, Stream::PREROLL) )
|
||||
ret = GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
else
|
||||
ret = GST_FLOW_FLUSHING;
|
||||
|
||||
// release sample
|
||||
gst_sample_unref (sample);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GstFlowReturn Stream::callback_new_sample (GstAppSink *sink, gpointer p)
|
||||
{
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
|
||||
// if (gst_app_sink_is_eos (sink))
|
||||
// Log::Info("callback_new_sample got EOS");
|
||||
|
||||
// non-blocking read new sample
|
||||
GstSample *sample = gst_app_sink_pull_sample(sink);
|
||||
|
||||
// if got a valid sample
|
||||
if (sample != NULL && !gst_app_sink_is_eos (sink)) {
|
||||
|
||||
// send frames to media player only if ready
|
||||
Stream *m = static_cast<Stream *>(p);
|
||||
if (m && m->ready_) {
|
||||
|
||||
// get buffer from sample (valid until sample is released)
|
||||
GstBuffer *buf = gst_sample_get_buffer (sample) ;
|
||||
|
||||
// fill frame with buffer
|
||||
if ( !m->fill_frame(buf, Stream::SAMPLE) )
|
||||
ret = GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
else
|
||||
ret = GST_FLOW_FLUSHING;
|
||||
|
||||
// release sample
|
||||
gst_sample_unref (sample);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Stream::TimeCounter::TimeCounter() {
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Stream::TimeCounter::tic ()
|
||||
{
|
||||
// how long since last time
|
||||
GstClockTime t = gst_util_get_timestamp ();
|
||||
GstClockTime dt = t - last_time;
|
||||
|
||||
// one more frame since last time
|
||||
nbFrames++;
|
||||
|
||||
// calculate instantaneous framerate
|
||||
// Exponential moving averate with previous framerate to filter jitter (50/50)
|
||||
// The divition of frame/time is done on long integer GstClockTime, counting in microsecond
|
||||
// NB: factor 100 to get 0.01 precision
|
||||
fps = 0.5 * fps + 0.005 * static_cast<double>( ( 100 * GST_SECOND * nbFrames ) / dt );
|
||||
|
||||
// reset counter every second
|
||||
if ( dt >= GST_SECOND)
|
||||
{
|
||||
last_time = t;
|
||||
nbFrames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
GstClockTime Stream::TimeCounter::dt ()
|
||||
{
|
||||
GstClockTime t = gst_util_get_timestamp ();
|
||||
GstClockTime dt = t - tic_time;
|
||||
tic_time = t;
|
||||
|
||||
// return the instantaneous delta t
|
||||
return dt;
|
||||
}
|
||||
|
||||
void Stream::TimeCounter::reset ()
|
||||
{
|
||||
last_time = gst_util_get_timestamp ();;
|
||||
tic_time = last_time;
|
||||
nbFrames = 0;
|
||||
fps = 0.0;
|
||||
}
|
||||
|
||||
double Stream::TimeCounter::frameRate() const
|
||||
{
|
||||
return fps;
|
||||
}
|
||||
|
||||
201
Stream.h
Normal file
201
Stream.h
Normal file
@@ -0,0 +1,201 @@
|
||||
#ifndef STREAM_H
|
||||
#define STREAM_H
|
||||
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <future>
|
||||
|
||||
// GStreamer
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsink.h>
|
||||
|
||||
// Forward declare classes referenced
|
||||
class Visitor;
|
||||
|
||||
#define N_FRAME 3
|
||||
|
||||
class Stream {
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor of a GStreamer Stream
|
||||
*/
|
||||
Stream();
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~Stream();
|
||||
/**
|
||||
* Get unique id
|
||||
*/
|
||||
inline uint64_t id() const { return id_; }
|
||||
/**
|
||||
* Open a media using gstreamer pipeline keyword
|
||||
* */
|
||||
void open(const std::string &gstreamer_description, int w = 1024, int h = 576);
|
||||
/**
|
||||
* Get description string
|
||||
* */
|
||||
std::string description() const;
|
||||
/**
|
||||
* True if a media was oppenned
|
||||
* */
|
||||
bool isOpen() const;
|
||||
/**
|
||||
* True if problem occured
|
||||
* */
|
||||
bool failed() const;
|
||||
/**
|
||||
* Close the Media
|
||||
* */
|
||||
void close();
|
||||
/**
|
||||
* Update texture with latest frame
|
||||
* Must be called in rendering update loop
|
||||
* */
|
||||
virtual void update();
|
||||
/**
|
||||
* Enable / Disable
|
||||
* Suspend playing activity
|
||||
* (restores playing state when re-enabled)
|
||||
* */
|
||||
void enable(bool on);
|
||||
/**
|
||||
* True if enabled
|
||||
* */
|
||||
bool enabled() const;
|
||||
/**
|
||||
* True if its an image
|
||||
* */
|
||||
bool singleFrame() const;
|
||||
/**
|
||||
* True if its a live stream
|
||||
* */
|
||||
bool live() const;
|
||||
/**
|
||||
* Pause / Play
|
||||
* Can play backward if play speed is negative
|
||||
* */
|
||||
void play(bool on);
|
||||
/**
|
||||
* Get Pause / Play status
|
||||
* Performs a full check of the Gstreamer pipeline if testpipeline is true
|
||||
* */
|
||||
bool isPlaying(bool testpipeline = false) const;
|
||||
/**
|
||||
* Get rendering update framerate
|
||||
* measured during play
|
||||
* */
|
||||
double updateFrameRate() const;
|
||||
/**
|
||||
* Get frame width
|
||||
* */
|
||||
guint width() const;
|
||||
/**
|
||||
* Get frame height
|
||||
* */
|
||||
guint height() const;
|
||||
/**
|
||||
* Get frames displayt aspect ratio
|
||||
* NB: can be different than width() / height()
|
||||
* */
|
||||
float aspectRatio() const;
|
||||
/**
|
||||
* Get the OpenGL texture
|
||||
* Must be called in OpenGL context
|
||||
* */
|
||||
guint texture() const;
|
||||
/**
|
||||
* Accept visitors
|
||||
* Used for saving session file
|
||||
* */
|
||||
void accept(Visitor& v);
|
||||
|
||||
protected:
|
||||
|
||||
// video player description
|
||||
uint64_t id_;
|
||||
std::string description_;
|
||||
guint textureindex_;
|
||||
|
||||
// general properties of media
|
||||
guint width_;
|
||||
guint height_;
|
||||
bool single_frame_;
|
||||
bool live_;
|
||||
|
||||
// GST & Play status
|
||||
GstState desired_state_;
|
||||
GstElement *pipeline_;
|
||||
GstVideoInfo v_frame_video_info_;
|
||||
std::atomic<bool> ready_;
|
||||
std::atomic<bool> failed_;
|
||||
bool enabled_;
|
||||
|
||||
// fps counter
|
||||
struct TimeCounter {
|
||||
|
||||
GstClockTime last_time;
|
||||
GstClockTime tic_time;
|
||||
int nbFrames;
|
||||
gdouble fps;
|
||||
public:
|
||||
TimeCounter();
|
||||
GstClockTime dt();
|
||||
void tic();
|
||||
void reset();
|
||||
gdouble frameRate() const;
|
||||
};
|
||||
TimeCounter timecount_;
|
||||
|
||||
// frame stack
|
||||
typedef enum {
|
||||
SAMPLE = 0,
|
||||
PREROLL = 1,
|
||||
EOS = 2,
|
||||
INVALID = 3
|
||||
} FrameStatus;
|
||||
|
||||
struct Frame {
|
||||
GstVideoFrame vframe;
|
||||
FrameStatus status;
|
||||
bool full;
|
||||
GstClockTime position;
|
||||
std::mutex access;
|
||||
|
||||
Frame() {
|
||||
full = false;
|
||||
status = INVALID;
|
||||
position = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
void unmap();
|
||||
};
|
||||
Frame frame_[N_FRAME];
|
||||
guint write_index_;
|
||||
guint last_index_;
|
||||
std::mutex index_lock_;
|
||||
|
||||
// for PBO
|
||||
guint pbo_[2];
|
||||
guint pbo_index_, pbo_next_index_;
|
||||
guint pbo_size_;
|
||||
|
||||
// gst pipeline control
|
||||
virtual void execute_open();
|
||||
|
||||
// gst frame filling
|
||||
void init_texture(guint index);
|
||||
void fill_texture(guint index);
|
||||
bool fill_frame(GstBuffer *buf, FrameStatus status);
|
||||
|
||||
// gst callbacks
|
||||
static void callback_end_of_stream (GstAppSink *, gpointer);
|
||||
static GstFlowReturn callback_new_preroll (GstAppSink *, gpointer );
|
||||
static GstFlowReturn callback_new_sample (GstAppSink *, gpointer);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // STREAM_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user