2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
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.
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.
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/>.
19 * Author: Robert Nagy, ronag89@gmail.com
22 #include "../StdAfx.h"
24 #include "osd_graph.h"
26 #pragma warning (disable : 4244)
28 #include "call_context.h"
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>
37 #include <SFML/Graphics.hpp>
39 #include <boost/optional.hpp>
40 #include <boost/circular_buffer.hpp>
41 #include <boost/lexical_cast.hpp>
43 #include <tbb/concurrent_unordered_map.h>
44 #include <tbb/atomic.h>
45 #include <tbb/spin_mutex.h>
53 namespace caspar { namespace core { namespace diagnostics { namespace osd {
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;
59 sf::Color get_sfml_color(int color)
61 auto c = caspar::diagnostics::color(color);
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)
71 sf::Font& get_default_font()
73 static sf::Font DEFAULT_FONT = []()
76 if (!font.loadFromFile("LiberationSans-Regular.ttf"))
77 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info("LiberationSans-Regular.ttf not found"));
84 struct drawable : public sf::Drawable, public sf::Transformable
87 virtual void render(sf::RenderTarget& target, sf::RenderStates states) = 0;
88 virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override
90 states.transform *= getTransform();
91 const_cast<drawable*>(this)->render(target, states);
95 class context : public drawable
97 std::unique_ptr<sf::RenderWindow> window_;
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;
108 executor executor_ { L"diagnostics" };
111 static void register_drawable(const std::shared_ptr<drawable>& drawable)
116 get_instance()->executor_.begin_invoke([=]
118 get_instance()->do_register_drawable(drawable);
119 }, task_priority::high_priority);
122 static void show(bool value)
124 get_instance()->executor_.begin_invoke([=]
126 get_instance()->do_show(value);
127 }, task_priority::high_priority);
130 static void shutdown()
132 get_instance().reset();
137 executor_.begin_invoke([=]
139 set_priority_of_current_thread(thread_priority::LOW);
143 void do_show(bool value)
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;
155 glEnable(GL_LINE_SMOOTH);
156 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
157 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
172 while (window_->pollEvent(e))
176 case sf::Event::Closed:
179 case sf::Event::Resized:
180 calculate_view_ = true;
182 case sf::Event::MouseButtonPressed:
184 last_mouse_y_ = e.mouseButton.y;
186 case sf::Event::MouseButtonReleased:
189 case sf::Event::MouseMoved:
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;
199 case sf::Event::MouseWheelMoved:
200 scroll_position_ += e.mouseWheel.delta * 15;
201 calculate_view_ = true;
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;
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_);
222 calculate_view_ = false;
225 CASPAR_LOG_CALL(trace) << "osd_graph::tick()";
226 window_->draw(*this);
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;
236 timer.tick_millis(0);
237 timer.tick_millis(sleep_for);
241 display_time_.restart();
242 executor_.begin_invoke([this]{tick();});
245 void render(sf::RenderTarget& target, sf::RenderStates states)
249 for (auto it = drawables_.begin(); it != drawables_.end(); ++n)
251 auto drawable = it->lock();
254 float target_y = n * RENDERING_HEIGHT;
255 drawable->setPosition(0.0f, target_y);
256 target.draw(*drawable, states);
260 it = drawables_.erase(it);
264 void do_register_drawable(const std::shared_ptr<drawable>& drawable)
266 drawables_.push_back(drawable);
267 auto it = drawables_.begin();
268 while(it != drawables_.end())
273 it = drawables_.erase(it);
277 static std::unique_ptr<context>& get_instance()
279 static auto impl = std::unique_ptr<context>(new context);
284 class line : public drawable
287 boost::circular_buffer<sf::Vertex> line_data_ { res_ };
288 boost::circular_buffer<boost::optional<sf::VertexArray>> line_tags_ { res_ };
290 tbb::atomic<float> tick_data_;
291 tbb::atomic<bool> tick_tag_;
292 tbb::atomic<int> color_;
294 double x_delta_ = 1.0 / (res_ - 1);
295 //double x_pos_ = 1.0;
297 line(size_t res = 750)
305 void set_value(float value)
315 void set_color(int color)
325 void render(sf::RenderTarget& target, sf::RenderStates states)
327 /*states.transform.translate(x_pos_, 0.f);
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
332 x_pos_ -= x_delta_;*/
334 for (auto& vertex : line_data_)
335 vertex.position.x -= x_delta_;
337 for (auto& tag : line_tags_)
341 (*tag)[0].position.x -= x_delta_;
342 (*tag)[1].position.x -= x_delta_;
346 auto color = get_sfml_color(color_);
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));
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);
358 line_tags_.push_back(boost::none);
362 if (tick_data_ > -0.5)
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);
370 if (array_one.second > 0 && array_two.second > 0)
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);
381 glEnable(GL_LINE_STIPPLE);
382 glLineStipple(3, 0xAAAA);
384 for (size_t n = 0; n < line_tags_.size(); ++n)
388 target.draw(*line_tags_[n], states);
392 glDisable(GL_LINE_STIPPLE);
396 double get_insertion_xcoord() const
398 return line_data_.empty() ? 1.0 : line_data_.back().position.x + x_delta_;
402 struct graph : public drawable, public caspar::diagnostics::spi::graph_sink, public std::enable_shared_from_this<graph>
404 call_context context_ = call_context::for_thread();
405 tbb::concurrent_unordered_map<std::string, line> lines_;
407 tbb::spin_mutex mutex_;
409 bool auto_reset_ = false;
415 void activate() override
417 context::register_drawable(shared_from_this());
420 void set_text(const std::wstring& value) override
425 text_ = std::move(temp);
429 void set_value(const std::string& name, double value) override
431 lines_[name].set_value(value);
434 void set_tag(caspar::diagnostics::tag_severity /*severity*/, const std::string& name) override
436 lines_[name].set_tag();
439 void set_color(const std::string& name, int color) override
441 lines_[name].set_color(color);
444 void auto_reset() override
453 void render(sf::RenderTarget& target, sf::RenderStates states) override
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;
459 std::wstring text_str;
463 tbb::spin_mutex::scoped_lock lock(mutex_);
465 auto_reset = auto_reset_;
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);
472 target.draw(text, states);
474 if (context_.video_channel != -1)
476 auto ctx_str = boost::lexical_cast<std::string>(context_.video_channel);
478 if (context_.layer != -1)
479 ctx_str += "-" + boost::lexical_cast<std::string>(context_.layer);
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);
485 target.draw(context_text, states);
488 float x_offset = text_margin;
490 for(auto it = lines_.begin(); it != lines_.end(); ++it)
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;
499 static const auto rect = []()
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);
507 target.draw(rect, states);
510 .translate(0, text_offset)
511 .scale(RENDERING_WIDTH, RENDERING_HEIGHT * (static_cast<float>(RENDERING_HEIGHT - text_offset) / static_cast<float>(RENDERING_HEIGHT)));
513 static const sf::Color guide_color(255, 255, 255, 127);
514 static const sf::VertexArray middle_guide = []()
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));
521 static const sf::VertexArray bottom_guide = []()
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));
528 static const sf::VertexArray top_guide = []()
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));
536 glEnable(GL_LINE_STIPPLE);
537 glLineStipple(3, 0xAAAA);
539 target.draw(middle_guide, states);
540 target.draw(bottom_guide, states);
541 target.draw(top_guide, states);
543 glDisable(GL_LINE_STIPPLE);
545 for (auto it = lines_.begin(); it != lines_.end(); ++it)
547 target.draw(it->second, states);
549 it->second.set_value(0.0f);
556 caspar::diagnostics::spi::register_sink_factory([]
558 return spl::make_shared<graph>();
562 void show_graphs(bool value)
564 context::show(value);