Ticket #2649: videoout_opengl.2.cpp

File videoout_opengl.2.cpp, 64.5 KB (added by anonymous, 17 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