]> git.sesse.net Git - casparcg/blob - core/diagnostics/osd_graph.cpp
7847f76bfeb529ac1452b0df61a159ee19ac3c9f
[casparcg] / core / diagnostics / osd_graph.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "../StdAfx.h"
23
24 #include "osd_graph.h"
25
26 #pragma warning (disable : 4244)
27
28 #include "call_context.h"
29
30 #include <common/executor.h>
31 #include <common/lock.h>
32 #include <common/env.h>
33 #include <common/prec_timer.h>
34 #include <common/os/scheduling.h>
35
36 #include <SFML/Graphics.hpp>
37
38 #include <boost/optional.hpp>
39 #include <boost/circular_buffer.hpp>
40 #include <boost/lexical_cast.hpp>
41
42 #include <tbb/concurrent_unordered_map.h>
43 #include <tbb/atomic.h>
44 #include <tbb/spin_mutex.h>
45
46 #include <GL/glew.h>
47
48 #include <numeric>
49 #include <tuple>
50 #include <memory>
51
52 namespace caspar { namespace core { namespace diagnostics { namespace osd {
53
54 static const int PREFERRED_VERTICAL_GRAPHS = 8;
55 static const int RENDERING_WIDTH = 750;
56 static const int RENDERING_HEIGHT = RENDERING_WIDTH / PREFERRED_VERTICAL_GRAPHS;
57
58 sf::Color get_sfml_color(int color)
59 {
60         auto c = caspar::diagnostics::color(color);
61
62         return {
63                 (color >> 24) & 255,
64                 (color >> 16) & 255,
65                 (color >> 8) & 255,
66                 (color >> 0) & 255
67         };
68 }
69
70 sf::Font& get_default_font()
71 {
72         static sf::Font DEFAULT_FONT = []()
73         {
74                 sf::Font font;
75                 if (!font.loadFromFile("LiberationSans-Regular.ttf"))
76                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("LiberationSans-Regular.ttf not found"));
77                 return font;
78         }();
79
80         return DEFAULT_FONT;
81 }
82
83 struct drawable : public sf::Drawable, public sf::Transformable
84 {
85         virtual ~drawable(){}
86         virtual void render(sf::RenderTarget& target, sf::RenderStates states) = 0;
87         virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override
88         {
89                 states.transform *= getTransform();
90                 const_cast<drawable*>(this)->render(target, states);
91         }
92 };
93
94 class context : public drawable
95 {       
96         std::unique_ptr<sf::RenderWindow>       window_;
97         sf::View                                                        view_;
98         
99         std::list<std::weak_ptr<drawable>>      drawables_;
100         int64_t                                                         refresh_rate_millis_            = 16;
101         boost::timer                                            display_time_;
102         bool                                                            calculate_view_                         = true;
103         int                                                                     scroll_position_                        = 0;
104         bool                                                            dragging_                                       = false;
105         int                                                                     last_mouse_y_                           = 0;
106
107         executor                                                        executor_                                       { L"diagnostics" };
108 public:                                 
109
110         static void register_drawable(const std::shared_ptr<drawable>& drawable)
111         {
112                 if(!drawable)
113                         return;
114
115                 get_instance()->executor_.begin_invoke([=]
116                 {
117                         get_instance()->do_register_drawable(drawable);
118                 }, task_priority::high_priority);
119         }
120
121         static void show(bool value)
122         {
123                 get_instance()->executor_.begin_invoke([=]
124                 {       
125                         get_instance()->do_show(value);
126                 }, task_priority::high_priority);
127         }
128         
129         static void shutdown()
130         {
131                 get_instance().reset();
132         }
133 private:
134         context()
135         {
136                 executor_.begin_invoke([=]
137                 {
138                         set_priority_of_current_thread(thread_priority::LOW);
139                 });
140         }
141
142         void do_show(bool value)
143         {
144                 if(value)
145                 {
146                         if(!window_)
147                         {
148                                 window_.reset(new sf::RenderWindow(sf::VideoMode(RENDERING_WIDTH, RENDERING_WIDTH), "CasparCG Diagnostics"));
149                                 window_->setPosition(sf::Vector2i(0, 0));
150                                 window_->setActive();
151                                 window_->setVerticalSyncEnabled(true);
152                                 calculate_view_ = true;
153                                 glEnable(GL_BLEND);
154                                 glEnable(GL_LINE_SMOOTH);
155                                 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
156                                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
157
158                                 tick();
159                         }
160                 }
161                 else
162                         window_.reset();
163         }
164
165         void tick()
166         {
167                 if(!window_)
168                         return;
169
170                 sf::Event e;
171                 while (window_->pollEvent(e))
172                 {
173                         switch (e.type)
174                         {
175                         case sf::Event::Closed:
176                                 window_.reset();
177                                 return;
178                         case sf::Event::Resized:
179                                 calculate_view_ = true;
180                                 break;
181                         case sf::Event::MouseButtonPressed:
182                                 dragging_ = true;
183                                 last_mouse_y_ = e.mouseButton.y;
184                                 break;
185                         case sf::Event::MouseButtonReleased:
186                                 dragging_ = false;
187                                 break;
188                         case sf::Event::MouseMoved:
189                                 if (dragging_)
190                                 {
191                                         auto delta_y = e.mouseMove.y - last_mouse_y_;
192                                         scroll_position_ += delta_y;
193                                         last_mouse_y_ = e.mouseMove.y;
194                                         calculate_view_ = true;
195                                 }
196
197                                 break;
198                         case sf::Event::MouseWheelMoved:
199                                 scroll_position_ += e.mouseWheel.delta * 15;
200                                 calculate_view_ = true;
201                                 break;
202                         }
203                 }
204
205                 window_->clear();
206
207                 if (calculate_view_)
208                 {
209                         view_.setViewport(sf::FloatRect(0, 0, 1.0, 1.0));
210                         view_.setSize(RENDERING_WIDTH, window_->getSize().y);
211                         view_.setCenter(RENDERING_WIDTH / 2, window_->getSize().y / 2 - scroll_position_);
212                         window_->setView(view_);
213                         calculate_view_ = false;
214                 }
215
216                 window_->draw(*this);
217
218                 static const auto THRESHOLD = 1;
219                 int64_t since_last_refresh = display_time_.elapsed() * 1000;
220                 int64_t until_next_refresh = refresh_rate_millis_ - since_last_refresh;
221                 int64_t sleep_for = until_next_refresh - THRESHOLD;
222
223                 if (sleep_for > 0)
224                 {
225                         prec_timer timer;
226                         timer.tick_millis(0);
227                         timer.tick_millis(sleep_for);
228                 }
229
230                 window_->display();
231                 display_time_.restart();
232                 executor_.begin_invoke([this]{tick();});
233         }
234
235         void render(sf::RenderTarget& target, sf::RenderStates states)
236         {
237                 int n = 0;
238
239                 for (auto it = drawables_.begin(); it != drawables_.end(); ++n)
240                 {
241                         auto drawable = it->lock();
242                         if (drawable)
243                         {
244                                 float target_y = n * RENDERING_HEIGHT;
245                                 drawable->setPosition(0.0f, target_y);                  
246                                 target.draw(*drawable, states);
247                                 ++it;           
248                         }
249                         else    
250                                 it = drawables_.erase(it);
251                 }
252         }       
253         
254         void do_register_drawable(const std::shared_ptr<drawable>& drawable)
255         {
256                 drawables_.push_back(drawable);
257                 auto it = drawables_.begin();
258                 while(it != drawables_.end())
259                 {
260                         if(it->lock())
261                                 ++it;
262                         else    
263                                 it = drawables_.erase(it);                      
264                 }
265         }
266         
267         static std::unique_ptr<context>& get_instance()
268         {
269                 static auto impl = std::unique_ptr<context>(new context);
270                 return impl;
271         }
272 };
273
274 class line : public drawable
275 {
276         size_t                                                                                                          res_;
277         boost::circular_buffer<sf::Vertex>                                                      line_data_      { res_ };
278         boost::circular_buffer<boost::optional<sf::VertexArray>>        line_tags_      { res_ };
279
280         tbb::atomic<float>                                                                                      tick_data_;
281         tbb::atomic<bool>                                                                                       tick_tag_;
282         tbb::atomic<int>                                                                                        color_;
283
284         double                                                                                                          x_delta_        = 1.0 / (res_ - 1);
285         //double                                                                                                                x_pos_          = 1.0;
286 public:
287         line(size_t res = 750)
288                 : res_(res)
289         {
290                 tick_data_      = -1.0f;
291                 color_          = 0xFFFFFFFF;
292                 tick_tag_       = false;
293         }
294         
295         void set_value(float value)
296         {
297                 tick_data_ = value;
298         }
299         
300         void set_tag()
301         {
302                 tick_tag_ = true;
303         }
304                 
305         void set_color(int color)
306         {
307                 color_ = color;
308         }
309
310         int get_color()
311         {
312                 return color_;
313         }
314                 
315         void render(sf::RenderTarget& target, sf::RenderStates states)
316         {
317                 /*states.transform.translate(x_pos_, 0.f);
318
319                 if (line_data_.size() == res_)
320                         x_pos_ = -get_insertion_xcoord() + 1.0 - x_delta_; // Otherwise the graph will drift because of floating point precision
321                 else
322                         x_pos_ -= x_delta_;*/
323
324                 for (auto& vertex : line_data_)
325                         vertex.position.x -= x_delta_;
326
327                 auto color = get_sfml_color(color_);
328                 color.a = 255 * 0.8;
329                 line_data_.push_back(sf::Vertex(sf::Vector2f(get_insertion_xcoord(), std::max(0.05, std::min(0.95, (1.0f - tick_data_) * 0.8 + 0.1f))), color));
330
331                 if (tick_tag_)
332                 {
333                         sf::VertexArray vertical_dash(sf::LinesStrip);
334                         vertical_dash.append(sf::Vertex(sf::Vector2f(get_insertion_xcoord() - x_delta_, 0.f), color));
335                         vertical_dash.append(sf::Vertex(sf::Vector2f(get_insertion_xcoord() - x_delta_, 1.f), color));
336                         line_tags_.push_back(vertical_dash);
337                 }
338                 else
339                         line_tags_.push_back(boost::none);
340
341                 tick_tag_ = false;
342
343                 if (tick_data_ > -0.5)
344                 {
345                         auto array_one = line_data_.array_one();
346                         auto array_two = line_data_.array_two();
347                         // since boost::circular_buffer guarantees two contigous views of the buffer we can provide raw access to SFML, which can use glDrawArrays.
348                         target.draw(array_one.first, static_cast<unsigned int>(array_one.second), sf::LinesStrip, states);
349                         target.draw(array_two.first, static_cast<unsigned int>(array_two.second), sf::LinesStrip, states);
350
351                         if (array_one.second > 0 && array_two.second > 0)
352                         {
353                                 // Connect the gap between the arrays
354                                 sf::VertexArray connecting_line(sf::LinesStrip);
355                                 connecting_line.append(*(array_one.first + array_one.second - 1));
356                                 connecting_line.append(*(array_two.first));
357                                 target.draw(connecting_line, states);
358                         }
359                 }
360                 else
361                 {
362                         glEnable(GL_LINE_STIPPLE);
363                         glLineStipple(3, 0xAAAA);
364
365                         for (size_t n = 0; n < line_tags_.size(); ++n)
366                         {
367                                 if (line_tags_[n])
368                                 {
369                                         target.draw(*line_tags_[n], states);
370                                 }
371                         }
372
373                         glDisable(GL_LINE_STIPPLE);
374                 }
375         }
376 private:
377         double get_insertion_xcoord() const
378         {
379                 return line_data_.empty() ? 1.0 : line_data_.back().position.x + x_delta_;
380         }
381 };
382
383 struct graph : public drawable, public caspar::diagnostics::spi::graph_sink, public std::enable_shared_from_this<graph>
384 {
385         call_context                                                                            context_        = call_context::for_thread();
386         tbb::concurrent_unordered_map<std::string, line>        lines_;
387
388         tbb::spin_mutex                                                                         mutex_;
389         std::wstring                                                                            text_;
390         bool                                                                                            auto_reset_     = false;
391
392         graph()
393         {
394         }
395
396         void activate() override
397         {
398                 context::register_drawable(shared_from_this());
399         }
400                 
401         void set_text(const std::wstring& value) override
402         {
403                 auto temp = value;
404                 lock(mutex_, [&]
405                 {
406                         text_ = std::move(temp);
407                 });
408         }
409
410         void set_value(const std::string& name, double value) override
411         {
412                 lines_[name].set_value(value);
413         }
414
415         void set_tag(const std::string& name) override
416         {
417                 lines_[name].set_tag();
418         }
419
420         void set_color(const std::string& name, int color) override
421         {
422                 lines_[name].set_color(color);
423         }
424
425         void auto_reset() override
426         {
427                 lock(mutex_, [this]
428                 {
429                         auto_reset_ = true;
430                 });
431         }
432                 
433 private:
434         void render(sf::RenderTarget& target, sf::RenderStates states) override
435         {
436                 const size_t text_size = 15;
437                 const size_t text_margin = 2;
438                 const size_t text_offset = (text_size+text_margin*2)*2;
439
440                 std::wstring text_str;
441                 bool auto_reset;
442
443                 {
444                         tbb::spin_mutex::scoped_lock lock(mutex_);
445                         text_str = text_;
446                         auto_reset = auto_reset_;
447                 }
448
449                 sf::Text text(text_str.c_str(), get_default_font(), text_size);
450                 text.setStyle(sf::Text::Italic);
451                 text.move(text_margin, text_margin);
452                 
453                 target.draw(text, states);
454
455                 if (context_.video_channel != -1)
456                 {
457                         auto ctx_str = boost::lexical_cast<std::string>(context_.video_channel);
458
459                         if (context_.layer != -1)
460                                 ctx_str += "-" + boost::lexical_cast<std::string>(context_.layer);
461
462                         sf::Text context_text(ctx_str, get_default_font(), text_size);
463                         context_text.setStyle(sf::Text::Italic);
464                         context_text.move(RENDERING_WIDTH - text_margin - 5 - context_text.getLocalBounds().width, text_margin);
465
466                         target.draw(context_text, states);
467                 }
468
469                 float x_offset = text_margin;
470
471                 for(auto it = lines_.begin(); it != lines_.end(); ++it)
472                 {
473                         sf::Text line_text(it->first, get_default_font(), text_size);
474                         line_text.setPosition(x_offset, text_margin+text_offset/2);
475                         line_text.setColor(get_sfml_color(it->second.get_color()));
476                         target.draw(line_text, states);
477                         x_offset += line_text.getLocalBounds().width + text_margin * 2;
478                 }
479
480                 static const auto rect = []()
481                 {
482                         sf::RectangleShape r(sf::Vector2f(RENDERING_WIDTH, RENDERING_HEIGHT - 2));
483                         r.setFillColor(sf::Color(255, 255, 255, 51));
484                         r.setOutlineThickness(0.00f);
485                         r.move(0, 1);
486                         return r;
487                 }();
488                 target.draw(rect, states);
489
490                 states.transform
491                         .translate(0, text_offset)
492                         .scale(RENDERING_WIDTH, RENDERING_HEIGHT * (static_cast<float>(RENDERING_HEIGHT - text_offset) / static_cast<float>(RENDERING_HEIGHT)));
493                 
494                 static const sf::Color guide_color(255, 255, 255, 127);
495                 static const sf::VertexArray middle_guide = []()
496                 {
497                         sf::VertexArray result(sf::LinesStrip);
498                         result.append(sf::Vertex(sf::Vector2f(0.0f, 0.5f), guide_color));
499                         result.append(sf::Vertex(sf::Vector2f(1.0f, 0.5f), guide_color));
500                         return result;
501                 }();
502                 static const sf::VertexArray bottom_guide = []()
503                 {
504                         sf::VertexArray result(sf::LinesStrip);
505                         result.append(sf::Vertex(sf::Vector2f(0.0f, 0.9f), guide_color));
506                         result.append(sf::Vertex(sf::Vector2f(1.0f, 0.9f), guide_color));
507                         return result;
508                 }();
509                 static const sf::VertexArray top_guide = []()
510                 {
511                         sf::VertexArray result(sf::LinesStrip);
512                         result.append(sf::Vertex(sf::Vector2f(0.0f, 0.1f), guide_color));
513                         result.append(sf::Vertex(sf::Vector2f(1.0f, 0.1f), guide_color));
514                         return result;
515                 }();
516
517                 glEnable(GL_LINE_STIPPLE);
518                 glLineStipple(3, 0xAAAA);
519                 
520                 target.draw(middle_guide, states);
521                 target.draw(bottom_guide, states);
522                 target.draw(top_guide, states);
523                 
524                 glDisable(GL_LINE_STIPPLE);
525
526                 for (auto it = lines_.begin(); it != lines_.end(); ++it)                
527                 {
528                         target.draw(it->second, states);
529                         if(auto_reset)
530                                 it->second.set_value(0.0f);
531                 }
532         }
533 };
534
535 void register_sink()
536 {
537         caspar::diagnostics::spi::register_sink_factory([]
538         {
539                 return spl::make_shared<graph>();
540         });
541 }
542
543 void show_graphs(bool value)
544 {
545         context::show(value);
546 }
547
548 void shutdown()
549 {
550         context::shutdown();
551 }
552
553 }}}}