Ticket #2649: videoout_opengl.2.cpp

File videoout_opengl.2.cpp, 64.5 KB (added by anonymous, 20 years ago)
Line 
1#include <cmath>
2
3using namespace std;
4
5// MythTV videout headers
6#include "videoout_opengl.h"
7#include "osd.h"
8#include "osdsurface.h"
9#include "util-opengl.h"
10#include "util-x11.h"
11#include "videodisplayprofile.h"
12
13// MythTV General headers
14#include "../libavcodec/avcodec.h"
15#include "mythconfig.h"
16#include "mythcontext.h"
17#include "filtermanager.h"
18#include "NuppelVideoPlayer.h"
19#define IGNORE_TV_PLAY_REC
20#include "tv.h"
21
22extern "C" {
23#define XMD_H 1
24#include <X11/extensions/xf86vmode.h>
25#include <X11/extensions/Xinerama.h>
26}
27
28#define LOC QString("VideoOutputOpengl: ")
29#define LOC_ERR QString("VideoOutputOpengl Error: ")
30
31VideoOutputOpengl::VideoOutputOpengl()
32 : VideoOutput(),
33 display_res(NULL), global_lock(true),
34 gl(new OpenglContext), videochain(NULL), pipchain(NULL), osdchain(NULL),
35 render_onscreen(true),
36 gl_osd(false), actually_draw_pip(false), argb_frame(NULL)
37{
38 bzero(&av_pause_frame, sizeof(av_pause_frame));
39 use_colourcontrols = gContext->GetNumSetting(
40 "UseOutputPictureControls", 0);
41 allowpreviewepg = false;
42}
43
44VideoOutputOpengl::~VideoOutputOpengl()
45{
46 X11L;
47 if (videochain)
48 delete videochain;
49 if (pipchain)
50 delete pipchain;
51 if (osdchain)
52 delete osdchain;
53 if (gl)
54 delete gl;
55 X11U;
56 if (argb_frame)
57 delete argb_frame;
58 DeleteBuffers(true);
59 if (display_res)
60 display_res->SwitchToGUI();
61}
62
63bool VideoOutputOpengl::Init(int width, int height, float aspect,
64 WId winid, int winx, int winy, int winw,
65 int winh, WId embedid)
66{
67 if (piptype > kPIPOn)
68 {
69 VERBOSE(VB_PLAYBACK, LOC + "Use NullVideo for Opengl PiP.");
70 return false;
71 }
72
73 if (gContext->GetNumSetting("UseVideoModes", 0) &&
74 render_onscreen && !piptype)
75 display_res = DisplayRes::GetDisplayRes();
76
77 if (winid <= 0)
78 {
79 VERBOSE(VB_PLAYBACK, LOC_ERR + "Invalid Window ID.");
80 return false;
81 }
82 VideoOutput::Init(width, height, aspect,
83 winid, winx, winy, winw, winh,
84 embedid);
85 if (!gl->Create(winid, display_visible_rect.width(),
86 display_visible_rect.height(),
87 render_onscreen))
88 return false;
89 X11S(gl->Init());
90 InitDisplayMeasurements(width, height);
91 MoveResize();
92
93 X11L;
94 gl->MakeCurrent(true);
95 videochain = new OpenglVideo();
96 bool success = videochain->Init(gl, use_colourcontrols,
97 render_onscreen, video_dim,
98 display_visible_rect,
99 display_video_rect, true);
100 gl->MakeCurrent(false);
101 X11U;
102 if (!success)
103 return false;
104 if (!InitSetupBuffers())
105 return false;
106
107 db_vdisp_profile->SetVideoRenderer("opengl");
108 if (use_colourcontrols)
109 InitPictureAttributes();
110
111 if (db_vdisp_profile->GetOSDRenderer() == "opengl")
112 gl_osd = true;
113
114 if (gl_osd && render_onscreen && !piptype)
115 {
116 X11L;
117 gl->MakeCurrent(true);
118 osdchain = new OpenglVideo();
119 if (!osdchain->Init(gl, use_colourcontrols,
120 render_onscreen, QSize(display_visible_rect.width(),
121 display_visible_rect.height()),
122 display_visible_rect,
123 display_visible_rect, false, true))
124 {
125 delete osdchain;
126 osdchain = NULL;
127 gl_osd = false;
128 }
129 gl->MakeCurrent(false);
130 X11U;
131 }
132 return true;
133}
134
135bool VideoOutputOpengl::SetDeinterlacingEnabled(bool enable)
136{
137 if (!videochain)
138 return false;
139
140 if (enable && m_deinterlacing)
141 return m_deinterlacing;
142
143 if (enable)
144 {
145 if (m_deintfiltername == "")
146 return SetupDeinterlace(enable);
147 if (m_deintfiltername.contains("opengl"))
148 {
149 if (videochain->GetDeinterlacer() == "")
150 return SetupDeinterlace(enable);
151 }
152 else if (!m_deintfiltername.contains("opengl"))
153 {
154 // make sure opengl deinterlacing is disabled
155 X11S(videochain->SetDeinterlacing(false));
156 if (!m_deintFiltMan || !m_deintFilter)
157 return VideoOutput::SetupDeinterlace(enable);
158 }
159 }
160
161 if (videochain)
162 X11S(videochain->SetDeinterlacing(enable));
163 m_deinterlacing = enable;
164 return m_deinterlacing;
165}
166
167bool VideoOutputOpengl::SetupDeinterlace(bool interlaced,
168 const QString& overridefilter)
169{
170 m_deintfiltername = db_vdisp_profile->GetFilteredDeint(overridefilter);
171
172 if (!m_deintfiltername.contains("opengl"))
173 {
174 X11S(videochain->SetDeinterlacing(false));
175 videochain->SetSoftwareDeinterlacer("");
176 VideoOutput::SetupDeinterlace(interlaced, overridefilter);
177 if (m_deinterlacing)
178 videochain->SetSoftwareDeinterlacer(m_deintfiltername);
179 return m_deinterlacing;
180 }
181
182 // clear any non opengl filters
183 if (m_deintFiltMan)
184 {
185 delete m_deintFiltMan;
186 m_deintFiltMan = NULL;
187 }
188 if (m_deintFilter)
189 {
190 delete m_deintFilter;
191 m_deintFilter = NULL;
192 }
193
194 if (m_deinterlacing == interlaced)
195 return m_deinterlacing;
196 m_deinterlacing = interlaced;
197
198 if (!videochain)
199 return false;
200
201 if (m_deinterlacing && !m_deintfiltername.isEmpty())
202 {
203 if (videochain->GetDeinterlacer() != m_deintfiltername)
204 {
205 X11L;
206 if (!videochain->AddDeinterlacer(m_deintfiltername))
207 {
208 VERBOSE(VB_IMPORTANT, LOC +
209 QString("Couldn't load deinterlace filter %1")
210 .arg(m_deintfiltername));
211 m_deinterlacing = false;
212 m_deintfiltername = "";
213 }
214 else
215 {
216 VERBOSE(VB_PLAYBACK, LOC +
217 QString("Using deinterlace method %1")
218 .arg(m_deintfiltername));
219 }
220 X11U;
221 }
222 }
223 X11S(videochain->SetDeinterlacing(m_deinterlacing));
224 return m_deinterlacing;
225}
226
227void VideoOutputOpengl::GetOSDBounds(QRect &total, QRect &visible,
228 float &visible_aspect,
229 float &font_scaling) const
230{
231 if (gl_osd)
232 {
233 total = display_visible_rect;
234 visible = display_visible_rect;
235 visible_aspect = (float) display_aspect;
236 font_scaling = 1.0f;
237 return;
238 }
239 VideoOutput::GetOSDBounds(total, visible, visible_aspect, font_scaling);
240}
241
242void VideoOutputOpengl::PrepareFrame(VideoFrame *buffer, FrameScanType t)
243{
244 if (!buffer)
245 buffer = vbuffers.GetScratchFrame();
246
247 framesPlayed = buffer->frameNumber + 1;
248
249 // TODO should cope with YUV422P, rgb24, argb32 etc
250 if (buffer->codec != FMT_YV12)
251 return;
252
253 X11L;
254 gl->MakeCurrent(true);
255 videochain->Show(t, m_deinterlacing, framesPlayed);
256
257 if (actually_draw_pip && render_onscreen && pipchain)
258 pipchain->Show(t, m_deinterlacing, framesPlayed);
259
260 if (osdchain && render_onscreen && actually_draw_osd)
261 osdchain->Show(t, m_deinterlacing, framesPlayed);
262 glFlush();
263 gl->MakeCurrent(false);
264 X11U;
265}
266
267void VideoOutputOpengl::ShowPip(VideoFrame *frame,
268 NuppelVideoPlayer *pipplayer)
269{
270 (void) frame;
271 actually_draw_pip = false;
272 if (!pipplayer)
273 return;
274
275 int pipw, piph;
276 VideoFrame *pipimage = pipplayer->GetCurrentFrame(pipw, piph);
277 float pipVideoAspect = pipplayer->GetVideoAspect();
278 uint pipVideoWidth = pipplayer->GetVideoWidth();
279 uint pipVideoHeight = pipplayer->GetVideoHeight();
280
281 // If PiP is not initialized to values we like, silently ignore the frame.
282 if ((pipVideoAspect <= 0) || !pipimage ||
283 !pipimage->buf || pipimage->codec != FMT_YV12)
284 {
285 pipplayer->ReleaseCurrentFrame(pipimage);
286 return;
287 }
288
289 QRect position = GetPIPRect(db_pip_location, pipplayer);
290
291 if (!pipchain)
292 {
293 VERBOSE(VB_PLAYBACK, LOC + "Initialise PiP.");
294 pipchain = new OpenglVideo();
295 bool success = pipchain->Init(gl, use_colourcontrols,
296 true, QSize(pipVideoWidth, pipVideoHeight),
297 position,
298 position, false);
299 success &= pipchain->AddDeinterlacer("openglonefield");
300 pipchain->SetMasterViewport(videochain->GetViewPort());
301 if (!success)
302 {
303 pipplayer->ReleaseCurrentFrame(pipimage);
304 return;
305 }
306 }
307 QSize current = pipchain->GetVideoSize();
308 if ((uint)current.width() != pipVideoWidth ||
309 (uint)current.height() != pipVideoHeight)
310 {
311 VERBOSE(VB_PLAYBACK, LOC + "Re-initialise PiP.");
312 bool success =pipchain->ReInit(gl, use_colourcontrols,
313 true, QSize(pipVideoWidth, pipVideoHeight),
314 position, position, false);
315 pipchain->SetMasterViewport(videochain->GetViewPort());
316 if (!success)
317 {
318 pipplayer->ReleaseCurrentFrame(pipimage);
319 return;
320 }
321
322 }
323 pipchain->SetVideoRect(position);
324 pipchain->UpdateInputFrame(pipimage);
325 actually_draw_pip = true;
326 pipplayer->ReleaseCurrentFrame(pipimage);
327}
328
329QRect VideoOutputOpengl::GetPIPRect(int location, NuppelVideoPlayer *pipplayer)
330{
331 if (!pipplayer || pipplayer == NULL)
332 return VideoOutput::GetPIPRect(location, pipplayer);
333
334 QRect position;
335 float pipVideoAspect = pipplayer ?
336 (float)pipplayer->GetVideoAspect() : display_aspect;
337 int tmph = (display_visible_rect.height() * db_pip_size) / 100;
338 float pixel_adj = ((float)display_visible_rect.width() /
339 (float)display_visible_rect.height()) / display_aspect;
340 position.setHeight(tmph);
341 position.setWidth((int)(tmph * pipVideoAspect * pixel_adj));
342
343 int xoff = (int)(display_visible_rect.width() * 0.06);
344 int yoff = (int)(display_visible_rect.height() * 0.06);
345 switch (location)
346 {
347 default:
348 case kPIPTopLeft:
349 yoff = display_visible_rect.height()
350 - position.height() - yoff;
351 break;
352 case kPIPBottomLeft:
353 break;
354 case kPIPTopRight:
355 xoff = display_visible_rect.width()
356 - position.width() - xoff;
357 yoff = display_visible_rect.height()
358 - position.height() - yoff;
359 break;
360 case kPIPBottomRight:
361 xoff = display_visible_rect.width()
362 - position.width() - xoff;
363 break;
364 }
365 position.moveBy(xoff, yoff);
366 return position;
367}
368
369void VideoOutputOpengl::Show(FrameScanType )
370{
371 X11L;
372 gl->SwapBuffers();
373 X11U;
374}
375
376unsigned char* VideoOutputOpengl::GetARGBFrame(QSize &size)
377{
378 // TODO
379 (void) size;
380 return NULL;
381}
382
383void VideoOutputOpengl::DrawUnusedRects(bool sync)
384{
385 (void) sync;
386}
387
388void VideoOutputOpengl::Zoom(int direction)
389{
390 VideoOutput::Zoom(direction);
391 MoveResize();
392}
393
394void VideoOutputOpengl::InputChanged(int width, int height,
395 float aspect, MythCodecID av_codec_id)
396{
397 VERBOSE(VB_PLAYBACK, LOC + "InputChanged");
398 QRect current_visible = display_visible_rect;
399 VideoOutput::InputChanged(width, height, aspect, av_codec_id);
400 ResizeForVideo((uint) width, (uint) height);
401 MoveResize();
402
403 if (videochain)
404 {
405 QSize size = videochain->GetVideoSize();
406 if (width != size.width() ||
407 height != size.height())
408 {
409 X11L;
410 videochain->ReInit(gl, use_colourcontrols,
411 render_onscreen, video_dim,
412 display_visible_rect,
413 display_video_rect, true);
414 X11U;
415 if (pipchain && pipchain != NULL)
416 pipchain->SetMasterViewport(videochain->GetViewPort());
417 CreateBuffers();
418 CreatePauseFrame();
419 }
420 }
421}
422
423void VideoOutputOpengl::MoveResize(void)
424{
425 VideoOutput::MoveResize();
426 if (videochain)
427 videochain->SetVideoRect(display_video_rect);
428}
429
430bool VideoOutputOpengl::InitSetupBuffers(void)
431{
432 vbuffers.Init(31, true, 1, 12, 4, 2, false);
433 CreateBuffers();
434 CreatePauseFrame();
435 return true;
436}
437
438void VideoOutputOpengl::CreateBuffers(void)
439{
440 if (videochain)
441 {
442 QSize size = videochain->GetVideoSize();
443 if (vbuffers.CreateBuffers(size.width(), size.height()))
444 return;
445 }
446 VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to create buffers");
447}
448
449void VideoOutputOpengl::CreatePauseFrame(void)
450{
451 vbuffers.LockFrame(&av_pause_frame, "CreatePauseFrame");
452 if (av_pause_frame.buf)
453 {
454 delete [] av_pause_frame.buf;
455 av_pause_frame.buf = NULL;
456 }
457 av_pause_frame.height = vbuffers.GetScratchFrame()->height;
458 av_pause_frame.width = vbuffers.GetScratchFrame()->width;
459 av_pause_frame.bpp = vbuffers.GetScratchFrame()->bpp;
460 av_pause_frame.size = vbuffers.GetScratchFrame()->size;
461 av_pause_frame.frameNumber = vbuffers.GetScratchFrame()->frameNumber;
462 av_pause_frame.buf = new unsigned char[av_pause_frame.size];
463 av_pause_frame.qscale_table = NULL;
464 av_pause_frame.qstride = 0;
465
466 vbuffers.UnlockFrame(&av_pause_frame, "CreatePauseFrame");
467}
468
469void VideoOutputOpengl::DeleteBuffers(bool delete_pause_frame)
470{
471 DiscardFrames(true);
472 vbuffers.DeleteBuffers();
473 if (delete_pause_frame)
474 {
475 if (av_pause_frame.buf)
476 {
477 delete [] av_pause_frame.buf;
478 av_pause_frame.buf = NULL;
479 }
480 if (av_pause_frame.qscale_table)
481 {
482 delete [] av_pause_frame.qscale_table;
483 av_pause_frame.qscale_table = NULL;
484 }
485 }
486}
487
488void VideoOutputOpengl::EmbedInWidget(WId wid, int x, int y, int w, int h)
489{
490 (void) wid;
491 (void) x;
492 (void) y;
493 (void) w;
494 (void) h;
495}
496
497void VideoOutputOpengl::StopEmbedding(void)
498{
499}
500
501float VideoOutputOpengl::GetDisplayAspect(void)
502{
503 return display_aspect;
504}
505
506void VideoOutputOpengl::UpdatePauseFrame(void)
507{
508 vbuffers.LockFrame(&av_pause_frame, "UpdatePauseFrame -- pause");
509
510 vbuffers.begin_lock(kVideoBuffer_used);
511 VideoFrame *used_frame = NULL;
512 if (vbuffers.size(kVideoBuffer_used) > 0)
513 {
514 used_frame = vbuffers.head(kVideoBuffer_used);
515 if (!vbuffers.TryLockFrame(used_frame, "UpdatePauseFrame -- used"))
516 used_frame = NULL;
517 }
518 if (used_frame)
519 {
520 CopyFrame(&av_pause_frame, used_frame);
521 vbuffers.UnlockFrame(used_frame, "UpdatePauseFrame -- used");
522 }
523 vbuffers.end_lock();
524 if (!used_frame &&
525 vbuffers.TryLockFrame(vbuffers.GetScratchFrame(),
526 "UpdatePauseFrame -- scratch"))
527 {
528 vbuffers.GetScratchFrame()->frameNumber = framesPlayed - 1;
529 CopyFrame(&av_pause_frame, vbuffers.GetScratchFrame());
530 vbuffers.UnlockFrame(vbuffers.GetScratchFrame(),
531 "UpdatePauseFrame -- scratch");
532 }
533 vbuffers.UnlockFrame(&av_pause_frame, "UpdatePauseFrame - used");
534}
535
536void VideoOutputOpengl::ProcessFrame(VideoFrame *frame, OSD *osd,
537 FilterChain *filterList,
538 NuppelVideoPlayer *pipPlayer)
539{
540 bool pauseframe = false;
541 if (!frame)
542 {
543 frame = vbuffers.GetScratchFrame();
544 CopyFrame(vbuffers.GetScratchFrame(), &av_pause_frame);
545 pauseframe = true;
546 }
547
548 // disable image processing for offscreen rendering
549 X11L;
550 gl->MakeCurrent(true);
551 if (render_onscreen)
552 {
553 if (filterList)
554 filterList->ProcessFrame(frame);
555 if (m_deinterlacing && m_deintFilter != NULL &&
556 m_deinterlaceBeforeOSD &&
557 !pauseframe)
558 {
559 m_deintFilter->ProcessFrame(frame);
560 }
561 ShowPip(frame, pipPlayer);
562 gl_osd ? DisplayOSD(frame, osd) : VideoOutput::DisplayOSD(frame, osd);
563
564 if (m_deinterlacing && m_deintFilter != NULL &&
565 !m_deinterlaceBeforeOSD &&
566 !pauseframe)
567 {
568 m_deintFilter->ProcessFrame(frame);
569 }
570 }
571 if (videochain)
572 videochain->UpdateInputFrame(frame);
573 gl->MakeCurrent(false);
574 X11U;
575}
576
577int VideoOutputOpengl::DisplayOSD(VideoFrame *frame, OSD *osd,
578 int stride, int revision)
579{
580 (void) stride;
581 (void) frame;
582 actually_draw_osd = false;
583 if (!osd || !osdchain)
584 return -1;
585
586 if (vsz_enabled && videochain)
587 videochain->SetVideoResize(&vsz_desired_display_rect);
588
589 OSDSurface *surface = osd->Display();
590 if (!surface)
591 return -1;
592
593 actually_draw_osd = true;
594 bool changed = (-1 == revision) ?
595 surface->Changed() : (surface->GetRevision()!=revision);
596
597 if (changed)
598 {
599 QSize visible(display_visible_rect.width(),
600 display_visible_rect.height());
601 osdchain->UpdateInput(surface->yuvbuffer, 0, FMT_YV12, visible);
602 osdchain->UpdateInput(surface->alpha, 1, FMT_ALPHA, visible);
603 }
604 return changed;
605}
606
607void VideoOutputOpengl::ShutdownVideoResize(void)
608{
609 if (!osdchain)
610 {
611 VideoOutput::ShutdownVideoResize();
612 return;
613 }
614 if (videochain)
615 videochain->SetVideoResize(NULL);
616 vsz_enabled = false;
617}
618
619int VideoOutputOpengl::SetPictureAttribute(int attributeType, int newValue)
620{
621 if (kPictureAttribute_Hue == attributeType)
622 return -1;
623 SetPictureAttributeDBValue(attributeType, newValue);
624 if (videochain && use_colourcontrols)
625 videochain->SetPictureAttribute(attributeType, newValue);
626 return newValue;
627}
628
629int VideoOutputOpengl::GetRefreshRate(void)
630{
631 XF86VidModeModeLine mode_line;
632 int dot_clock;
633
634 int ret = False;
635 X11S(ret = XF86VidModeGetModeLine(gl->GetDisplay(), gl->GetScreenNum(),
636 &dot_clock, &mode_line));
637 if (!ret)
638 {
639 VERBOSE(VB_IMPORTANT, LOC_ERR + "GetRefreshRate(): "
640 "X11 ModeLine query failed");
641 return -1;
642 }
643
644 double rate = (double)((double)(dot_clock * 1000.0) /
645 (double)(mode_line.htotal * mode_line.vtotal));
646
647 // Assume 60Hz if we can't otherwise determine it.
648 if (rate == 0)
649 rate = 60;
650
651 if (rate < 20 || rate > 200)
652 {
653 VERBOSE(VB_PLAYBACK, LOC + QString("Unreasonable refresh rate %1Hz "
654 "reported by X").arg(rate));
655 rate = 60;
656 }
657
658 rate = 1000000.0 / rate;
659
660 return (int)rate;
661}
662void VideoOutputOpengl::ResizeForGui(void)
663{
664 if (display_res)
665 display_res->SwitchToGUI();
666}
667
668void VideoOutputOpengl::ResizeForVideo(uint width, uint height)
669{
670 if ((width == 1920 || width == 1440) && height == 1088)
671 height = 1080; // ATSC 1920x1080
672
673 if (display_res && display_res->SwitchToVideo(width, height))
674 {
675 // Switching to custom display resolution succeeded
676 // Make a note of the new size
677 display_dim = QSize(display_res->GetPhysicalWidth(),
678 display_res->GetPhysicalHeight());
679 display_aspect = display_res->GetAspectRatio();
680
681 bool fullscreen = !gContext->GetNumSetting("GuiSizeForTV", 0);
682
683 // if width && height are zero users expect fullscreen playback
684 if (!fullscreen)
685 {
686 int gui_width = 0, gui_height = 0;
687 gContext->GetResolutionSetting("Gui", gui_width, gui_height);
688 fullscreen |= (0 == gui_width && 0 == gui_height);
689 }
690
691 if (fullscreen)
692 {
693 QSize sz(display_res->GetWidth(), display_res->GetHeight());
694 display_visible_rect = QRect(QPoint(0,0), sz);
695 // Resize X window to fill new resolution
696 X11S(XMoveResizeWindow(gl->GetDisplay(), gl->GetWindow(),
697 display_visible_rect.left(),
698 display_visible_rect.top(),
699 display_visible_rect.width(),
700 display_visible_rect.height()));
701 }
702 }
703}
704
705void VideoOutputOpengl::ResizeForVideo(void)
706{
707 if (videochain)
708 {
709 QSize size = videochain->GetVideoSize();
710 ResizeForVideo(size.width(), size.height());
711 }
712}
713
714void VideoOutputOpengl::InitDisplayMeasurements(uint width, uint height)
715{
716 if (display_res)
717 {
718 // The very first Resize needs to be the maximum possible
719 // desired res, because X will mask off anything outside
720 // the initial dimensions
721 X11S(XMoveResizeWindow(gl->GetDisplay(), gl->GetWindow(), 0, 0,
722 display_res->GetMaxWidth(),
723 display_res->GetMaxHeight()));
724 ResizeForVideo(width, height);
725 }
726 else
727 {
728 display_dim = QSize(DisplayWidthMM(gl->GetDisplay(),
729 gl->GetScreenNum()),
730 DisplayHeightMM(gl->GetDisplay(),
731 gl->GetScreenNum()));
732
733 if (db_display_dim.width() > 0 && db_display_dim.height() > 0)
734 display_dim = db_display_dim;
735 }
736
737 // Fetch pixel width and height of the display
738 int xbase, ybase, w, h;
739 gContext->GetScreenBounds(xbase, ybase, w, h);
740 // Determine window dimensions in pixels
741 int window_w = w, window_h = h;
742 if (gContext->GetNumSetting("GuiSizeForTV", 0))
743 gContext->GetResolutionSetting("Gui", window_w, window_h);
744 else
745 gContext->GetScreenBounds(xbase, ybase, window_w, window_h);
746 window_w = (window_w) ? window_w : w;
747 window_h = (window_h) ? window_h : h;
748 float pixel_aspect = ((float)w) / ((float)h);
749
750 VERBOSE(VB_PLAYBACK, LOC + QString(
751 "Pixel dimensions: Screen %1x%2, window %3x%4")
752 .arg(w).arg(h).arg(window_w).arg(window_h));
753
754 // Determine if we are using Xinerama
755 int event_base, error_base;
756 bool usingXinerama = false;
757 X11S(usingXinerama =
758 (XineramaQueryExtension(gl->GetDisplay(), &event_base, &error_base) &&
759 XineramaIsActive(gl->GetDisplay())));
760
761 // If the dimensions are invalid, assume square pixels and 17" screen.
762 // Only print warning if this isn't Xinerama, we will fix Xinerama later.
763 if (((display_dim.width() <= 0) || (display_dim.height() <= 0)) &&
764 !usingXinerama)
765 {
766 VERBOSE(VB_GENERAL, LOC + "Physical size of display unknown."
767 "\n\t\t\tAssuming 17\" monitor with square pixels.");
768 display_dim.setHeight(300);
769 display_dim.setWidth((int) round(300 * pixel_aspect));
770 }
771
772 // If we are using Xinerama the display dimensions can not be trusted.
773 // We need to use the Xinerama monitor aspect ratio from the DB to set
774 // the physical screen width. This assumes the height is correct, which
775 // is more or less true in the typical side-by-side monitor setup.
776 if (usingXinerama)
777 {
778 float displayAspect = gContext->GetFloatSettingOnHost(
779 "XineramaMonitorAspectRatio",
780 gContext->GetHostName(), pixel_aspect);
781 int height = display_dim.height();
782 if (height <= 0)
783 display_dim.setHeight(height = 300);
784 display_dim.setWidth((int) round(height * displayAspect));
785 }
786
787 VERBOSE(VB_PLAYBACK, LOC +
788 QString("Estimated display dimensions: %1x%2 mm Aspect: %3")
789 .arg(display_dim.width()).arg(display_dim.height())
790 .arg(((float) display_dim.width()) / display_dim.height()));
791
792 // We must now scale the display measurements to our window size.
793 // If we are running fullscreen this is a no-op.
794 display_dim = QSize((display_dim.width() * window_w) / w,
795 (display_dim.height() * window_h) / h);
796
797 // Now that we know the physical monitor size, we can
798 // calculate the display aspect ratio pretty simply...
799 display_aspect = ((float)display_dim.width()) / display_dim.height();
800
801 // If we are using XRandR, use the aspect ratio from it instead...
802 if (display_res)
803 display_aspect = display_res->GetAspectRatio();
804
805 VERBOSE(VB_PLAYBACK, LOC +
806 QString("Estimated window dimensions: %1x%2 mm Aspect: %3")
807 .arg(display_dim.width()).arg(display_dim.height())
808 .arg(display_aspect));
809}
810
811#undef LOC
812#undef LOC_ERR
813
814#define LOC QString("OpenglVid: ")
815#define LOC_ERR QString("OpenglVid Error: ")
816
817QMap<int,float> OpenglVideo::pictureAttribs;
818
819OpenglVideo::OpenglVideo() :
820 convertBuf(NULL)
821{
822}
823
824OpenglVideo::~OpenglVideo()
825{
826 Cleanup();
827}
828
829void OpenglVideo::Cleanup()
830{
831 ShutDownYUV2RGB();
832 gl->MakeCurrent(true);
833 if (frameBuffer)
834 gl->DeleteFrameBuffer(&frameBuffer);
835 if (frameBufferTexture)
836 gl->DeleteTexture(&frameBufferTexture);
837
838 for (uint i = 0; i < inputTextures.size(); i++)
839 gl->DeleteTexture(&inputTextures[i]);
840 inputTextures.clear();
841
842 if (!filters.empty())
843 {
844 map<OpenglFilter,Filter>::iterator it;
845 for (it = filters.begin(); it != filters.end(); it++)
846 {
847 if (it->second.fragmentProgram)
848 gl->DeleteProgram(&(it->second.fragmentProgram));
849 vector<GLuint> temp = it->second.frameBuffers;
850 for (uint i = 0; i < temp.size(); i++)
851 gl->DeleteFrameBuffer(&(temp[i]));
852 temp = it->second.frameBufferTextures;
853 for (uint i = 0; i < temp.size(); i++)
854 gl->DeleteTexture(&(temp[i]));
855 }
856 }
857 filters.clear();
858}
859
860bool OpenglVideo::Init(OpenglContext *glcontext, bool colour_control,
861 bool onscreen, QSize video_size, QRect visible_rect,
862 QRect video_rect, bool viewport_control, bool osd)
863{
864 gl = glcontext;
865 videoSize = video_size;
866 visibleRect = visible_rect;
867 videoRect = video_rect;
868 masterViewportSize = QSize(1920, 1080);
869 QSize rect = videoSize;
870 GetTextureSize(&rect, videoSize);
871 frameBufferRect = QRect(QPoint(0,0), rect);
872 invertVideo = true;
873 softwareDeinterlacer = "";
874 hardwareDeinterlacing = false;
875 useColourControl = colour_control;
876 viewportControl = viewport_control;
877 inputTextureSize = QSize(0,0);
878 convertSize = QSize(0,0);
879 videoResize = false;
880 videoResizeRect = QRect(0,0,0,0);
881 frameBuffer = 0;
882 currentFrameNum = -1;
883 inputUpdated = false;
884
885 if (!onscreen)
886 {
887 QSize fb_size(visibleRect.width(), visibleRect.height());
888 GetTextureSize(&fb_size, fb_size);
889 if (!AddFrameBuffer(&frameBuffer, &frameBufferTexture, fb_size))
890 return false;
891 }
892
893 SetViewPort(visibleRect.width(), visibleRect.height());
894 InitOpengl();
895
896 if (osd)
897 {
898 QSize osdsize(visibleRect.width(),
899 visibleRect.height());
900 GLuint alphatex = CreateVideoTexture(osdsize, &inputTextureSize,
901 gl->TextureSupport() ? false : true);
902 GLuint yuv12tex = CreateVideoTexture(osdsize, &inputTextureSize, true);
903 if ((alphatex && yuv12tex) && AddFilter(kYUV2RGBA))
904 {
905 inputTextures.push_back(yuv12tex);
906 inputTextures.push_back(alphatex);
907 }
908 }
909 else
910 {
911 GLuint yuv12tex = CreateVideoTexture(videoSize,
912 &inputTextureSize, true);
913 if (yuv12tex && AddFilter(kYUV2RGB))
914 inputTextures.push_back(yuv12tex);
915 }
916
917 if (filters.empty())
918 {
919 if (osd)
920 {
921 Cleanup();
922 return false;
923 }
924 VERBOSE(VB_PLAYBACK, LOC + "Opengl colour conversion failed.");
925 VERBOSE(VB_PLAYBACK, LOC + "Falling back to software conversion.");
926 VERBOSE(VB_PLAYBACK, LOC + "Any opengl filters will also be disabled.");
927 GLuint yuv12tex = CreateVideoTexture(videoSize,
928 &inputTextureSize, false);
929 if (yuv12tex && AddFilter(kRESIZE))
930 inputTextures.push_back(yuv12tex);
931 else
932 {
933 VERBOSE(VB_PLAYBACK, LOC_ERR + "Fatal error.");
934 Cleanup();
935 return false;
936 }
937 }
938 return true;
939}
940
941OpenglFilter OpenglVideo::GetDeintFilter(void)
942{
943 if (filters.count(kKDEINT))
944 return kKDEINT;
945 if (filters.count(kLINBLEND))
946 return kLINBLEND;
947 if (filters.count(kONEFIELD))
948 return kONEFIELD;
949 if (filters.count(kBOBDEINT))
950 return kBOBDEINT;
951 if (filters.count(kPROGONEFIELD))
952 return kPROGONEFIELD;
953 if (filters.count(kPROGLINBLEND))
954 return kPROGLINBLEND;
955 if (filters.count(kPROGKDEINT))
956 return kPROGKDEINT;
957 return kNOFILT;
958}
959
960bool OpenglVideo::OptimiseFilters(void)
961{
962 // if video height does not match display rect height, add resize stage
963 // to preserve field information N.B. assumes interlaced
964 // if video rectangle is smaller than display rectangle, add resize stage
965 // to improve performance
966
967 bool needResize = ((videoSize.height() != videoRect.height()) ||
968 (videoSize.width() < videoRect.width()));
969 if (needResize && !filters.count(kRESIZE))
970 AddFilter(kRESIZE);
971
972 map<OpenglFilter, Filter>::reverse_iterator it;
973
974 // add/remove required frame buffer objects
975 // and link filters
976 uint buffers_needed = 1;
977 bool last_filter = true;
978 bool needtorotate = false;
979 int i = filters.size();
980 for (it = filters.rbegin(); it != filters.rend(); it++, i--)
981 {
982 it->second.outputBuffer = kFrameBufferObject;
983 it->second.rotateFrameBuffers = needtorotate;
984 if (!last_filter)
985 {
986 uint buffers_have = it->second.frameBuffers.size();
987 int buffers_diff = buffers_needed - buffers_have;
988 if (buffers_diff > 0)
989 {
990 GLuint tmp_buf, tmp_tex;
991 QSize fb_size(videoSize.width(), videoSize.height());
992 GetTextureSize(&fb_size, fb_size);
993 for (int i = 0; i < buffers_diff; i++)
994 {
995 if (!AddFrameBuffer(&tmp_buf, &tmp_tex, fb_size))
996 return false;
997 else
998 {
999 it->second.frameBuffers.push_back(tmp_buf);
1000 it->second.frameBufferTextures.push_back(tmp_tex);
1001 }
1002 }
1003 }
1004 else if (buffers_diff < 0)
1005 {
1006 for (int i = 0; i > buffers_diff; i--)
1007 {
1008 gl->DeleteFrameBuffer(&(it->second.frameBuffers.back()));
1009 gl->DeleteTexture(&(it->second.frameBufferTextures.back()));
1010 it->second.frameBuffers.pop_back();
1011 it->second.frameBufferTextures.pop_back();
1012 }
1013 }
1014 }
1015 else
1016 {
1017 last_filter = false;
1018 }
1019 buffers_needed = it->second.numInputs;
1020 needtorotate = (it->first == kKDEINT ||
1021 it->first == kLINBLEND ||
1022 it->first == kPROGONEFIELD ||
1023 it->first == kPROGLINBLEND ||
1024 it->first == kPROGKDEINT);
1025 }
1026
1027 bool deinterlacing = hardwareDeinterlacing;
1028 hardwareDeinterlacing = true;
1029 SetDeinterlacing(false);
1030 if (deinterlacing)
1031 SetDeinterlacing(deinterlacing);
1032 return true;
1033}
1034
1035void OpenglVideo::SetFiltering(void)
1036{
1037 // filter settings included for performance only
1038 // no (obvious) quality improvement over GL_LINEAR throughout
1039 if (filters.empty())
1040 return;
1041
1042 if (filters.size() == 1)
1043 {
1044 SetTextureFilters(&inputTextures, GL_LINEAR);
1045 return;
1046 }
1047
1048 SetTextureFilters(&inputTextures, GL_NEAREST);
1049 vector<GLuint> textures;
1050 map<OpenglFilter,Filter>::iterator it;
1051 for (it = filters.begin(); it != filters.end(); it++)
1052 SetTextureFilters(&(it->second.frameBufferTextures), GL_NEAREST);
1053
1054 // resize or last active (ie don't need resize) need GL_LINEAR
1055 map<OpenglFilter, Filter>::reverse_iterator rit;
1056 bool next = false;
1057 bool resize = filters.count(kRESIZE);
1058 for (rit = filters.rbegin(); rit != filters.rend(); rit++)
1059 {
1060 if (next && (rit->second.outputBuffer != kNoBuffer))
1061 {
1062 SetTextureFilters(&(rit->second.frameBufferTextures), GL_LINEAR);
1063 return;
1064 break;
1065 }
1066 if (resize)
1067 if (rit->first == kRESIZE)
1068 next = true;
1069 else
1070 if (rit->second.outputBuffer == kDefaultBuffer)
1071 next = true;
1072 }
1073 SetTextureFilters(&inputTextures, GL_LINEAR);
1074}
1075
1076bool OpenglVideo::ReInit(OpenglContext *glcontext, bool colour_control,
1077 bool onscreen, QSize video_size, QRect visible_rect,
1078 QRect video_rect, bool viewport_control, bool osd)
1079{
1080 VERBOSE(VB_PLAYBACK, LOC + "Reinit");
1081 gl->MakeCurrent(true);
1082 QString harddeint = GetDeinterlacer(); // N.B. only adds back deinterlacer
1083 QString softdeint = softwareDeinterlacer;
1084 bool interlacing = hardwareDeinterlacing;
1085 bool resize = videoResize;
1086 QRect resize_rect = videoResizeRect;
1087
1088 Cleanup();
1089 bool success = Init(glcontext, colour_control, onscreen, video_size,
1090 visible_rect, video_rect, viewport_control, osd);
1091 if (harddeint != "")
1092 success &= AddDeinterlacer(harddeint);
1093 softwareDeinterlacer = softdeint;
1094 SetDeinterlacing(interlacing);
1095 if (resize)
1096 SetVideoResize(&resize_rect);
1097
1098 return success;
1099}
1100
1101bool OpenglVideo::AddFilter(OpenglFilter filter)
1102{
1103 if (filters.count(filter))
1104 return true;
1105
1106 VERBOSE(VB_PLAYBACK, LOC + QString("Creating %1 filter.")
1107 .arg(FilterToString(filter)));
1108 gl->MakeCurrent(true);
1109 Filter temp;
1110 if (filter == kYUV2RGBA || filter == kLINBLEND || filter == kKDEINT)
1111 temp.numInputs = 2;
1112 else if (filter == kPROGONEFIELD || filter == kPROGKDEINT ||
1113 filter == kPROGLINBLEND)
1114 temp.numInputs = 3;
1115 else
1116 temp.numInputs = 1;
1117
1118 GLuint program = 0;
1119 if (filter != kNOFILT && filter != kRESIZE)
1120 {
1121 program = AddFragmentProgram(filter);
1122 if (!program)
1123 return false;
1124 }
1125 temp.fragmentProgram = program;
1126 temp.outputBuffer = kDefaultBuffer;
1127 temp.rotateFrameBuffers = false;
1128 temp.frameBuffers.clear();
1129 temp.frameBufferTextures.clear();
1130 filters[filter] = temp;
1131 if (OptimiseFilters())
1132 return true;
1133
1134 RemoveFilter(filter);
1135 return false;
1136}
1137
1138bool OpenglVideo::RemoveFilter(OpenglFilter filter)
1139{
1140 if (!filters.count(filter))
1141 return true;
1142
1143 gl->MakeCurrent(true);
1144 gl->DeleteProgram(&(filters[filter].fragmentProgram));
1145 vector<GLuint> temp;
1146 vector<GLuint>::iterator it;
1147 temp = filters[filter].frameBuffers;
1148 for (it = temp.begin(); it != temp.end(); it++)
1149 gl->DeleteFrameBuffer(&(*(it)));
1150 temp = filters[filter].frameBufferTextures;
1151 for (it = temp.begin(); it != temp.end(); it++)
1152 gl->DeleteTexture(&(*(it)));
1153 filters.erase(filter);
1154 gl->MakeCurrent(false);
1155
1156 return true;
1157}
1158
1159bool OpenglVideo::AddDeinterlacer(QString filter)
1160{
1161 QString current_deinterlacer = GetDeinterlacer();
1162 if (current_deinterlacer != "")
1163 {
1164 if (current_deinterlacer == filter)
1165 return true;
1166 else
1167 RemoveFilter(current_deinterlacer);
1168 }
1169 return AddFilter(filter);
1170}
1171
1172GLuint OpenglVideo::AddFragmentProgram(OpenglFilter name)
1173{
1174 if (!gl->FragProgSupport())
1175 {
1176 VERBOSE(VB_PLAYBACK, LOC_ERR + "Fragment programs not supported");
1177 return false;
1178 }
1179 GLuint ret;
1180 QString program = GetProgramString(name);
1181 program.replace("%1", gl->TextureSupport() ? "RECT" : "2D");
1182 if (gl->CreateProgram(&program, &ret))
1183 {
1184 VERBOSE(VB_PLAYBACK, LOC + QString("Created fragment program %1.")
1185 .arg(FilterToString(name)));
1186 return ret;
1187 }
1188 return 0;
1189}
1190
1191bool OpenglVideo::AddFrameBuffer(GLuint *framebuffer,
1192 GLuint *texture, QSize size)
1193{
1194 if (!gl->FrameBufSupport())
1195 {
1196 VERBOSE(VB_PLAYBACK, LOC_ERR + "Offscreen rendering not supported.");
1197 return false;
1198 }
1199 GLuint tmp_buf, tmp_tex;
1200 gl->CreateTexture(&tmp_tex);
1201 if(gl->CreateFrameBuffer( &tmp_buf, &tmp_tex, size.width(), size.height()))
1202 {
1203 *framebuffer = tmp_buf;
1204 *texture = tmp_tex;
1205 return true;
1206 }
1207 gl->DeleteTexture(&tmp_tex);
1208 return false;
1209}
1210
1211void OpenglVideo::SetViewPort(uint width, uint height)
1212{
1213 uint w = max((int)width, videoSize.width());
1214 uint h = max((int)height, videoSize.height());
1215 viewportSize = QSize(w, h);
1216 if (!viewportControl)
1217 return;
1218 VERBOSE(VB_PLAYBACK, LOC +
1219 QString("Viewport: %1x%2").arg(w).arg(h));
1220 ViewPort(w, h);
1221}
1222
1223void OpenglVideo::ViewPort(uint width, uint height)
1224{
1225 glViewport( 0, 0, width, height);
1226 glMatrixMode( GL_PROJECTION );
1227 glLoadIdentity();
1228 glOrtho( 0, width - 1, 0, height - 1, 1, -1 ); // aargh...
1229 glMatrixMode( GL_MODELVIEW );
1230 glLoadIdentity();
1231}
1232
1233void OpenglVideo::InitOpengl(void)
1234{
1235 gl->MakeCurrent(true);
1236 glDisable( GL_BLEND );
1237 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); // for gl osd
1238 glDisable( GL_DEPTH_TEST );
1239 glDepthMask( GL_FALSE );
1240 glDisable( GL_CULL_FACE );
1241 gl->EnableTextures();;
1242 glShadeModel( GL_FLAT );
1243 glDisable( GL_POLYGON_SMOOTH );
1244 glDisable( GL_LINE_SMOOTH );
1245 glDisable( GL_POINT_SMOOTH );
1246 glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
1247 glClear( GL_COLOR_BUFFER_BIT );
1248 glFlush();
1249 gl->MakeCurrent(false);
1250}
1251
1252GLuint OpenglVideo::CreateVideoTexture(QSize size, QSize *tex_size, bool YUV12)
1253{
1254 GLuint tmp_tex;
1255 QSize temp = size;
1256 gl->CreateTexture(&tmp_tex);
1257 GetTextureSize(&temp, size);
1258 if (YUV12)
1259 {
1260 if (gl->TextureSupport())
1261 {
1262 temp.rheight() = size.height() * 3 / 2;
1263 }
1264 else
1265 {
1266 if ((size.height() * 1.5) > temp.height())
1267 temp.rheight() *= 2;
1268 }
1269 }
1270 if (temp.width() > gl->GetMaxTexSize() ||
1271 temp.height() > gl->GetMaxTexSize() ||
1272 !gl->SetupTexture(temp, &tmp_tex, GL_LINEAR))
1273 {
1274 VERBOSE(VB_PLAYBACK, LOC_ERR + "Could not create texture.");
1275 gl->DeleteTexture(&tmp_tex);
1276 return 0;
1277 }
1278
1279 tex_size->setWidth(temp.width());
1280 tex_size->setHeight(temp.height());
1281 VERBOSE(VB_PLAYBACK, LOC + QString("Created main input texture %1x%2")
1282 .arg(temp.width()).arg(temp.height()));
1283 return tmp_tex;
1284}
1285
1286void OpenglVideo::GetTextureSize(QSize *temp, QSize size)
1287{
1288 if (gl->TextureSupport())
1289 return; glOrtho( 0, width, 0, height, 1, -1 ); // aargh...
1290 int w, h;
1291 w = h = 64;
1292 while (w < size.width()) { w *= 2; }
1293 while (h < size.height()) { h *= 2; }
1294 temp->setWidth(w);
1295 temp->setHeight(h);
1296}
1297
1298void OpenglVideo::UpdateInputFrame(VideoFrame *frame)
1299{
1300 bool errored = false;
1301 if (frame->width != videoSize.width() ||
1302 frame->height != videoSize.height() ||
1303 frame->width < 1 ||
1304 frame->height < 1)
1305 errored = true;
1306
1307 if (filters.count(kYUV2RGB) && !errored && frame->codec == FMT_YV12)
1308 UpdateInput(frame->buf, 0, FMT_YV12, videoSize);
1309 else // software yuv2rgb
1310 {
1311 if (errored)
1312 {
1313 ShutDownYUV2RGB();
1314 return;
1315 }
1316 if (convertSize != videoSize)
1317 {
1318 ShutDownYUV2RGB();
1319 VERBOSE(VB_PLAYBACK, LOC + "Init software conversion.");
1320 convertSize = videoSize;
1321 convertBuf = new unsigned char[(videoSize.width() *
1322 videoSize.height() * 3) + 4];
1323 }
1324 if (convertBuf)
1325 {
1326 AVPicture img_in, img_out;
1327 avpicture_fill(&img_out, (uint8_t *)convertBuf, PIX_FMT_RGB24,
1328 convertSize.width(), convertSize.height());
1329 avpicture_fill(&img_in, (uint8_t *)frame->buf, PIX_FMT_YUV420P,
1330 convertSize.width(), convertSize.height());
1331 img_convert(&img_out, PIX_FMT_RGB24,
1332 &img_in, PIX_FMT_YUV420P,
1333 convertSize.width(), convertSize.height());
1334 UpdateInput(convertBuf, 0, FMT_RGB24, convertSize);
1335 }
1336 }
1337}
1338
1339void OpenglVideo::UpdateInput(unsigned char *buf, uint texture,
1340 int format, QSize size)
1341{
1342 inputUpdated = false;
1343 if (texture > (inputTextures.size() - 1))
1344 return;
1345
1346 glBindTexture( gl->m_texture, inputTextures[texture] );
1347 switch (format)
1348 {
1349 case FMT_YV12:
1350 glTexSubImage2D( gl->m_texture, 0, 0, 0,
1351 size.width(), size.height(),
1352 GL_LUMINANCE, GL_UNSIGNED_BYTE, buf);
1353 glTexSubImage2D( gl->m_texture, 0, 0, size.height() ,
1354 size.width() / 2, size.height() / 2,
1355 GL_LUMINANCE, GL_UNSIGNED_BYTE,
1356 buf + (size.width() * size.height()));
1357 glTexSubImage2D( gl->m_texture, 0, size.width() / 2,
1358 size.height() , size.width() / 2,
1359 size.height() / 2, GL_LUMINANCE,
1360 GL_UNSIGNED_BYTE,buf + (size.width()
1361 * size.height()) * 5 / 4);
1362 break;
1363 case FMT_RGB24:
1364 glTexSubImage2D( gl->m_texture, 0, 0, 0,
1365 size.width(), size.height(),
1366 GL_RGB, GL_UNSIGNED_BYTE, buf);
1367 break;
1368 case FMT_ALPHA:
1369 glTexSubImage2D( gl->m_texture, 0, 0, 0,
1370 size.width(), size.height(),
1371 GL_ALPHA, GL_UNSIGNED_BYTE,
1372 buf);
1373 break;
1374 default:
1375 break;
1376 }
1377 inputUpdated = true;
1378}
1379
1380void OpenglVideo::ShutDownYUV2RGB(void)
1381{
1382 if (convertBuf)
1383 {
1384 delete convertBuf;
1385 convertBuf = NULL;
1386 }
1387 convertSize = QSize(0,0);
1388}
1389
1390void OpenglVideo::SetVideoResize(QRect *size)
1391{
1392 if (!size || size == NULL)
1393 {
1394 videoResize = false;
1395 videoResizeRect = QRect(0, 0, 0, 0);
1396 return;
1397 }
1398
1399 bool abort = (size->right() > videoSize.width() ||
1400 size->bottom() > videoSize.height() ||
1401 size->width() > videoSize.width() ||
1402 size->height() > videoSize.height());
1403 // if resize == existing frame, no need to carry on
1404 abort |= !size->left() && !size->top() && (size->size() == videoSize);
1405
1406 if (abort)
1407 {
1408 videoResize = false;
1409 videoResizeRect = QRect(0, 0, 0, 0);
1410 return;
1411 }
1412
1413 videoResize = true;
1414 videoResizeRect = *size;
1415}
1416
1417void OpenglVideo::CalculateResize(float *left, float *top,
1418 float *right, float *bottom)
1419{
1420 // FIXME video aspect == display aspect
1421 float height = visibleRect.height();
1422 float new_top = height - ((float)videoResizeRect.bottom() /
1423 (float)videoSize.height()) * height;
1424 float new_bottom = height - ((float)videoResizeRect.top() /
1425 (float)videoSize.height()) * height;
1426 *left = ((float)videoResizeRect.left() / (float)videoSize.width()) *
1427 visibleRect.width();
1428 *right = ((float)videoResizeRect.right() / (float)videoSize.width()) *
1429 visibleRect.width();
1430 *top = new_top;
1431 *bottom = new_bottom;
1432}
1433
1434void OpenglVideo::SetDeinterlacing(bool deinterlacing)
1435{
1436 if (deinterlacing == hardwareDeinterlacing)
1437 return;
1438 VERBOSE(VB_PLAYBACK, LOC + QString("Turning %1 deinterlacing.")
1439 .arg(deinterlacing ? "on" : "off"));
1440 hardwareDeinterlacing = deinterlacing;
1441 map<OpenglFilter, Filter>::iterator it;
1442 for (it = filters.begin(); it !=filters.end(); it++)
1443 {
1444 it->second.outputBuffer = kFrameBufferObject;
1445 if (it->first >= kKDEINT && it->first <= kPROGONEFIELD)
1446 {
1447 if (!deinterlacing)
1448 it->second.outputBuffer = kNoBuffer;
1449 }
1450 }
1451 map<OpenglFilter, Filter>::reverse_iterator rit;
1452 for (rit = filters.rbegin(); rit !=filters.rend(); rit++)
1453 {
1454 if (rit->second.outputBuffer == kFrameBufferObject)
1455 {
1456 rit->second.outputBuffer = kDefaultBuffer;
1457 break;
1458 }
1459 }
1460 gl->MakeCurrent(true);
1461 SetFiltering();
1462 gl->MakeCurrent(false);
1463}
1464
1465void OpenglVideo::Show(FrameScanType scan, bool softwareDeinterlacing,
1466 long long frame)
1467{
1468 if (inputTextures.empty() || filters.empty())
1469 return;
1470
1471 vector<GLuint> inputs = inputTextures;
1472 QSize inputsize = inputTextureSize;
1473 uint numfilters = filters.size();
1474
1475 map<OpenglFilter, Filter>::iterator it;
1476 for (it = filters.begin(); it !=filters.end(); it++)
1477 {
1478 if (it->second.rotateFrameBuffers &&
1479 !(it->first == kYUV2RGB && scan == kScan_Intr2ndField))
1480 {
1481 Rotate(&(it->second.frameBufferTextures));
1482 Rotate(&(it->second.frameBuffers));
1483 }
1484
1485 // skip disabled filters
1486 if (it->second.outputBuffer == kNoBuffer)
1487 continue;
1488
1489 OpenglFilter type = it->first;
1490 Filter filter = it->second;
1491
1492 // skip colour conversion for frames already in frame buffer
1493 if (!inputUpdated)
1494 if (frame == currentFrameNum && type == kYUV2RGB && frame != 0)
1495 if (!(softwareDeinterlacing &&
1496 softwareDeinterlacer == "bobdeint"))
1497 {
1498 inputs = filter.frameBufferTextures;
1499 inputsize = videoSize;
1500 continue;
1501 }
1502
1503 // texture coordinates
1504 float t_right = (float)videoSize.width();
1505 float t_bottom = (float)videoSize.height();
1506 float t_top = 0.0f;
1507
1508 if (!gl->TextureSupport())
1509 {
1510 t_right /= inputsize.width();
1511 t_bottom /= inputsize.height();
1512 }
1513
1514 float full_height = t_bottom;
1515 float line_height = (t_bottom / (float)videoSize.height());
1516 float bob = line_height / 2.0f;
1517
1518 if (type == kBOBDEINT)
1519 {
1520 if (scan == kScan_Interlaced)
1521 {
1522 t_bottom += bob;
1523 t_top += bob;
1524 }
1525 if (scan == kScan_Intr2ndField)
1526 {
1527 t_bottom -= bob;
1528 t_top -= bob;
1529 }
1530 }
1531 if (softwareDeinterlacer == "bobdeint" &&
1532 softwareDeinterlacing &&
1533 type == kYUV2RGB)
1534 {
1535 bob = line_height / 4.0f;
1536 if (scan == kScan_Interlaced)
1537 {
1538 t_bottom /= 2;
1539 t_bottom += bob;
1540 t_top += bob;
1541 }
1542 if (scan == kScan_Intr2ndField)
1543 {
1544 t_top = t_bottom / 2;
1545 t_bottom -= bob;
1546 t_top -= bob;
1547 }
1548 }
1549
1550 // vertex coordinates
1551 QRect display;
1552 if (filter.outputBuffer == kDefaultBuffer)
1553 display = videoRect;
1554 else
1555 display = frameBufferRect;
1556
1557 float vleft = display.left();
1558 float vright = display.right();
1559 float vtop = display.top();
1560 float vbot = display.bottom();
1561
1562 // resize for interactive tv
1563 if (videoResize && filter.outputBuffer == kDefaultBuffer)
1564 CalculateResize(&vleft, &vtop, &vright, &vbot);
1565
1566 if (invertVideo && (type == kYUV2RGB || type == kYUV2RGBA)
1567 || (type == kRESIZE && numfilters == 1))
1568 {
1569 float temp = vtop;
1570 vtop = vbot;
1571 vbot = temp;
1572 }
1573
1574 // bind correct frame buffer (default onscreen) and set viewport
1575 switch (filter.outputBuffer)
1576 {
1577 case kDefaultBuffer:
1578 if (frameBuffer)
1579 gl->m_glBindFramebufferEXT( GL_FRAMEBUFFER_EXT,
1580 frameBuffer);
1581 // clear the buffer
1582 if (viewportControl)
1583 {
1584 glClear(GL_COLOR_BUFFER_BIT);
1585 ViewPort(visibleRect.width(), visibleRect.height());
1586 }
1587 else
1588 ViewPort(masterViewportSize.width(),
1589 masterViewportSize.height());
1590 break;
1591 case kFrameBufferObject:
1592 gl->m_glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,
1593 filter.frameBuffers[0] );
1594 ViewPort(frameBufferRect.width(), frameBufferRect.height());
1595 break;
1596 case kNoBuffer:
1597 continue;
1598 }
1599
1600 // bind correct textures
1601 for (uint i = 0; i < inputs.size(); i++)
1602 {
1603 glActiveTexture( GL_TEXTURE0 + i );
1604 glBindTexture( gl->m_texture, inputs[i] );
1605 }
1606
1607 // enable fragment program and set any environment variables
1608 if (!(type == kNOFILT || type == kRESIZE))
1609 {
1610 glEnable ( GL_FRAGMENT_PROGRAM_ARB );
1611 gl->m_glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB,
1612 filter.fragmentProgram );
1613 float field = -line_height;
1614 switch (type)
1615 {
1616 case kYUV2RGB:
1617 case kYUV2RGBA:
1618 gl->m_glProgramEnvParameter4fARB (GL_FRAGMENT_PROGRAM_ARB,
1619 1, t_right / 2.0f, full_height, 0.0f, 0.0f);
1620 if (useColourControl)
1621 gl->m_glProgramEnvParameter4fARB(
1622 GL_FRAGMENT_PROGRAM_ARB, 0,
1623 (pictureAttribs[kPictureAttribute_Brightness] / 50 ) - 0.5,
1624 (pictureAttribs[kPictureAttribute_Contrast] / 50),
1625 (pictureAttribs[kPictureAttribute_Colour] / 50),
1626 0.0f);
1627 break;
1628 case kBOBDEINT:
1629 case kPROGONEFIELD:
1630 case kPROGKDEINT:
1631 case kPROGLINBLEND:
1632 if (scan == kScan_Intr2ndField)
1633 field *= -1;
1634 case kONEFIELD:
1635 case kKDEINT:
1636 case kLINBLEND:
1637 gl->m_glProgramEnvParameter4fARB ( GL_FRAGMENT_PROGRAM_ARB,
1638 0, line_height * 2, field, 0.0f, 0.0f);
1639 break;
1640 case kNOFILT:
1641 case kRESIZE:
1642 break;
1643 }
1644 }
1645
1646 // enable blending for osd
1647 if (type == kYUV2RGBA)
1648 glEnable(GL_BLEND);
1649
1650 // draw quad
1651 glBegin( GL_QUADS );
1652 glTexCoord2f(0.0f, t_top); glVertex2f(vleft, vtop);
1653 glTexCoord2f(t_right, t_top); glVertex2f(vright, vtop);
1654 glTexCoord2f(t_right, t_bottom); glVertex2f(vright, vbot);
1655 glTexCoord2f(0.0f, t_bottom); glVertex2f(vleft, vbot);
1656 glEnd();
1657
1658 // disable blending
1659 if (type == kYUV2RGBA)
1660 glDisable(GL_BLEND);
1661
1662 // disable fragment program
1663 if (!(type == kNOFILT || type == kRESIZE))
1664 {
1665 gl->m_glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, 0 );
1666 glDisable ( GL_FRAGMENT_PROGRAM_ARB );
1667 }
1668
1669 // switch back to default framebuffer
1670 if (filter.outputBuffer != kDefaultBuffer || frameBuffer)
1671 gl->m_glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
1672
1673 inputs = filter.frameBufferTextures;
1674 inputsize = videoSize;
1675 }
1676 currentFrameNum = frame;
1677 inputUpdated = false;
1678}
1679
1680void OpenglVideo::Rotate(vector<GLuint> *target)
1681{
1682 if (target->size() < 2)
1683 return;
1684 GLuint tmp = (*target)[target->size() - 1];
1685 for (uint i = target->size() - 1; i > 0; i--)
1686 (*target)[i] = (*target)[i-1];
1687 (*target)[0] = tmp;
1688}
1689
1690void OpenglVideo::SetPictureAttribute(int attributeType, int newValue)
1691{
1692 pictureAttribs[attributeType] = (float) newValue;
1693}
1694
1695void OpenglVideo::SetTextureFilters(vector<GLuint> *textures, int filt)
1696{
1697 if (textures->empty())
1698 return;
1699 for (uint i = 0; i < textures->size(); i++)
1700 gl->SetupTextureFilters(&((*textures)[i]), filt);
1701}
1702
1703OpenglFilter OpenglVideo::StringToFilter(QString filter)
1704{
1705 OpenglFilter ret = kNOFILT;
1706 if (filter.contains("master"))
1707 ret = kYUV2RGB;
1708 else if (filter.contains("osd"))
1709 ret = kYUV2RGBA;
1710 else if (filter.contains("openglkerneldeint"))
1711 ret = kKDEINT;
1712 else if (filter.contains("opengllinearblend"))
1713 ret = kLINBLEND;
1714 else if (filter.contains("openglonefield"))
1715 ret = kONEFIELD;
1716 else if (filter.contains("openglbobdeint"))
1717 ret = kBOBDEINT;
1718 else if (filter.contains("openglprogressivelinearblend"))
1719 ret = kPROGLINBLEND;
1720 else if (filter.contains("openglprogressivekerneldeint"))
1721 ret = kPROGKDEINT;
1722 else if (filter.contains("openglprogressiveonefield"))
1723 ret = kPROGONEFIELD;
1724 else if (filter.contains("resize"))
1725 ret = kRESIZE;
1726 return ret;
1727}
1728
1729QString OpenglVideo::FilterToString(OpenglFilter filt)
1730{
1731 switch (filt)
1732 {
1733 case kNOFILT:
1734 break;
1735 case kYUV2RGB:
1736 return "master";
1737 case kYUV2RGBA:
1738 return "osd";
1739 case kKDEINT:
1740 return "openglkerneldeint";
1741 case kLINBLEND:
1742 return "opengllinearblend";
1743 case kONEFIELD:
1744 return "openglonefield";
1745 case kBOBDEINT:
1746 return "openglbobdeint";
1747 case kPROGLINBLEND:
1748 return "openglprogressivelinearblend";
1749 case kPROGKDEINT:
1750 return "openglprogressivekerneldeint";
1751 case kPROGONEFIELD:
1752 return "openglprogressiveonefield";
1753 case kRESIZE:
1754 return "resize";
1755 }
1756 return "";
1757}
1758
1759static const QString yuv2rgb1a =
1760 "ATTRIB ytex = fragment.texcoord[0];"
1761 "PARAM off = program.env[1];"
1762 "TEMP res, tmp, tmp2;";
1763
1764static const QString yuv2rgb1b =
1765 "TEMP alpha;"
1766 "TEX alpha, ytex, texture[1], %1;";
1767
1768static const QString yuv2rgb1c =
1769 "TEX res, ytex, texture[0], %1;"
1770 "MAD tmp2, ytex, {0.5, 0.5}, off.wyww;"
1771 "TEX tmp.x, tmp2, texture[0], %1;"
1772 "ADD tmp2, tmp2, off.xwww;"
1773 "TEX tmp.y, tmp2, texture[0], %1;";
1774
1775static const QString yuv2rgb2 =
1776 "PARAM adj = program.env[0];"
1777 "SUB res, res, 0.5;"
1778 "MAD res, res, adj.yyyy, adj.xxxx;"
1779 "SUB tmp, tmp, { 0.5, 0.5 };"
1780 "MAD tmp, adj.zzzz, tmp, 0.5;";
1781
1782static const QString yuv2rgb3 =
1783 "MAD res, res, 1.164, -0.063;"
1784 "SUB tmp, tmp, { 0.5, 0.5 };"
1785 "MAD res, { 0, -.392, 2.017 }, tmp.xxxw, res;";
1786
1787static const QString yuv2rgb4 =
1788 "MAD result.color, { 1.596, -.813, 0, 0 }, tmp.yyyw, res;";
1789
1790static const QString yuv2rgb5 =
1791 "MAD result.color, { 0, -.813, 1.596, 0 }, tmp.yyyw, res.bgra;";
1792
1793static const QString yuv2rgb6 =
1794 "MOV result.color.a, alpha.a;";
1795
1796
1797QString OpenglVideo::GetProgramString(OpenglFilter name)
1798{
1799 QString ret =
1800 "!!ARBfp1.0\n"
1801 "OPTION ARB_precision_hint_fastest;";
1802 switch (name)
1803 {
1804 case kYUV2RGB:
1805 ret = ret + yuv2rgb1a + yuv2rgb1c;
1806 if (useColourControl)
1807 ret += yuv2rgb2;
1808 ret += yuv2rgb3;
1809 ret += frameBuffer ? yuv2rgb5 : yuv2rgb4;
1810 break;
1811 case kYUV2RGBA:
1812 ret = ret + yuv2rgb1a + yuv2rgb1b + yuv2rgb1c;
1813 if (useColourControl)
1814 ret += yuv2rgb2;
1815 ret = ret + yuv2rgb3 + yuv2rgb4 + yuv2rgb6;
1816 break;
1817 case kKDEINT:
1818 ret +=
1819 "ATTRIB tex = fragment.texcoord[0];"
1820 "PARAM off = program.env[0];"
1821 "TEMP sam, pos, cum, cur, field, mov;"
1822 "RCP field, off.x;"
1823 "MUL field, tex.yyyy, field;"
1824 "FRC field, field;"
1825 "SUB field, field, 0.5;"
1826 "TEX sam, tex, texture[1], %1;"
1827 "TEX cur, tex, texture[0], %1;"
1828 "SUB mov, cur, sam;"
1829 "MUL cum, sam, 0.125;"
1830 "MAD cum, cur, 0.125, cum;"
1831 "ABS mov, mov;"
1832 "SUB mov, mov, 0.12;"
1833 "ADD pos, tex, off.wyww;"
1834 "TEX sam, pos, texture[0], %1;"
1835 "MAD cum, sam, 0.5, cum;"
1836 "SUB pos, tex, off.wyww;"
1837 "TEX sam, pos, texture[0], %1;"
1838 "MAD cum, sam, 0.5, cum;"
1839 "MAD pos, off.wyww, 2.0, tex;"
1840 "TEX sam, pos, texture[0], %1;"
1841 "MAD cum, sam, -0.0625, cum;"
1842 "TEX sam, pos, texture[1], %1;"
1843 "MAD cum, sam, -0.0625, cum;"
1844 "MAD pos, off.wyww, -2.0, tex;"
1845 "TEX sam, pos, texture[0], %1;"
1846 "MAD cum, sam, -0.0625, cum;"
1847 "TEX sam, pos, texture[1], %1;"
1848 "MAD cum, sam, -0.0625, cum;"
1849 "CMP cum, mov, cur, cum;"
1850 "CMP result.color, field, cum, cur;";
1851 break;
1852 case kPROGLINBLEND:
1853 ret +=
1854 "ATTRIB tex = fragment.texcoord[0];"
1855 "PARAM off = program.env[0];"
1856 "TEMP field, top, bot, current, previous, next, other, mov;"
1857 "TEX next, tex, texture[0], %1;"
1858 "TEX current, tex, texture[1], %1;"
1859 "TEX previous, tex, texture[2], %1;"
1860 "ADD top, tex, off.wyww;"
1861 "TEX other, top, texture[1], %1;"
1862 "SUB top, tex, off.wyww;"
1863 "TEX bot, top, texture[1], %1;"
1864 "LRP other, 0.5, other, bot;"
1865 "RCP field, off.x;"
1866 "MUL field, tex.yyyy, field;"
1867 "FRC field, field;"
1868 "SUB field, field, 0.5;"
1869 "SUB top, current, next;"
1870 "SUB bot, current, previous;"
1871 "CMP mov, field, bot, top;"
1872 "ABS mov, mov;"
1873 "SUB mov, mov, 0.12;"
1874 "CMP other, mov, current, other;"
1875 "CMP top, field, other, current;"
1876 "CMP bot, field, current, other;"
1877 "CMP result.color, off.y, top, bot;";
1878 break;
1879 case kPROGONEFIELD:
1880 ret +=
1881 "ATTRIB tex = fragment.texcoord[0];"
1882 "PARAM off = program.env[0];"
1883 "TEMP field, top, bot, current, previous, next, other, mov;"
1884 "TEX next, tex, texture[0], %1;"
1885 "TEX current, tex, texture[1], %1;"
1886 "TEX previous, tex, texture[2], %1;"
1887 "ADD top, tex, off.wyww;"
1888 "TEX other, top, texture[1], %1;"
1889 "RCP field, off.x;"
1890 "MUL field, tex.yyyy, field;"
1891 "FRC field, field;"
1892 "SUB field, field, 0.5;"
1893 "SUB top, current, next;"
1894 "SUB bot, current, previous;"
1895 "CMP mov, field, bot, top;"
1896 "ABS mov, mov;"
1897 "SUB mov, mov, 0.12;"
1898 "CMP other, mov, current, other;"
1899 "CMP top, field, other, current;"
1900 "CMP bot, field, current, other;"
1901 "CMP result.color, off.y, top, bot;";
1902 break;
1903 case kPROGKDEINT:
1904 ret +=
1905 "ATTRIB tex = fragment.texcoord[0];"
1906 "PARAM off = program.env[0];"
1907 "TEMP sam, pos, bot, top, cur, pre, nex, field, mov;"
1908 "RCP field, off.x;"
1909 "MUL field, tex.yyyy, field;"
1910 "FRC field, field;"
1911 "SUB field, field, 0.5;"
1912 "TEX pre, tex, texture[2], %1;" // -1,0
1913 "TEX cur, tex, texture[1], %1;" // 0,0
1914 "TEX nex, tex, texture[0], %1;" // +1,0
1915 "SUB top, nex, cur;"
1916 "SUB bot, pre, cur;"
1917 "CMP mov, field, bot, top;"
1918 "ABS mov, mov;"
1919 "SUB mov, mov, 0.12;"
1920 "MUL bot, pre, 0.125;" // BOT -1,0
1921 "MAD bot, cur, 0.125, bot;" // BOT +1,0
1922 "MUL top, cur, 0.125;" // TOP -1,0
1923 "MAD top, nex, 0.125, top;" // TOP +1,0
1924 "ADD pos, tex, off.wyww;"
1925 "TEX sam, pos, texture[1], %1;" // 0,+1
1926 "MAD bot, sam, 0.5, bot;" // BOT 0,+1
1927 "MAD top, sam, 0.5, top;" // TOP 0,+1
1928 "SUB pos, tex, off.wyww;"
1929 "TEX sam, pos, texture[1], %1;" // 0,-1
1930 "MAD bot, sam, 0.5, bot;" // BOT 0,-1
1931 "MAD top, sam, 0.5, top;" // TOP 0,-1
1932 "MAD pos, off.wyww, 2.0, tex;"
1933 "TEX sam, pos, texture[1], %1;" // 0,+2
1934 "MAD bot, sam, -0.0625, bot;" // BOT +1,+2
1935 "MAD top, sam, -0.0625, top;" // TOP -1,+2
1936 "TEX sam, pos, texture[2], %1;" // -1,+2
1937 "MAD bot, sam, -0.0625, bot;" // BOT -1,+2
1938 "TEX sam, pos, texture[0], %1;" // +1,+2
1939 "MAD top, sam, -0.0625, top;" // TOP +1,+2
1940 "MAD pos, off.wyww, -2.0, tex;"
1941 "TEX sam, pos, texture[1], %1;" // +1,-2
1942 "MAD bot, sam, -0.0625, bot;" // BOT +1,-2
1943 "MAD top, sam, -0.0625, top;" // TOP -1,-2
1944 "TEX sam, pos, texture[2], %1;" // -1, -2 row
1945 "MAD bot, sam, -0.0625, bot;" // BOT -1,-2
1946 "TEX sam, pos, texture[0], %1;" // +1,-2
1947 "MAD top, sam, -0.0625, top;" // TOP +1,-2
1948 "CMP top, mov, cur, top;"
1949 "CMP bot, mov, cur, bot;"
1950 "CMP top, field, top, cur;"
1951 "CMP bot, field, cur, bot;"
1952 "CMP result.color, off.y, top, bot;";
1953 break;
1954 case kBOBDEINT:
1955 case kONEFIELD:
1956 ret +=
1957 "ATTRIB tex = fragment.texcoord[0];"
1958 "PARAM off = program.env[0];"
1959 "TEMP field, top, bottom, current, other;"
1960 "TEX current, tex, texture[0], %1;"
1961 "RCP field, off.x;"
1962 "MUL field, tex.yyyy, field;"
1963 "FRC field, field;"
1964 "SUB field, field, 0.5;"
1965 "ADD top, tex, off.wyww;"
1966 "TEX other, top, texture[0], %1;"
1967 "CMP top, field, other, current;"
1968 "CMP bottom, field, current, other;"
1969 "CMP result.color, off.y, top, bottom;";
1970 break;
1971 case kLINBLEND:
1972 ret +=
1973 "ATTRIB tex = fragment.texcoord[0];"
1974 "PARAM off = program.env[0];"
1975 "TEMP mov, field, cur, pre, pos;"
1976 "RCP field, off.x;"
1977 "MUL field, tex.yyyy, field;"
1978 "FRC field, field;"
1979 "SUB field, field, 0.5;"
1980 "TEX cur, tex, texture[0], %1;"
1981 "TEX pre, tex, texture[1], %1;"
1982 "SUB mov, cur, pre;"
1983 "ABS mov, mov;"
1984 "SUB mov, mov, 0.12;"
1985 "ADD pos, tex, off.wyww;"
1986 "TEX pre, pos, texture[0], %1;"
1987 "SUB pos, tex, off.wyww;"
1988 "TEX pos, pos, texture[0], %1;"
1989 "LRP pre, 0.5, pos, pre;"
1990 "CMP pre, field, pre, cur;"
1991 "CMP result.color, mov, cur, pre;";
1992 break;
1993 case kNOFILT:
1994 case kRESIZE:
1995 break;
1996 default:
1997 VERBOSE(VB_PLAYBACK, LOC_ERR + "Unknown fragment program.");
1998 }
1999 return ret + "END";
2000}
2001