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