]> git.sesse.net Git - casparcg/blob - modules/screen/consumer/screen_consumer.cpp
Merged asynchronous invocation of consumers from 2.0
[casparcg] / modules / screen / consumer / screen_consumer.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 "screen_consumer.h"
23
24 #include <GL/glew.h>
25 #include <SFML/Window.hpp>
26
27 #include <common/diagnostics/graph.h>
28 #include <common/gl/gl_check.h>
29 #include <common/log.h>
30 #include <common/memory.h>
31 #include <common/array.h>
32 #include <common/memshfl.h>
33 #include <common/utf.h>
34 #include <common/prec_timer.h>
35 #include <common/future.h>
36
37 #include <ffmpeg/producer/filter/filter.h>
38
39 #include <core/video_format.h>
40 #include <core/frame/frame.h>
41 #include <core/consumer/frame_consumer.h>
42
43 #include <boost/timer.hpp>
44 #include <boost/circular_buffer.hpp>
45 #include <boost/lexical_cast.hpp>
46 #include <boost/foreach.hpp>
47 #include <boost/property_tree/ptree.hpp>
48 #include <boost/thread.hpp>
49
50 #include <tbb/atomic.h>
51 #include <tbb/concurrent_queue.h>
52 #include <tbb/parallel_for.h>
53
54 #include <boost/assign.hpp>
55
56 #include <asmlib.h>
57
58 #include <algorithm>
59 #include <vector>
60
61 #if defined(_MSC_VER)
62 #pragma warning (push)
63 #pragma warning (disable : 4244)
64 #endif
65 extern "C" 
66 {
67         #define __STDC_CONSTANT_MACROS
68         #define __STDC_LIMIT_MACROS
69         #include <libavcodec/avcodec.h>
70         #include <libavutil/imgutils.h>
71 }
72 #if defined(_MSC_VER)
73 #pragma warning (pop)
74 #endif
75
76 namespace caspar { namespace screen {
77                 
78 enum stretch
79 {
80         none,
81         uniform,
82         fill,
83         uniform_to_fill
84 };
85
86 struct configuration
87 {
88         enum aspect_ratio
89         {
90                 aspect_4_3 = 0,
91                 aspect_16_9,
92                 aspect_invalid,
93         };
94                 
95         std::wstring    name;
96         int                             screen_index;
97         stretch                 stretch;
98         bool                    windowed;
99         bool                    auto_deinterlace;
100         bool                    key_only;
101         aspect_ratio    aspect; 
102         bool                    vsync;
103
104         configuration()
105                 : name(L"ogl")
106                 , screen_index(0)
107                 , stretch(fill)
108                 , windowed(true)
109                 , auto_deinterlace(true)
110                 , key_only(false)
111                 , aspect(aspect_invalid)
112                 , vsync(true)
113         {
114         }
115 };
116
117 struct screen_consumer : boost::noncopyable
118 {               
119         const configuration                                     config_;
120         core::video_format_desc                         format_desc_;
121         int                                                                     channel_index_;
122
123         GLuint                                                          texture_;
124         std::vector<GLuint>                                     pbos_;
125                         
126         float                                                           width_;
127         float                                                           height_;        
128         int                                                                     screen_x_;
129         int                                                                     screen_y_;
130         int                                                                     screen_width_;
131         int                                                                     screen_height_;
132         int                                                                     square_width_;
133         int                                                                     square_height_;                         
134         
135         sf::Window                                                      window_;
136         
137         spl::shared_ptr<diagnostics::graph>     graph_;
138         boost::timer                                            perf_timer_;
139         boost::timer                                            tick_timer_;
140
141         caspar::prec_timer                                      wait_timer_;
142
143         tbb::concurrent_bounded_queue<core::const_frame>        frame_buffer_;
144
145         boost::thread                                           thread_;
146         tbb::atomic<bool>                                       is_running_;
147         
148         ffmpeg::filter                                          filter_;
149 public:
150         screen_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) 
151                 : config_(config)
152                 , format_desc_(format_desc)
153                 , channel_index_(channel_index)
154                 , texture_(0)
155                 , pbos_(2, 0)   
156                 , screen_width_(format_desc.width)
157                 , screen_height_(format_desc.height)
158                 , square_width_(format_desc.square_width)
159                 , square_height_(format_desc.square_height)
160                 , filter_(format_desc.field_mode == core::field_mode::progressive || !config.auto_deinterlace ? L"" : L"YADIF=1:-1", boost::assign::list_of(PIX_FMT_BGRA))
161         {               
162                 if(format_desc_.format == core::video_format::ntsc && config_.aspect == configuration::aspect_4_3)
163                 {
164                         // Use default values which are 4:3.
165                 }
166                 else
167                 {
168                         if(config_.aspect == configuration::aspect_16_9)
169                                 square_width_ = (format_desc.height*16)/9;
170                         else if(config_.aspect == configuration::aspect_4_3)
171                                 square_width_ = (format_desc.height*4)/3;
172                 }
173
174                 frame_buffer_.set_capacity(2);
175                 
176                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   
177                 graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
178                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
179                 graph_->set_text(print());
180                 diagnostics::register_graph(graph_);
181                                                                         
182                 DISPLAY_DEVICE d_device = {sizeof(d_device), 0};                        
183                 std::vector<DISPLAY_DEVICE> displayDevices;
184                 for(int n = 0; EnumDisplayDevices(NULL, n, &d_device, NULL); ++n)
185                         displayDevices.push_back(d_device);
186
187                 if(config_.screen_index >= displayDevices.size())
188                         CASPAR_LOG(warning) << print() << L" Invalid screen-index: " << config_.screen_index;
189                 
190                 DEVMODE devmode = {};
191                 if(!EnumDisplaySettings(displayDevices[config_.screen_index].DeviceName, ENUM_CURRENT_SETTINGS, &devmode))
192                         CASPAR_LOG(warning) << print() << L" Could not find display settings for screen-index: " << config_.screen_index;
193                 
194                 screen_x_               = devmode.dmPosition.x;
195                 screen_y_               = devmode.dmPosition.y;
196                 screen_width_   = config_.windowed ? square_width_ : devmode.dmPelsWidth;
197                 screen_height_  = config_.windowed ? square_height_ : devmode.dmPelsHeight;
198                 
199                 is_running_ = true;
200                 thread_ = boost::thread([this]{run();});
201         }
202         
203         ~screen_consumer()
204         {
205                 is_running_ = false;
206                 frame_buffer_.try_push(core::const_frame::empty());
207                 thread_.join();
208         }
209
210         void init()
211         {
212                 window_.Create(sf::VideoMode(screen_width_, screen_height_, 32), u8(print()), config_.windowed ? sf::Style::Resize | sf::Style::Close : sf::Style::Fullscreen);
213                 window_.ShowMouseCursor(false);
214                 window_.SetPosition(screen_x_, screen_y_);
215                 window_.SetSize(screen_width_, screen_height_);
216                 window_.SetActive();
217                 
218                 if(!GLEW_VERSION_2_1 && glewInit() != GLEW_OK)
219                         CASPAR_THROW_EXCEPTION(gl::ogl_exception() << msg_info("Failed to initialize GLEW."));
220
221                 if(!GLEW_VERSION_2_1)
222                         CASPAR_THROW_EXCEPTION(not_supported() << msg_info("Missing OpenGL 2.1 support."));
223
224                 GL(glEnable(GL_TEXTURE_2D));
225                 GL(glDisable(GL_DEPTH_TEST));           
226                 GL(glClearColor(0.0, 0.0, 0.0, 0.0));
227                 GL(glViewport(0, 0, format_desc_.width, format_desc_.height));
228                 GL(glLoadIdentity());
229                                 
230                 calculate_aspect();
231                         
232                 GL(glGenTextures(1, &texture_));
233                 GL(glBindTexture(GL_TEXTURE_2D, texture_));
234                 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
235                 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
236                 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP));
237                 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP));
238                 GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, format_desc_.width, format_desc_.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0));
239                 GL(glBindTexture(GL_TEXTURE_2D, 0));
240                                         
241                 GL(glGenBuffers(2, pbos_.data()));
242                         
243                 glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pbos_[0]);
244                 glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, format_desc_.size, 0, GL_STREAM_DRAW_ARB);
245                 glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pbos_[1]);
246                 glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, format_desc_.size, 0, GL_STREAM_DRAW_ARB);
247                 glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
248                 
249                 auto wglSwapIntervalEXT = reinterpret_cast<void(APIENTRY*)(int)>(wglGetProcAddress("wglSwapIntervalEXT"));
250                 if(wglSwapIntervalEXT)
251                 {
252                         if(config_.vsync)
253                         {
254                                 wglSwapIntervalEXT(1);
255                                 CASPAR_LOG(info) << print() << " Enabled vsync.";
256                         }
257                         else
258                                 wglSwapIntervalEXT(0);
259                 }
260
261                 CASPAR_LOG(info) << print() << " Successfully Initialized.";
262         }
263
264         void uninit()
265         {               
266                 if(texture_)
267                         glDeleteTextures(1, &texture_);
268
269                 BOOST_FOREACH(auto& pbo, pbos_)
270                 {
271                         if(pbo)
272                                 glDeleteBuffers(1, &pbo);
273                 }
274         }
275
276         void run()
277         {
278                 try
279                 {
280                         init();
281
282                         while(is_running_)
283                         {                       
284                                 try
285                                 {
286                                         sf::Event e;            
287                                         while(window_.GetEvent(e))
288                                         {
289                                                 if(e.Type == sf::Event::Resized)
290                                                         calculate_aspect();
291                                                 else if(e.Type == sf::Event::Closed)
292                                                         is_running_ = false;
293                                         }
294                         
295                                         auto frame = core::const_frame::empty();
296                                         frame_buffer_.pop(frame);
297
298                                         render_and_draw_frame(frame);
299                                         
300                                         /*perf_timer_.restart();
301                                         render(frame);
302                                         graph_->set_value("frame-time", perf_timer_.elapsed()*format_desc_.fps*0.5);    
303
304                                         window_.Display();*/
305
306                                         graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);     
307                                         tick_timer_.restart();
308                                 }
309                                 catch(...)
310                                 {
311                                         CASPAR_LOG_CURRENT_EXCEPTION();
312                                         is_running_ = false;
313                                 }
314                         }
315
316                         uninit();
317                 }
318                 catch(...)
319                 {
320                         CASPAR_LOG_CURRENT_EXCEPTION();
321                 }
322         }
323
324         void try_sleep_almost_until_vblank()
325         {
326                 static const double THRESHOLD = 0.003;
327                 double threshold = config_.vsync ? THRESHOLD : 0.0;
328
329                 auto frame_time = 1.0 / (format_desc_.fps * format_desc_.field_count);
330
331                 wait_timer_.tick(frame_time - threshold);
332         }
333
334         void wait_for_vblank_and_display()
335         {
336                 try_sleep_almost_until_vblank();
337                 window_.Display();
338                 // Make sure that the next tick measures the duration from this point in time.
339                 wait_timer_.tick(0.0);
340         }
341
342         spl::shared_ptr<AVFrame> get_av_frame()
343         {               
344                 spl::shared_ptr<AVFrame> av_frame(avcodec_alloc_frame(), av_free);      
345                 avcodec_get_frame_defaults(av_frame.get());
346                                                 
347                 av_frame->linesize[0]           = format_desc_.width*4;                 
348                 av_frame->format                        = PIX_FMT_BGRA;
349                 av_frame->width                         = format_desc_.width;
350                 av_frame->height                        = format_desc_.height;
351                 av_frame->interlaced_frame      = format_desc_.field_mode != core::field_mode::progressive;
352                 av_frame->top_field_first       = format_desc_.field_mode == core::field_mode::upper ? 1 : 0;
353
354                 return av_frame;
355         }
356
357         void render_and_draw_frame(core::const_frame frame)
358         {
359                 if(static_cast<size_t>(frame.image_data().size()) != format_desc_.size)
360                         return;
361
362                 if(screen_width_ == 0 && screen_height_ == 0)
363                         return;
364                                         
365                 perf_timer_.restart();
366                 auto av_frame = get_av_frame();
367                 av_frame->data[0] = const_cast<uint8_t*>(frame.image_data().begin());
368
369                 filter_.push(av_frame);
370                 auto frames = filter_.poll_all();
371
372                 if (frames.empty())
373                         return;
374
375                 if (frames.size() == 1)
376                 {
377                         render(frames[0]);
378                         graph_->set_value("frame-time", perf_timer_.elapsed() * format_desc_.fps * 0.5);
379
380                         wait_for_vblank_and_display(); // progressive frame
381                 }
382                 else if (frames.size() == 2)
383                 {
384                         render(frames[0]);
385                         double perf_elapsed = perf_timer_.elapsed();
386
387                         wait_for_vblank_and_display(); // field1
388
389                         perf_timer_.restart();
390                         render(frames[1]);
391                         perf_elapsed += perf_timer_.elapsed();
392                         graph_->set_value("frame-time", perf_elapsed * format_desc_.fps * 0.5);
393
394                         wait_for_vblank_and_display(); // field2
395                 }
396         }
397
398         void render(spl::shared_ptr<AVFrame> av_frame)
399         {
400                 GL(glBindTexture(GL_TEXTURE_2D, texture_));
401
402                 GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbos_[0]));
403                 GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, format_desc_.width, format_desc_.height, GL_BGRA, GL_UNSIGNED_BYTE, 0));
404
405                 GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbos_[1]));
406                 GL(glBufferData(GL_PIXEL_UNPACK_BUFFER, format_desc_.size, 0, GL_STREAM_DRAW));
407
408                 auto ptr = reinterpret_cast<char*>(GL2(glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)));
409                 if(ptr)
410                 {
411                         if(config_.key_only)
412                         {
413                                 tbb::parallel_for(tbb::blocked_range<int>(0, format_desc_.height), [&](const tbb::blocked_range<int>& r)
414                                 {
415                                         for(int n = r.begin(); n != r.end(); ++n)
416                                                 aligned_memshfl(ptr+n*format_desc_.width*4, av_frame->data[0]+n*av_frame->linesize[0], format_desc_.width*4, 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
417                                 });
418                         }
419                         else
420                         {       
421                                 tbb::parallel_for(tbb::blocked_range<int>(0, format_desc_.height), [&](const tbb::blocked_range<int>& r)
422                                 {
423                                         for(int n = r.begin(); n != r.end(); ++n)
424                                                 A_memcpy(ptr+n*format_desc_.width*4, av_frame->data[0]+n*av_frame->linesize[0], format_desc_.width*4);
425                                 });
426                         }
427                         
428                         GL(glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)); // release the mapped buffer
429                 }
430
431                 GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));
432                                 
433                 GL(glClear(GL_COLOR_BUFFER_BIT));                       
434                 glBegin(GL_QUADS);
435                                 glTexCoord2f(0.0f,        1.0f);        glVertex2f(-width_, -height_);
436                                 glTexCoord2f(1.0f,        1.0f);        glVertex2f( width_, -height_);
437                                 glTexCoord2f(1.0f,        0.0f);        glVertex2f( width_,  height_);
438                                 glTexCoord2f(0.0f,        0.0f);        glVertex2f(-width_,  height_);
439                 glEnd();
440                 
441                 GL(glBindTexture(GL_TEXTURE_2D, 0));
442
443                 std::rotate(pbos_.begin(), pbos_.begin() + 1, pbos_.end());
444         }
445
446
447         boost::unique_future<bool> send(core::const_frame frame)
448         {
449                 if(!frame_buffer_.try_push(frame))
450                         graph_->set_tag("dropped-frame");
451
452                 return wrap_as_future(is_running_.load());
453         }
454                 
455         std::wstring print() const
456         {       
457                 return config_.name + L"[" + boost::lexical_cast<std::wstring>(channel_index_) + L"|" + format_desc_.name + L"]";
458         }
459         
460         void calculate_aspect()
461         {
462                 if(config_.windowed)
463                 {
464                         screen_height_ = window_.GetHeight();
465                         screen_width_ = window_.GetWidth();
466                 }
467                 
468                 GL(glViewport(0, 0, screen_width_, screen_height_));
469
470                 std::pair<float, float> target_ratio = None();
471                 if(config_.stretch == fill)
472                         target_ratio = Fill();
473                 else if(config_.stretch == uniform)
474                         target_ratio = Uniform();
475                 else if(config_.stretch == uniform_to_fill)
476                         target_ratio = UniformToFill();
477
478                 width_ = target_ratio.first;
479                 height_ = target_ratio.second;
480         }
481                 
482         std::pair<float, float> None()
483         {
484                 float width = static_cast<float>(square_width_)/static_cast<float>(screen_width_);
485                 float height = static_cast<float>(square_height_)/static_cast<float>(screen_height_);
486
487                 return std::make_pair(width, height);
488         }
489
490         std::pair<float, float> Uniform()
491         {
492                 float aspect = static_cast<float>(square_width_)/static_cast<float>(square_height_);
493                 float width = std::min(1.0f, static_cast<float>(screen_height_)*aspect/static_cast<float>(screen_width_));
494                 float height = static_cast<float>(screen_width_*width)/static_cast<float>(screen_height_*aspect);
495
496                 return std::make_pair(width, height);
497         }
498
499         std::pair<float, float> Fill()
500         {
501                 return std::make_pair(1.0f, 1.0f);
502         }
503
504         std::pair<float, float> UniformToFill()
505         {
506                 float wr = static_cast<float>(square_width_)/static_cast<float>(screen_width_);
507                 float hr = static_cast<float>(square_height_)/static_cast<float>(screen_height_);
508                 float r_inv = 1.0f/std::min(wr, hr);
509
510                 float width = wr*r_inv;
511                 float height = hr*r_inv;
512
513                 return std::make_pair(width, height);
514         }
515 };
516
517
518 struct screen_consumer_proxy : public core::frame_consumer
519 {
520         const configuration config_;
521         std::unique_ptr<screen_consumer> consumer_;
522
523 public:
524
525         screen_consumer_proxy(const configuration& config)
526                 : config_(config){}
527         
528         // frame_consumer
529
530         void initialize(const core::video_format_desc& format_desc, int channel_index) override
531         {
532                 consumer_.reset();
533                 consumer_.reset(new screen_consumer(config_, format_desc, channel_index));
534         }
535         
536         boost::unique_future<bool> send(core::const_frame frame) override
537         {
538                 return consumer_->send(frame);
539         }
540         
541         std::wstring print() const override
542         {
543                 return consumer_ ? consumer_->print() : L"[screen_consumer]";
544         }
545
546         std::wstring name() const override
547         {
548                 return L"screen";
549         }
550
551         boost::property_tree::wptree info() const override
552         {
553                 boost::property_tree::wptree info;
554                 info.add(L"type", L"screen");
555                 info.add(L"key-only", config_.key_only);
556                 info.add(L"windowed", config_.windowed);
557                 info.add(L"auto-deinterlace", config_.auto_deinterlace);
558                 return info;
559         }
560
561         bool has_synchronization_clock() const override
562         {
563                 return false;
564         }
565         
566         int buffer_depth() const override
567         {
568                 return 2;
569         }
570
571         int index() const override
572         {
573                 return 600 + (config_.key_only ? 10 : 0) + config_.screen_index;
574         }
575
576         void subscribe(const monitor::observable::observer_ptr& o) override
577         {
578         }
579
580         void unsubscribe(const monitor::observable::observer_ptr& o) override
581         {
582         }       
583 };      
584
585 spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
586 {
587         if(params.size() < 1 || params[0] != L"SCREEN")
588                 return core::frame_consumer::empty();
589         
590         configuration config;
591                 
592         if(params.size() > 1)
593                 config.screen_index = boost::lexical_cast<int>(params[1]);
594                 
595         config.key_only = std::find(params.begin(), params.end(), L"WINDOWED") != params.end();
596         config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
597
598         auto name_it    = std::find(params.begin(), params.end(), L"NAME");
599         if(name_it != params.end() && ++name_it != params.end())
600                 config.name = *name_it;
601
602         return spl::make_shared<screen_consumer_proxy>(config);
603 }
604
605 spl::shared_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) 
606 {
607         configuration config;
608         config.name                             = ptree.get(L"name",     config.name);
609         config.screen_index             = ptree.get(L"device",   config.screen_index+1)-1;
610         config.windowed                 = ptree.get(L"windowed", config.windowed);
611         config.key_only                 = ptree.get(L"key-only", config.key_only);
612         config.auto_deinterlace = ptree.get(L"auto-deinterlace", config.auto_deinterlace);
613         config.vsync                    = ptree.get(L"vsync", config.vsync);
614         
615         auto stretch_str = ptree.get(L"stretch", L"default");
616         if(stretch_str == L"uniform")
617                 config.stretch = stretch::uniform;
618         else if(stretch_str == L"uniform_to_fill")
619                 config.stretch = stretch::uniform_to_fill;
620
621         auto aspect_str = ptree.get(L"aspect-ratio", L"default");
622         if(aspect_str == L"16:9")
623                 config.aspect = configuration::aspect_16_9;
624         else if(aspect_str == L"4:3")
625                 config.aspect = configuration::aspect_4_3;
626         
627         return spl::make_shared<screen_consumer_proxy>(config);
628 }
629
630 }}