]> git.sesse.net Git - casparcg/blob - common/diagnostics/graph.cpp
5378f515642683b7f30d24d2ef7dcb515cbc18f7
[casparcg] / common / diagnostics / graph.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This file is part of CasparCG.\r
5 *\r
6 *    CasparCG is free software: you can redistribute it and/or modify\r
7 *    it under the terms of the GNU General Public License as published by\r
8 *    the Free Software Foundation, either version 3 of the License, or\r
9 *    (at your option) any later version.\r
10 *\r
11 *    CasparCG is distributed in the hope that it will be useful,\r
12 *    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 *    GNU General Public License for more details.\r
15 \r
16 *    You should have received a copy of the GNU General Public License\r
17 *    along with CasparCG.  If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 */\r
20 #include "../stdafx.h"\r
21 \r
22 #include "graph.h"\r
23 \r
24 #pragma warning (disable : 4244)\r
25 \r
26 #include "../concurrency/executor.h"\r
27 #include "../env.h"\r
28 \r
29 #include <SFML/Graphics.hpp>\r
30 \r
31 #include <boost/foreach.hpp>\r
32 #include <boost/optional.hpp>\r
33 #include <boost/circular_buffer.hpp>\r
34 #include <boost/range/algorithm_ext/erase.hpp>\r
35 \r
36 #include <numeric>\r
37 #include <map>\r
38 #include <array>\r
39 \r
40 namespace caspar { namespace diagnostics {\r
41                 \r
42 struct drawable : public sf::Drawable\r
43 {\r
44         virtual ~drawable(){}\r
45         virtual void render(sf::RenderTarget& target) = 0;\r
46         virtual void Render(sf::RenderTarget& target) const { const_cast<drawable*>(this)->render(target);}\r
47 };\r
48 \r
49 class context : public drawable\r
50 {       \r
51         sf::RenderWindow window_;\r
52         \r
53         std::list<std::shared_ptr<drawable>> drawables_;\r
54                 \r
55         executor executor_;\r
56 public:                                 \r
57 \r
58         template<typename Func>\r
59         static auto begin_invoke(Func&& func) -> boost::unique_future<decltype(func())> // noexcept\r
60         {       \r
61                 return get_instance().executor_.begin_invoke(std::forward<Func>(func)); \r
62         }\r
63 \r
64         static void register_drawable(const std::shared_ptr<drawable>& drawable)\r
65         {\r
66                 if(!drawable)\r
67                         return;\r
68 \r
69                 begin_invoke([=]\r
70                 {\r
71                         get_instance().do_register_drawable(drawable);\r
72                 });\r
73         }\r
74                         \r
75 private:\r
76         context() : executor_(L"diagnostics")\r
77         {\r
78                 executor_.begin_invoke([this]\r
79                 {                       \r
80                         SetThreadPriority(GetCurrentThread(), BELOW_NORMAL_PRIORITY_CLASS);\r
81                         window_.Create(sf::VideoMode(600, 1000), "CasparCG Diagnostics");\r
82                         window_.SetPosition(0, 0);\r
83                         window_.SetActive();\r
84                         glEnable(GL_BLEND);\r
85                         glEnable(GL_LINE_SMOOTH);\r
86                         glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);\r
87                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\r
88                         tick();\r
89                 });\r
90         }\r
91 \r
92         void tick()\r
93         {\r
94                 sf::Event e;\r
95                 while(window_.GetEvent(e)){}            \r
96                 glClear(GL_COLOR_BUFFER_BIT);\r
97                 window_.Draw(*this);\r
98                 window_.Display();\r
99                 boost::this_thread::sleep(boost::posix_time::milliseconds(20));\r
100                 executor_.begin_invoke([this]{tick();});\r
101         }\r
102 \r
103         void render(sf::RenderTarget& target)\r
104         {\r
105                 auto count = std::max<size_t>(8, drawables_.size());\r
106                 float target_dy = 1.0f/static_cast<float>(count);\r
107 \r
108                 float last_y = 0.0f;\r
109                 int n = 0;\r
110                 for(auto it = drawables_.begin(); it != drawables_.end(); ++n)\r
111                 {\r
112                         auto& drawable = *it;\r
113                         if(!drawable.unique())\r
114                         {\r
115                                 drawable->SetScale(static_cast<float>(window_.GetWidth()), static_cast<float>(target_dy*window_.GetHeight()));\r
116                                 float target_y = std::max(last_y, static_cast<float>(n * window_.GetHeight())*target_dy);\r
117                                 drawable->SetPosition(0.0f, target_y);                  \r
118                                 target.Draw(*drawable);                         \r
119                                 ++it;           \r
120                         }\r
121                         else    \r
122                                 it = drawables_.erase(it);                      \r
123                 }               \r
124         }       \r
125         \r
126         void do_register_drawable(const std::shared_ptr<drawable>& drawable)\r
127         {\r
128                 drawables_.push_back(drawable);\r
129         }\r
130         \r
131         static context& get_instance()\r
132         {\r
133                 static context impl;\r
134                 return impl;\r
135         }\r
136 };\r
137 \r
138 class guide : public drawable\r
139 {\r
140         float value_;\r
141         color c_;\r
142 public:\r
143         guide(color c = color(1.0f, 1.0f, 1.0f, 0.6f)) \r
144                 : value_(0.0f)\r
145                 , c_(c){}\r
146 \r
147         guide(float value, color c = color(1.0f, 1.0f, 1.0f, 0.6f)) \r
148                 : value_(value)\r
149                 , c_(c){}\r
150                         \r
151         void set_color(color c) {c_ = c;}\r
152 \r
153         void render(sf::RenderTarget&)\r
154         {               \r
155                 glEnable(GL_LINE_STIPPLE);\r
156                 glLineStipple(3, 0xAAAA);\r
157                 glBegin(GL_LINE_STRIP); \r
158                         glColor4f(c_.red, c_.green, c_.blue+0.2f, c_.alpha);            \r
159                         glVertex3f(0.0f, (1.0f-value_) * 0.8f + 0.1f, 0.0f);            \r
160                         glVertex3f(1.0f, (1.0f-value_) * 0.8f + 0.1f, 0.0f);    \r
161                 glEnd();\r
162                 glDisable(GL_LINE_STIPPLE);\r
163         }\r
164 };\r
165 \r
166 class line : public drawable\r
167 {\r
168         boost::optional<diagnostics::guide> guide_;\r
169         boost::circular_buffer<std::pair<double, bool>> line_data_;\r
170 \r
171         std::vector<double>             tick_data_;\r
172         bool                                    tick_tag_;\r
173         color c_;\r
174 public:\r
175         line(size_t res = 600)\r
176                 : line_data_(res)\r
177                 , tick_tag_(false)\r
178                 , c_(1.0f, 1.0f, 1.0f)\r
179         {\r
180                 line_data_.push_back(std::make_pair(-1.0f, false));\r
181         }\r
182         \r
183         void update(double value)\r
184         {\r
185                 tick_data_.push_back(value);\r
186         }\r
187 \r
188         void set(double value)\r
189         {\r
190                 tick_data_.clear();\r
191                 tick_data_.push_back(value);\r
192         }\r
193         \r
194         void tag()\r
195         {\r
196                 tick_tag_ = true;\r
197         }\r
198 \r
199         void guide(const guide& guide)\r
200         {\r
201                 guide_ = guide;\r
202                 guide_->set_color(c_);\r
203         }\r
204         \r
205         void set_color(color c)\r
206         {\r
207                 c_ = c;\r
208                 if(guide_)\r
209                         guide_->set_color(c_);\r
210         }\r
211 \r
212         color get_color() const { return c_; }\r
213         \r
214         void render(sf::RenderTarget& target)\r
215         {\r
216                 float dx = 1.0f/static_cast<float>(line_data_.capacity());\r
217                 float x = static_cast<float>(line_data_.capacity()-line_data_.size())*dx;\r
218 \r
219                 if(!tick_data_.empty())\r
220                 {\r
221                         float sum = std::accumulate(tick_data_.begin(), tick_data_.end(), 0.0) + std::numeric_limits<float>::min();\r
222                         line_data_.push_back(std::make_pair(static_cast<float>(sum)/static_cast<float>(tick_data_.size()), tick_tag_));\r
223                         tick_data_.clear();\r
224                 }\r
225                 else if(!line_data_.empty())\r
226                 {\r
227                         line_data_.push_back(std::make_pair(line_data_.back().first, tick_tag_));\r
228                 }\r
229                 tick_tag_ = false;\r
230 \r
231                 if(guide_)\r
232                         target.Draw(*guide_);\r
233                 \r
234                 glBegin(GL_LINE_STRIP);\r
235                 glColor4f(c_.red, c_.green, c_.blue, 0.8f);             \r
236                 for(size_t n = 0; n < line_data_.size(); ++n)           \r
237                         if(line_data_[n].first > -0.5)\r
238                                 glVertex3d(x+n*dx, std::max(0.05, std::min(0.95, (1.0f-line_data_[n].first)*0.8 + 0.1f)), 0.0);         \r
239                 glEnd();\r
240                                 \r
241                 glEnable(GL_LINE_STIPPLE);\r
242                 glLineStipple(3, 0xAAAA);\r
243                 for(size_t n = 0; n < line_data_.size(); ++n)   \r
244                 {\r
245                         if(line_data_[n].second)\r
246                         {\r
247                                 glBegin(GL_LINE_STRIP);\r
248                                 glColor4f(c_.red, c_.green, c_.blue, c_.alpha);                                 \r
249                                         glVertex3f(x+n*dx, 0.0f, 0.0f);                         \r
250                                         glVertex3f(x+n*dx, 1.0f, 0.0f);         \r
251                                 glEnd();\r
252                         }\r
253                 }\r
254                 glDisable(GL_LINE_STIPPLE);\r
255         }\r
256 };\r
257 \r
258 struct graph::implementation : public drawable\r
259 {\r
260         std::map<std::string, diagnostics::line> lines_;\r
261         const printer parent_printer_;\r
262         std::string name_;\r
263 \r
264         int counter_;\r
265 \r
266         implementation(const std::string& name) \r
267                 : name_(name)\r
268                 , counter_(0){}\r
269 \r
270         implementation(const printer& parent_printer) \r
271                 : parent_printer_(parent_printer)\r
272                 , name_(parent_printer_ ? narrow(parent_printer_()) : "")\r
273                 , counter_(0){}\r
274 \r
275         void update(const std::string& name, double value)\r
276         {\r
277                 lines_[name].update(value);\r
278         }\r
279 \r
280         void set(const std::string& name, double value)\r
281         {\r
282                 lines_[name].set(value);\r
283         }\r
284 \r
285         void tag(const std::string& name)\r
286         {\r
287                 lines_[name].tag();\r
288         }\r
289 \r
290         void set_color(const std::string& name, color c)\r
291         {\r
292                 lines_[name].set_color(c);\r
293         }\r
294         \r
295         void guide(const std::string& name, double value)\r
296         {\r
297                 lines_[name].guide(diagnostics::guide(value));  \r
298         }\r
299         \r
300 private:\r
301         void render(sf::RenderTarget& target)\r
302         {\r
303                 if(counter_++ > 25) // Don't update name too often since print can be implemented with locks.\r
304                 {\r
305                         counter_ = 0;\r
306                         if(parent_printer_)\r
307                                 name_ = narrow(parent_printer_());\r
308                 }\r
309                 const size_t text_size = 15;\r
310                 const size_t text_margin = 2;\r
311                 const size_t text_offset = (text_size+text_margin*2)*2;\r
312 \r
313                 sf::String text(name_.c_str(), sf::Font::GetDefaultFont(), text_size);\r
314                 text.SetStyle(sf::String::Italic);\r
315                 text.Move(text_margin, text_margin);\r
316                 \r
317                 glPushMatrix();\r
318                         glScaled(1.0f/GetScale().x, 1.0f/GetScale().y, 1.0f);\r
319                         target.Draw(text);\r
320                         float x_offset = text_margin;\r
321                         for(auto it = lines_.begin(); it != lines_.end(); ++it)\r
322                         {                                               \r
323                                 sf::String line_text(it->first, sf::Font::GetDefaultFont(), text_size);\r
324                                 line_text.SetPosition(x_offset, text_margin+text_offset/2);\r
325                                 auto c = it->second.get_color();\r
326                                 line_text.SetColor(sf::Color(c.red*255.0f, c.green*255.0f, c.blue*255.0f, c.alpha*255.0f));\r
327                                 target.Draw(line_text);\r
328                                 x_offset = line_text.GetRect().Right + text_margin*2;\r
329                         }\r
330 \r
331                         glDisable(GL_TEXTURE_2D);\r
332                 glPopMatrix();\r
333 \r
334                 glBegin(GL_QUADS);\r
335                         glColor4f(1.0f, 1.0f, 1.0f, 0.2f);      \r
336                         glVertex2f(1.0f, 0.99f);\r
337                         glVertex2f(0.0f, 0.99f);\r
338                         glVertex2f(0.0f, 0.01f);        \r
339                         glVertex2f(1.0f, 0.01f);        \r
340                 glEnd();\r
341 \r
342                 glPushMatrix();\r
343                         glTranslated(0.0f, text_offset/GetScale().y, 1.0f);\r
344                         glScaled(1.0f, 1.0-text_offset/GetScale().y, 1.0f);\r
345                 \r
346                         target.Draw(diagnostics::guide(1.0f, color(1.0f, 1.0f, 1.0f, 0.6f)));\r
347                         target.Draw(diagnostics::guide(0.0f, color(1.0f, 1.0f, 1.0f, 0.6f)));\r
348 \r
349                         for(auto it = lines_.begin(); it != lines_.end(); ++it)         \r
350                                 target.Draw(it->second);\r
351                 \r
352                 glPopMatrix();\r
353         }\r
354 \r
355         implementation(implementation&);\r
356         implementation& operator=(implementation&);\r
357 };\r
358         \r
359 graph::graph(const std::string& name) : impl_(env::properties().get("configuration.diagnostics.graphs", true) ? new implementation(name) : nullptr)\r
360 {\r
361         if(impl_)\r
362                 context::register_drawable(impl_);\r
363 }\r
364 \r
365 graph::graph(const printer& parent_printer) : impl_(env::properties().get("configuration.diagnostics.graphs", true) ? new implementation(parent_printer) : nullptr)\r
366 {\r
367         if(impl_)\r
368                 context::register_drawable(impl_);\r
369 }\r
370 \r
371 void graph::update_value(const std::string& name, double value)\r
372 {\r
373         if(impl_)\r
374         {       \r
375                 auto p = impl_;\r
376                 context::begin_invoke([=]\r
377                 {       \r
378                         p->update(name, value);\r
379                 });\r
380         }\r
381 }\r
382 void graph::set_value(const std::string& name, double value)\r
383 {\r
384         if(impl_)\r
385         {               \r
386                 auto p = impl_;\r
387                 context::begin_invoke([=]\r
388                 {       \r
389                         p->set(name, value);\r
390                 });\r
391         }\r
392 }\r
393 void graph::set_color(const std::string& name, color c)\r
394 {       \r
395         if(impl_)\r
396         {               \r
397                 auto p = impl_;\r
398                 context::begin_invoke([=]\r
399                 {       \r
400                         p->set_color(name, c);\r
401                 });\r
402         }\r
403 }\r
404 void graph::add_tag(const std::string& name)\r
405 {       \r
406         if(impl_)\r
407         {               \r
408                 auto p = impl_;\r
409                 context::begin_invoke([=]\r
410                 {       \r
411                         p->tag(name);\r
412                 });\r
413         }\r
414 }\r
415 void graph::add_guide(const std::string& name, double value)\r
416 {       \r
417         if(impl_)\r
418         {               \r
419                 auto p = impl_;\r
420                 context::begin_invoke([=]\r
421                 {       \r
422                         p->guide(name, value);\r
423                 });\r
424         }\r
425 }\r
426 \r
427 safe_ptr<graph> create_graph(const std::string& name)\r
428 {\r
429         return safe_ptr<graph>(new graph(name));\r
430 }\r
431 safe_ptr<graph> create_graph(const printer& parent_printer)\r
432 {\r
433         return safe_ptr<graph>(new graph(parent_printer));\r
434 }\r
435 \r
436 \r
437 //namespace v2\r
438 //{     \r
439 //      \r
440 //struct line::implementation\r
441 //{\r
442 //      std::wstring name_;\r
443 //      boost::circular_buffer<data> ticks_;\r
444 //\r
445 //      implementation(const std::wstring& name) \r
446 //              : name_(name)\r
447 //              , ticks_(1024){}\r
448 //      \r
449 //      void update_value(float value)\r
450 //      {\r
451 //              ticks_.push_back();\r
452 //              ticks_.back().value = value;\r
453 //      }\r
454 //\r
455 //      void set_value(float value)\r
456 //      {\r
457 //              ticks_.clear();\r
458 //              update_value(value);\r
459 //      }\r
460 //};\r
461 //\r
462 //line::line(){}\r
463 //line::line(const std::wstring& name) : impl_(new implementation(name)){}\r
464 //std::wstring line::print() const {return impl_->name_;}\r
465 //void line::update_value(float value){impl_->update_value(value);}\r
466 //void line::set_value(float value){impl_->set_value(value);}\r
467 //boost::circular_buffer<data>& line::ticks() { return impl_->ticks_;}\r
468 //\r
469 //struct graph::implementation\r
470 //{\r
471 //      std::map<std::wstring, line> lines_;\r
472 //      color                                            color_;\r
473 //      printer                                          printer_;\r
474 //\r
475 //      implementation(const std::wstring& name) \r
476 //              : printer_([=]{return name;}){}\r
477 //\r
478 //      implementation(const printer& parent_printer) \r
479 //              : printer_(parent_printer){}\r
480 //      \r
481 //      void update_value(const std::wstring& name, float value)\r
482 //      {\r
483 //              auto it = lines_.find(name);\r
484 //              if(it == lines_.end())\r
485 //                      it = lines_.insert(std::make_pair(name, line(name))).first;\r
486 //\r
487 //              it->second.update_value(value);\r
488 //      }\r
489 //\r
490 //      void set_value(const std::wstring& name, float value)\r
491 //      {\r
492 //              auto it = lines_.find(name);\r
493 //              if(it == lines_.end())\r
494 //                      it = lines_.insert(std::make_pair(name, line(name))).first;\r
495 //\r
496 //              it->second.set_value(value);\r
497 //      }\r
498 //      \r
499 //      void set_color(const std::wstring& name, color color)\r
500 //      {\r
501 //              color_ = color;\r
502 //      }\r
503 //\r
504 //      std::map<std::wstring, line>& get_lines()\r
505 //      {\r
506 //              return lines_;\r
507 //      }\r
508 //      \r
509 //      color get_color() const\r
510 //      {\r
511 //              return color_;\r
512 //      }\r
513 //\r
514 //      std::wstring print() const\r
515 //      {\r
516 //              return printer_ ? printer_() : L"graph";\r
517 //      }\r
518 //};\r
519 //      \r
520 //graph::graph(const std::wstring& name) : impl_(new implementation(name)){}\r
521 //graph::graph(const printer& parent_printer) : impl_(new implementation(parent_printer)){}\r
522 //void graph::update_value(const std::wstring& name, float value){impl_->update_value(name, value);}\r
523 //void graph::set_value(const std::wstring& name, float value){impl_->set_value(name, value);}\r
524 //void graph::set_color(const std::wstring& name, color c){impl_->set_color(name, c);}\r
525 //color graph::get_color() const {return impl_->get_color();}\r
526 //std::wstring graph::print() const {return impl_->print();}\r
527 //\r
528 //safe_ptr<graph> graph::clone() const \r
529 //{\r
530 //      safe_ptr<graph> clone(new graph(std::wstring(L"")));\r
531 //      clone->impl_->printer_ = impl_->printer_;\r
532 //      clone->impl_->lines_ = impl_->lines_;\r
533 //      clone->impl_->color_ = impl_->color_;   \r
534 //}\r
535 //\r
536 //std::map<std::wstring, line>& graph::get_lines() {impl_->get_lines();}\r
537 //\r
538 //std::vector<safe_ptr<graph>> g_graphs;\r
539 //\r
540 //safe_ptr<graph> create_graph(const std::string& name)\r
541 //{\r
542 //      g_graphs.push_back(make_safe<graph>(name));\r
543 //      return g_graphs.back();\r
544 //}\r
545 //\r
546 //safe_ptr<graph> create_graph(const printer& parent_printer)\r
547 //{\r
548 //      g_graphs.push_back(make_safe<graph>(parent_printer));\r
549 //      return g_graphs.back();\r
550 //}\r
551 //\r
552 //static std::vector<safe_ptr<graph>> get_all_graphs()\r
553 //{\r
554 //      std::vector<safe_ptr<graph>> graphs;\r
555 //      BOOST_FOREACH(auto& graph, g_graphs)\r
556 //              graphs.push_back(graph->clone());\r
557 //\r
558 //      return graphs;\r
559 //}\r
560 //\r
561 //}\r
562 \r
563 }}