#include <cmath>

using namespace std;

// MythTV videout headers
#include "videoout_opengl.h"
#include "osd.h"
#include "osdsurface.h"
#include "util-opengl.h"
#include "util-x11.h"
#include "videodisplayprofile.h"

// MythTV General headers
#include "../libavcodec/avcodec.h"
#include "mythconfig.h"
#include "mythcontext.h"
#include "filtermanager.h"
#include "NuppelVideoPlayer.h"
#define IGNORE_TV_PLAY_REC
#include "tv.h"

extern "C" {
#define XMD_H 1
#include <X11/extensions/xf86vmode.h>
#include <X11/extensions/Xinerama.h>
}

#define LOC QString("VideoOutputOpengl: ")
#define LOC_ERR QString("VideoOutputOpengl Error: ")

VideoOutputOpengl::VideoOutputOpengl()
    : VideoOutput(),
      display_res(NULL), global_lock(true),
      gl(new OpenglContext), videochain(NULL), pipchain(NULL), osdchain(NULL),
      render_onscreen(true),
      gl_osd(false), actually_draw_pip(false), argb_frame(NULL)
{
    bzero(&av_pause_frame, sizeof(av_pause_frame));
    use_colourcontrols  = gContext->GetNumSetting(
                            "UseOutputPictureControls", 0);
    allowpreviewepg = false;
}

VideoOutputOpengl::~VideoOutputOpengl()
{
    X11L;
    if (videochain)
        delete videochain;
    if (pipchain)
        delete pipchain;
    if (osdchain)
        delete osdchain;
    if (gl)
        delete gl;
    X11U;
    if (argb_frame)
        delete argb_frame;
    DeleteBuffers(true);
    if (display_res)
        display_res->SwitchToGUI();
}

bool VideoOutputOpengl::Init(int width, int height, float aspect, 
                         WId winid, int winx, int winy, int winw, 
                         int winh, WId embedid)
{
    if (piptype > kPIPOn)
    {
        VERBOSE(VB_PLAYBACK, LOC + "Use NullVideo for Opengl PiP.");
        return false;
    }

    if (gContext->GetNumSetting("UseVideoModes", 0) &&
                                 render_onscreen && !piptype)
        display_res = DisplayRes::GetDisplayRes();

    if (winid <= 0)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Invalid Window ID.");
        return false;
    }
    VideoOutput::Init(width, height, aspect,
                      winid, winx, winy, winw, winh,
                      embedid);
    if (!gl->Create(winid, display_visible_rect.width(),
                    display_visible_rect.height(),
                    render_onscreen))
        return false;
    X11S(gl->Init());
    InitDisplayMeasurements(width, height);
    MoveResize();

    X11L;
    gl->MakeCurrent(true);
    videochain = new OpenglVideo();
    bool success = videochain->Init(gl, use_colourcontrols,
                   render_onscreen, video_dim,
                   display_visible_rect,
                   display_video_rect, true);
    gl->MakeCurrent(false);
    X11U;
    if (!success)
        return false;
    if (!InitSetupBuffers())
        return false;

    db_vdisp_profile->SetVideoRenderer("opengl");
    if (use_colourcontrols)
        InitPictureAttributes();

    if (db_vdisp_profile->GetOSDRenderer() == "opengl")
        gl_osd = true;

    if (gl_osd && render_onscreen && !piptype)
    {
        X11L;
        gl->MakeCurrent(true);
        osdchain = new OpenglVideo();
        if (!osdchain->Init(gl, use_colourcontrols,
                      render_onscreen, QSize(display_visible_rect.width(),
                      display_visible_rect.height()),
                      display_visible_rect,
                      display_visible_rect, false, true))
        {
            delete osdchain;
            osdchain = NULL;
            gl_osd = false;
        }
        gl->MakeCurrent(false);
        X11U;
    }
    return true;
}

bool VideoOutputOpengl::SetDeinterlacingEnabled(bool enable)
{
    if (!videochain)
        return false;

    if (enable && m_deinterlacing)
        return m_deinterlacing;

    if (enable)
    {
        if (m_deintfiltername == "")
            return SetupDeinterlace(enable);
        if (m_deintfiltername.contains("opengl"))
        {
            if (videochain->GetDeinterlacer() == "")
                return SetupDeinterlace(enable);
        }
        else if (!m_deintfiltername.contains("opengl"))
        {
            // make sure opengl deinterlacing is disabled
            X11S(videochain->SetDeinterlacing(false));
            if (!m_deintFiltMan || !m_deintFilter)
                return VideoOutput::SetupDeinterlace(enable);
        }
    }

    if (videochain)
        X11S(videochain->SetDeinterlacing(enable));
    m_deinterlacing = enable;
    return m_deinterlacing;
}

bool VideoOutputOpengl::SetupDeinterlace(bool interlaced, 
                                   const QString& overridefilter)
{
    m_deintfiltername = db_vdisp_profile->GetFilteredDeint(overridefilter);

    if (!m_deintfiltername.contains("opengl"))
    {
        X11S(videochain->SetDeinterlacing(false));
        videochain->SetSoftwareDeinterlacer("");
        VideoOutput::SetupDeinterlace(interlaced, overridefilter);
        if (m_deinterlacing)
            videochain->SetSoftwareDeinterlacer(m_deintfiltername);
        return m_deinterlacing;
    }

    // clear any non opengl filters
    if (m_deintFiltMan)
    {
        delete m_deintFiltMan;
        m_deintFiltMan = NULL;
    }
    if (m_deintFilter)
    {
        delete m_deintFilter;
        m_deintFilter = NULL;
    }

    if (m_deinterlacing == interlaced)
        return m_deinterlacing;
    m_deinterlacing = interlaced;

    if (!videochain)
        return false;

    if (m_deinterlacing && !m_deintfiltername.isEmpty()) 
    {
        if (videochain->GetDeinterlacer() != m_deintfiltername)
        {
            X11L;
            if (!videochain->AddDeinterlacer(m_deintfiltername))
            {
                VERBOSE(VB_IMPORTANT, LOC +
                        QString("Couldn't load deinterlace filter %1")
                        .arg(m_deintfiltername));
                m_deinterlacing = false;
                m_deintfiltername = "";
            }
            else
            {
                VERBOSE(VB_PLAYBACK, LOC +
                        QString("Using deinterlace method %1")
                   .arg(m_deintfiltername));
            }
            X11U;
        }
    }
    X11S(videochain->SetDeinterlacing(m_deinterlacing));
    return m_deinterlacing;
}

void VideoOutputOpengl::GetOSDBounds(QRect &total, QRect &visible,
                               float &visible_aspect,
                               float &font_scaling) const
{
    if (gl_osd)
    {
        total   = display_visible_rect;
        visible = display_visible_rect;
        visible_aspect = (float) display_aspect;
        font_scaling = 1.0f;
        return;
    }
    VideoOutput::GetOSDBounds(total, visible, visible_aspect, font_scaling);
}

void VideoOutputOpengl::PrepareFrame(VideoFrame *buffer, FrameScanType t)
{
    if (!buffer)
        buffer = vbuffers.GetScratchFrame();

    framesPlayed = buffer->frameNumber + 1;

    // TODO should cope with YUV422P, rgb24, argb32 etc
    if (buffer->codec != FMT_YV12)
        return;

    X11L;
    gl->MakeCurrent(true);
    videochain->Show(t, m_deinterlacing, framesPlayed);

    if (actually_draw_pip && render_onscreen && pipchain)
        pipchain->Show(t, m_deinterlacing, framesPlayed);

    if (osdchain && render_onscreen && actually_draw_osd)
        osdchain->Show(t, m_deinterlacing, framesPlayed);
    glFlush();
    gl->MakeCurrent(false);
    X11U;
}

void VideoOutputOpengl::ShowPip(VideoFrame *frame,
                                NuppelVideoPlayer *pipplayer)
{
    (void) frame;
    actually_draw_pip = false;
    if (!pipplayer)
        return;

    int pipw, piph;
    VideoFrame *pipimage = pipplayer->GetCurrentFrame(pipw, piph);
    float pipVideoAspect = pipplayer->GetVideoAspect();
    uint  pipVideoWidth  = pipplayer->GetVideoWidth();
    uint  pipVideoHeight = pipplayer->GetVideoHeight();

    // If PiP is not initialized to values we like, silently ignore the frame.
    if ((pipVideoAspect <= 0) || !pipimage || 
        !pipimage->buf || pipimage->codec != FMT_YV12)
    {
        pipplayer->ReleaseCurrentFrame(pipimage);
        return;
    }

    QRect position = GetPIPRect(db_pip_location, pipplayer);

    if (!pipchain)
    {
        VERBOSE(VB_PLAYBACK, LOC + "Initialise PiP.");
        pipchain = new OpenglVideo();
        bool success = pipchain->Init(gl, use_colourcontrols,
                     true, QSize(pipVideoWidth, pipVideoHeight),
                     position,
                     position, false);
        success &= pipchain->AddDeinterlacer("openglonefield");
        pipchain->SetMasterViewport(videochain->GetViewPort());
        if (!success)
        {
            pipplayer->ReleaseCurrentFrame(pipimage);
            return;
        }
    }
    QSize current = pipchain->GetVideoSize();
    if ((uint)current.width()  != pipVideoWidth ||
        (uint)current.height() != pipVideoHeight)
    {
        VERBOSE(VB_PLAYBACK, LOC + "Re-initialise PiP.");
        bool success =pipchain->ReInit(gl, use_colourcontrols,
                      true, QSize(pipVideoWidth, pipVideoHeight),
                      position, position, false);
        pipchain->SetMasterViewport(videochain->GetViewPort());
        if (!success)
        {
            pipplayer->ReleaseCurrentFrame(pipimage);
            return;
        }

    }
    pipchain->SetVideoRect(position);
    pipchain->UpdateInputFrame(pipimage);
    actually_draw_pip = true;
    pipplayer->ReleaseCurrentFrame(pipimage);
}

QRect VideoOutputOpengl::GetPIPRect(int location, NuppelVideoPlayer *pipplayer)
{
    if (!pipplayer || pipplayer == NULL)
        return VideoOutput::GetPIPRect(location, pipplayer);

    QRect position;
    float pipVideoAspect = pipplayer ? 
                          (float)pipplayer->GetVideoAspect() : display_aspect;
    int tmph = (display_visible_rect.height() * db_pip_size) / 100;
    float pixel_adj = ((float)display_visible_rect.width() / 
                       (float)display_visible_rect.height()) / display_aspect;
    position.setHeight(tmph);
    position.setWidth((int)(tmph * pipVideoAspect * pixel_adj));

    int xoff = (int)(display_visible_rect.width() * 0.06);
    int yoff = (int)(display_visible_rect.height() * 0.06);
    switch (location)
    {
        default:
        case kPIPTopLeft:
                yoff = display_visible_rect.height()
                       - position.height() - yoff;
                break;
        case kPIPBottomLeft:
                break;
        case kPIPTopRight:
                xoff = display_visible_rect.width()
                       - position.width() - xoff;
                yoff = display_visible_rect.height()
                       - position.height() - yoff;
                break;
        case kPIPBottomRight:
                xoff = display_visible_rect.width()
                       - position.width() - xoff;
                break;
    }
    position.moveBy(xoff, yoff);
    return position;
}

void VideoOutputOpengl::Show(FrameScanType )
{
    X11L;
    gl->SwapBuffers();
    X11U;
}

unsigned char* VideoOutputOpengl::GetARGBFrame(QSize &size)
{
    // TODO
    (void) size;
    return NULL;
}

void VideoOutputOpengl::DrawUnusedRects(bool sync)
{
    (void) sync;
}

void VideoOutputOpengl::Zoom(int direction)
{
    VideoOutput::Zoom(direction);
    MoveResize();
}

void VideoOutputOpengl::InputChanged(int width, int height,
                                  float aspect, MythCodecID av_codec_id)
{
    VERBOSE(VB_PLAYBACK, LOC + "InputChanged");
    QRect current_visible = display_visible_rect;
    VideoOutput::InputChanged(width, height, aspect, av_codec_id);
    ResizeForVideo((uint) width, (uint) height);
    MoveResize();

    if (videochain)
    {
        QSize size = videochain->GetVideoSize();
        if (width  != size.width() ||
            height != size.height())
        {
            X11L;
            videochain->ReInit(gl, use_colourcontrols,
                        render_onscreen, video_dim,
                        display_visible_rect,
                        display_video_rect, true);
            X11U;
            if (pipchain && pipchain != NULL)
                pipchain->SetMasterViewport(videochain->GetViewPort());
            CreateBuffers();
            CreatePauseFrame();
        }
    }
}

void VideoOutputOpengl::MoveResize(void)
{
    VideoOutput::MoveResize();
    if (videochain)
        videochain->SetVideoRect(display_video_rect);
}

bool VideoOutputOpengl::InitSetupBuffers(void)
{
    vbuffers.Init(31, true, 1, 12, 4, 2, false);
    CreateBuffers();
    CreatePauseFrame();
    return true;
}

void VideoOutputOpengl::CreateBuffers(void)
{
    if (videochain)
    {
        QSize size = videochain->GetVideoSize();
        if (vbuffers.CreateBuffers(size.width(), size.height()))
            return;
    }
    VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to create buffers");
}

void VideoOutputOpengl::CreatePauseFrame(void)
{
    vbuffers.LockFrame(&av_pause_frame, "CreatePauseFrame");
    if (av_pause_frame.buf)
    {
        delete [] av_pause_frame.buf;
        av_pause_frame.buf = NULL;
    }
    av_pause_frame.height       = vbuffers.GetScratchFrame()->height;
    av_pause_frame.width        = vbuffers.GetScratchFrame()->width;
    av_pause_frame.bpp          = vbuffers.GetScratchFrame()->bpp;
    av_pause_frame.size         = vbuffers.GetScratchFrame()->size;
    av_pause_frame.frameNumber  = vbuffers.GetScratchFrame()->frameNumber;
    av_pause_frame.buf          = new unsigned char[av_pause_frame.size];
    av_pause_frame.qscale_table = NULL;
    av_pause_frame.qstride      = 0;

    vbuffers.UnlockFrame(&av_pause_frame, "CreatePauseFrame");
}

void VideoOutputOpengl::DeleteBuffers(bool delete_pause_frame)
{
    DiscardFrames(true);
    vbuffers.DeleteBuffers();
    if (delete_pause_frame)
    {
        if (av_pause_frame.buf)
        {
            delete [] av_pause_frame.buf;
            av_pause_frame.buf = NULL;
        }
        if (av_pause_frame.qscale_table)
        {
            delete [] av_pause_frame.qscale_table;
            av_pause_frame.qscale_table = NULL;
        }
    }
}

void VideoOutputOpengl::EmbedInWidget(WId wid, int x, int y, int w, int h)
{
    (void) wid;
    (void) x;
    (void) y;
    (void) w;
    (void) h;
}

void VideoOutputOpengl::StopEmbedding(void)
{
}

float VideoOutputOpengl::GetDisplayAspect(void)
{
    return display_aspect;
}

void VideoOutputOpengl::UpdatePauseFrame(void)
{
    vbuffers.LockFrame(&av_pause_frame, "UpdatePauseFrame -- pause");

    vbuffers.begin_lock(kVideoBuffer_used);
    VideoFrame *used_frame = NULL;
    if (vbuffers.size(kVideoBuffer_used) > 0)
    {
        used_frame = vbuffers.head(kVideoBuffer_used);
        if (!vbuffers.TryLockFrame(used_frame, "UpdatePauseFrame -- used"))
            used_frame = NULL;
    }
    if (used_frame)
    {
        CopyFrame(&av_pause_frame, used_frame);
        vbuffers.UnlockFrame(used_frame, "UpdatePauseFrame -- used");
    }
    vbuffers.end_lock();
    if (!used_frame &&
        vbuffers.TryLockFrame(vbuffers.GetScratchFrame(),
                              "UpdatePauseFrame -- scratch"))
    {
        vbuffers.GetScratchFrame()->frameNumber = framesPlayed - 1;
        CopyFrame(&av_pause_frame, vbuffers.GetScratchFrame());
        vbuffers.UnlockFrame(vbuffers.GetScratchFrame(),
                             "UpdatePauseFrame -- scratch");
    }
    vbuffers.UnlockFrame(&av_pause_frame, "UpdatePauseFrame - used");
}

void VideoOutputOpengl::ProcessFrame(VideoFrame *frame, OSD *osd,
                                 FilterChain *filterList,
                                 NuppelVideoPlayer *pipPlayer)
{
    bool pauseframe = false;
    if (!frame)
    {
        frame = vbuffers.GetScratchFrame();
        CopyFrame(vbuffers.GetScratchFrame(), &av_pause_frame);
        pauseframe = true;
    }

    // disable image processing for offscreen rendering
    X11L;
    gl->MakeCurrent(true);
    if (render_onscreen)
    {
        if (filterList)
            filterList->ProcessFrame(frame);
        if (m_deinterlacing && m_deintFilter != NULL &&
            m_deinterlaceBeforeOSD &&
            !pauseframe)
        {
            m_deintFilter->ProcessFrame(frame);
        }
        ShowPip(frame, pipPlayer);
        gl_osd ? DisplayOSD(frame, osd) : VideoOutput::DisplayOSD(frame, osd);

        if (m_deinterlacing && m_deintFilter != NULL &&
            !m_deinterlaceBeforeOSD &&
            !pauseframe)
        {
            m_deintFilter->ProcessFrame(frame);
        }
    }
    if (videochain)
        videochain->UpdateInputFrame(frame);
    gl->MakeCurrent(false);
    X11U;
}

int VideoOutputOpengl::DisplayOSD(VideoFrame *frame, OSD *osd,
                                  int stride, int revision)
{
    (void) stride;
    (void) frame;
    actually_draw_osd = false;
    if (!osd || !osdchain)
        return -1;

    if (vsz_enabled && videochain)
        videochain->SetVideoResize(&vsz_desired_display_rect);

    OSDSurface *surface = osd->Display();
    if (!surface)
        return -1;

    actually_draw_osd = true;
    bool changed = (-1 == revision) ?
        surface->Changed() : (surface->GetRevision()!=revision);

    if (changed)
    {
        QSize visible(display_visible_rect.width(),
                      display_visible_rect.height());
        osdchain->UpdateInput(surface->yuvbuffer, 0, FMT_YV12, visible);
        osdchain->UpdateInput(surface->alpha, 1, FMT_ALPHA, visible);
    }
    return changed;
}

void VideoOutputOpengl::ShutdownVideoResize(void)
{
    if (!osdchain)
    {
        VideoOutput::ShutdownVideoResize();
        return;
    }
    if (videochain)
        videochain->SetVideoResize(NULL);
    vsz_enabled      = false;
}

int VideoOutputOpengl::SetPictureAttribute(int attributeType, int newValue)
{
    if (kPictureAttribute_Hue == attributeType)
        return -1;
    SetPictureAttributeDBValue(attributeType, newValue);
    if (videochain && use_colourcontrols)
        videochain->SetPictureAttribute(attributeType, newValue);
    return newValue;
}

int VideoOutputOpengl::GetRefreshRate(void)
{
    XF86VidModeModeLine mode_line;
    int dot_clock;

    int ret = False;
    X11S(ret = XF86VidModeGetModeLine(gl->GetDisplay(), gl->GetScreenNum(),
                                      &dot_clock, &mode_line));
    if (!ret)
    {
        VERBOSE(VB_IMPORTANT, LOC_ERR + "GetRefreshRate(): "
                "X11 ModeLine query failed");
        return -1;
    }

    double rate = (double)((double)(dot_clock * 1000.0) /
                           (double)(mode_line.htotal * mode_line.vtotal));

    // Assume 60Hz if we can't otherwise determine it.
    if (rate == 0)
        rate = 60;

    if (rate < 20 || rate > 200)
    {
        VERBOSE(VB_PLAYBACK, LOC + QString("Unreasonable refresh rate %1Hz "
                                           "reported by X").arg(rate));
        rate = 60;
    }

    rate = 1000000.0 / rate;

    return (int)rate;
}
void VideoOutputOpengl::ResizeForGui(void)
{
    if (display_res)
        display_res->SwitchToGUI();
}

void VideoOutputOpengl::ResizeForVideo(uint width, uint height)
{
    if ((width == 1920 || width == 1440) && height == 1088)
        height = 1080; // ATSC 1920x1080

    if (display_res && display_res->SwitchToVideo(width, height))
    {
        // Switching to custom display resolution succeeded
        // Make a note of the new size
        display_dim = QSize(display_res->GetPhysicalWidth(),
                            display_res->GetPhysicalHeight());
        display_aspect = display_res->GetAspectRatio();

        bool fullscreen = !gContext->GetNumSetting("GuiSizeForTV", 0);

        // if width && height are zero users expect fullscreen playback
        if (!fullscreen)
        {
            int gui_width = 0, gui_height = 0;
            gContext->GetResolutionSetting("Gui", gui_width, gui_height);
            fullscreen |= (0 == gui_width && 0 == gui_height);
        }

        if (fullscreen)
        {
            QSize sz(display_res->GetWidth(), display_res->GetHeight());
            display_visible_rect = QRect(QPoint(0,0), sz);
            // Resize X window to fill new resolution
            X11S(XMoveResizeWindow(gl->GetDisplay(), gl->GetWindow(),
                                   display_visible_rect.left(),
                                   display_visible_rect.top(),
                                   display_visible_rect.width(),
                                   display_visible_rect.height()));
        }
    }
}

void VideoOutputOpengl::ResizeForVideo(void)
{
    if (videochain)
    {
        QSize size = videochain->GetVideoSize();
        ResizeForVideo(size.width(), size.height());
    }
}

void VideoOutputOpengl::InitDisplayMeasurements(uint width, uint height)
{
    if (display_res)
    {
        // The very first Resize needs to be the maximum possible
        // desired res, because X will mask off anything outside
        // the initial dimensions
        X11S(XMoveResizeWindow(gl->GetDisplay(), gl->GetWindow(), 0, 0,
                               display_res->GetMaxWidth(),
                               display_res->GetMaxHeight()));
        ResizeForVideo(width, height);
    }
    else
    {
        display_dim = QSize(DisplayWidthMM(gl->GetDisplay(),
                            gl->GetScreenNum()),
                            DisplayHeightMM(gl->GetDisplay(),
                            gl->GetScreenNum()));

        if (db_display_dim.width() > 0 && db_display_dim.height() > 0)
            display_dim = db_display_dim;
    }

    // Fetch pixel width and height of the display
    int xbase, ybase, w, h;
    gContext->GetScreenBounds(xbase, ybase, w, h);
    // Determine window dimensions in pixels
    int window_w = w, window_h = h;
    if (gContext->GetNumSetting("GuiSizeForTV", 0))
        gContext->GetResolutionSetting("Gui", window_w,  window_h);
    else
        gContext->GetScreenBounds(xbase, ybase, window_w, window_h);
    window_w = (window_w) ? window_w : w;
    window_h = (window_h) ? window_h : h;
    float pixel_aspect = ((float)w) / ((float)h);

    VERBOSE(VB_PLAYBACK, LOC + QString(
                "Pixel dimensions: Screen %1x%2, window %3x%4")
            .arg(w).arg(h).arg(window_w).arg(window_h));

    // Determine if we are using Xinerama
    int event_base, error_base;
    bool usingXinerama = false;
    X11S(usingXinerama = 
         (XineramaQueryExtension(gl->GetDisplay(), &event_base, &error_base) &&
          XineramaIsActive(gl->GetDisplay())));

    // If the dimensions are invalid, assume square pixels and 17" screen.
    // Only print warning if this isn't Xinerama, we will fix Xinerama later.
    if (((display_dim.width() <= 0) || (display_dim.height() <= 0)) &&
        !usingXinerama)
    {
        VERBOSE(VB_GENERAL, LOC + "Physical size of display unknown."
                "\n\t\t\tAssuming 17\" monitor with square pixels.");
        display_dim.setHeight(300);
        display_dim.setWidth((int) round(300 * pixel_aspect));
    }

    // If we are using Xinerama the display dimensions can not be trusted.
    // We need to use the Xinerama monitor aspect ratio from the DB to set
    // the physical screen width. This assumes the height is correct, which
    // is more or less true in the typical side-by-side monitor setup.
    if (usingXinerama)
    {
        float displayAspect = gContext->GetFloatSettingOnHost(
            "XineramaMonitorAspectRatio",
            gContext->GetHostName(), pixel_aspect);
        int height = display_dim.height();
        if (height <= 0)
            display_dim.setHeight(height = 300);
        display_dim.setWidth((int) round(height * displayAspect));
    }

    VERBOSE(VB_PLAYBACK, LOC +
            QString("Estimated display dimensions: %1x%2 mm  Aspect: %3")
            .arg(display_dim.width()).arg(display_dim.height())
            .arg(((float) display_dim.width()) / display_dim.height()));

    // We must now scale the display measurements to our window size.
    // If we are running fullscreen this is a no-op.
    display_dim = QSize((display_dim.width()  * window_w) / w,
                        (display_dim.height() * window_h) / h);

    // Now that we know the physical monitor size, we can
    // calculate the display aspect ratio pretty simply...
    display_aspect = ((float)display_dim.width()) / display_dim.height();

    // If we are using XRandR, use the aspect ratio from it instead...
    if (display_res)
        display_aspect = display_res->GetAspectRatio();

    VERBOSE(VB_PLAYBACK, LOC +
            QString("Estimated window dimensions: %1x%2 mm  Aspect: %3")
            .arg(display_dim.width()).arg(display_dim.height())
            .arg(display_aspect));
}

#undef LOC
#undef LOC_ERR

#define LOC QString("OpenglVid: ")
#define LOC_ERR QString("OpenglVid Error: ")

QMap<int,float> OpenglVideo::pictureAttribs;

OpenglVideo::OpenglVideo() :
    convertBuf(NULL)
{
}

OpenglVideo::~OpenglVideo()
{
    Cleanup();
}

void OpenglVideo::Cleanup()
{
    ShutDownYUV2RGB();
    gl->MakeCurrent(true);
    if (frameBuffer)
        gl->DeleteFrameBuffer(&frameBuffer);
    if (frameBufferTexture)
        gl->DeleteTexture(&frameBufferTexture);

    for (uint i = 0; i < inputTextures.size(); i++)
         gl->DeleteTexture(&inputTextures[i]);
    inputTextures.clear();

    if (!filters.empty())
    {
        map<OpenglFilter,Filter>::iterator it;
        for (it = filters.begin(); it != filters.end(); it++)
        {
            if (it->second.fragmentProgram)
                gl->DeleteProgram(&(it->second.fragmentProgram));
            vector<GLuint> temp = it->second.frameBuffers;
            for (uint i = 0; i < temp.size(); i++)
                gl->DeleteFrameBuffer(&(temp[i]));
            temp = it->second.frameBufferTextures;
            for (uint i = 0; i < temp.size(); i++)
                gl->DeleteTexture(&(temp[i]));
        }
    }
    filters.clear();
}

bool OpenglVideo::Init(OpenglContext *glcontext, bool colour_control,
                  bool onscreen, QSize video_size, QRect visible_rect,
                  QRect video_rect, bool viewport_control, bool osd)
{
    gl = glcontext;
    videoSize = video_size;
    visibleRect = visible_rect;
    videoRect = video_rect;
    masterViewportSize = QSize(1920, 1080);
    QSize rect = videoSize;
    GetTextureSize(&rect, videoSize);
    frameBufferRect = QRect(QPoint(0,0), rect);
    invertVideo = true;
    softwareDeinterlacer = "";
    hardwareDeinterlacing = false;
    useColourControl = colour_control;
    viewportControl = viewport_control;
    inputTextureSize = QSize(0,0);
    convertSize = QSize(0,0);
    videoResize = false;
    videoResizeRect = QRect(0,0,0,0);
    frameBuffer = 0;
    currentFrameNum = -1;
    inputUpdated = false;

    if (!onscreen)
    {
        QSize fb_size(visibleRect.width(), visibleRect.height());
        GetTextureSize(&fb_size, fb_size);
        if (!AddFrameBuffer(&frameBuffer, &frameBufferTexture, fb_size))
            return false;
    }

    SetViewPort(visibleRect.width(), visibleRect.height());
    InitOpengl();

    if (osd)
    {
        QSize osdsize(visibleRect.width(),
                      visibleRect.height());
        GLuint alphatex = CreateVideoTexture(osdsize, &inputTextureSize,
                                gl->TextureSupport() ? false : true);
        GLuint yuv12tex = CreateVideoTexture(osdsize, &inputTextureSize, true);
        if ((alphatex && yuv12tex) && AddFilter(kYUV2RGBA))
        {
            inputTextures.push_back(yuv12tex);
            inputTextures.push_back(alphatex);
        }
    }
    else
    {
        GLuint yuv12tex = CreateVideoTexture(videoSize,
                                            &inputTextureSize, true);
        if (yuv12tex && AddFilter(kYUV2RGB))
            inputTextures.push_back(yuv12tex);
    }

    if (filters.empty())
    {
        if (osd)
        {
            Cleanup();
            return false;
        }
        VERBOSE(VB_PLAYBACK, LOC + "Opengl colour conversion failed.");
        VERBOSE(VB_PLAYBACK, LOC + "Falling back to software conversion.");
        VERBOSE(VB_PLAYBACK, LOC + "Any opengl filters will also be disabled.");
        GLuint yuv12tex = CreateVideoTexture(videoSize,
                                            &inputTextureSize, false);
        if (yuv12tex && AddFilter(kRESIZE))
            inputTextures.push_back(yuv12tex);
        else
        {
            VERBOSE(VB_PLAYBACK, LOC_ERR + "Fatal error.");
            Cleanup();
            return false;
        }
    }
    return true;
}

OpenglFilter OpenglVideo::GetDeintFilter(void)
{
    if (filters.count(kKDEINT))
        return kKDEINT;
    if (filters.count(kLINBLEND))
        return kLINBLEND;
    if (filters.count(kONEFIELD))
        return kONEFIELD;
    if (filters.count(kBOBDEINT))
        return kBOBDEINT;
    if (filters.count(kPROGONEFIELD))
        return kPROGONEFIELD;
    if (filters.count(kPROGLINBLEND))
        return kPROGLINBLEND;
    if (filters.count(kPROGKDEINT))
        return kPROGKDEINT;
    return kNOFILT;
}

bool OpenglVideo::OptimiseFilters(void)
{
    // if video height does not match display rect height, add resize stage
    // to preserve field information N.B. assumes interlaced
    // if video rectangle is smaller than display rectangle, add resize stage
    // to improve performance

    bool needResize =  ((videoSize.height() != videoRect.height()) ||
                        (videoSize.width()  <  videoRect.width()));
    if (needResize && !filters.count(kRESIZE))
        AddFilter(kRESIZE);

    map<OpenglFilter, Filter>::reverse_iterator it;

    // add/remove required frame buffer objects
    // and link filters
    uint buffers_needed = 1;
    bool last_filter    = true;
    bool needtorotate   = false;
    int i = filters.size();
    for (it = filters.rbegin(); it != filters.rend(); it++, i--)
    {
        it->second.outputBuffer = kFrameBufferObject;
        it->second.rotateFrameBuffers = needtorotate;
        if (!last_filter)
        {
            uint buffers_have = it->second.frameBuffers.size();
            int buffers_diff = buffers_needed - buffers_have;
            if (buffers_diff > 0)
            {
                GLuint tmp_buf, tmp_tex;
                QSize fb_size(videoSize.width(), videoSize.height());
                GetTextureSize(&fb_size, fb_size);
                for (int i = 0; i < buffers_diff; i++)
                {
                    if (!AddFrameBuffer(&tmp_buf, &tmp_tex, fb_size))
                        return false;
                    else
                    {
                        it->second.frameBuffers.push_back(tmp_buf);
                        it->second.frameBufferTextures.push_back(tmp_tex);
                    }
                }
            }
            else if (buffers_diff < 0)
            {
                for (int i = 0; i > buffers_diff; i--)
                {
                    gl->DeleteFrameBuffer(&(it->second.frameBuffers.back()));
                    gl->DeleteTexture(&(it->second.frameBufferTextures.back()));
                    it->second.frameBuffers.pop_back();
                    it->second.frameBufferTextures.pop_back();
                }
            }
        }
        else
        {
            last_filter = false;
        }
        buffers_needed = it->second.numInputs;
        needtorotate = (it->first == kKDEINT ||
                        it->first == kLINBLEND ||
                        it->first == kPROGONEFIELD ||
                        it->first == kPROGLINBLEND ||
                        it->first == kPROGKDEINT);
    }

    bool deinterlacing = hardwareDeinterlacing;
    hardwareDeinterlacing = true;
    SetDeinterlacing(false);
    if (deinterlacing)
        SetDeinterlacing(deinterlacing);
    return true;
}

void OpenglVideo::SetFiltering(void)
{
    // filter settings included for performance only
    // no (obvious) quality improvement over GL_LINEAR throughout
    if (filters.empty())
        return;

    if (filters.size() == 1)
    {
        SetTextureFilters(&inputTextures, GL_LINEAR);
        return;
    }

    SetTextureFilters(&inputTextures, GL_NEAREST);
    vector<GLuint> textures;
    map<OpenglFilter,Filter>::iterator it;
    for (it = filters.begin(); it != filters.end(); it++)
        SetTextureFilters(&(it->second.frameBufferTextures), GL_NEAREST);

    // resize or last active (ie don't need resize) need GL_LINEAR
    map<OpenglFilter, Filter>::reverse_iterator rit;
    bool next = false;
    bool resize = filters.count(kRESIZE);
    for (rit = filters.rbegin(); rit != filters.rend(); rit++)
    {
        if (next && (rit->second.outputBuffer != kNoBuffer))
        {
            SetTextureFilters(&(rit->second.frameBufferTextures), GL_LINEAR);
            return;
            break;
        }
        if (resize)
            if (rit->first == kRESIZE)
                next = true;
        else
            if (rit->second.outputBuffer == kDefaultBuffer)
                next = true;
    }
    SetTextureFilters(&inputTextures, GL_LINEAR);
}

bool OpenglVideo::ReInit(OpenglContext *glcontext, bool colour_control,
                  bool onscreen, QSize video_size, QRect visible_rect,
                  QRect video_rect, bool viewport_control, bool osd)
{
    VERBOSE(VB_PLAYBACK, LOC + "Reinit");
    gl->MakeCurrent(true);
    QString harddeint = GetDeinterlacer(); // N.B. only adds back deinterlacer
    QString softdeint = softwareDeinterlacer;
    bool interlacing  = hardwareDeinterlacing;
    bool resize       = videoResize;
    QRect resize_rect = videoResizeRect;

    Cleanup();
    bool success = Init(glcontext, colour_control, onscreen, video_size,
         visible_rect, video_rect, viewport_control, osd);
    if (harddeint != "")
        success &= AddDeinterlacer(harddeint);
    softwareDeinterlacer = softdeint;
    SetDeinterlacing(interlacing);
    if (resize)
        SetVideoResize(&resize_rect);

    return success;
}

bool OpenglVideo::AddFilter(OpenglFilter filter)
{
    if (filters.count(filter))
        return true;

    VERBOSE(VB_PLAYBACK, LOC + QString("Creating %1 filter.")
                                  .arg(FilterToString(filter)));
    gl->MakeCurrent(true);
    Filter temp;
    if (filter == kYUV2RGBA || filter == kLINBLEND || filter == kKDEINT)
        temp.numInputs = 2;
    else if (filter == kPROGONEFIELD || filter == kPROGKDEINT ||
             filter == kPROGLINBLEND)
        temp.numInputs = 3;
    else
        temp.numInputs = 1;

    GLuint program = 0;
    if (filter != kNOFILT && filter != kRESIZE)
    {
        program = AddFragmentProgram(filter);
        if (!program)
            return false;
    }
    temp.fragmentProgram = program;
    temp.outputBuffer = kDefaultBuffer;
    temp.rotateFrameBuffers = false;
    temp.frameBuffers.clear();
    temp.frameBufferTextures.clear();
    filters[filter] = temp;
    if (OptimiseFilters())
        return true;

    RemoveFilter(filter);
    return false;
}

bool OpenglVideo::RemoveFilter(OpenglFilter filter)
{
    if (!filters.count(filter))
        return true;

    gl->MakeCurrent(true);
    gl->DeleteProgram(&(filters[filter].fragmentProgram));
    vector<GLuint> temp;
    vector<GLuint>::iterator it;
    temp = filters[filter].frameBuffers;
    for (it = temp.begin(); it != temp.end(); it++)
        gl->DeleteFrameBuffer(&(*(it)));
    temp = filters[filter].frameBufferTextures;
    for (it = temp.begin(); it != temp.end(); it++)
        gl->DeleteTexture(&(*(it)));
    filters.erase(filter);
    gl->MakeCurrent(false);

    return true;
}

bool OpenglVideo::AddDeinterlacer(QString filter)
{
    QString current_deinterlacer = GetDeinterlacer();
    if (current_deinterlacer != "")
    {
        if (current_deinterlacer == filter)
            return true;
        else
            RemoveFilter(current_deinterlacer);
    }
    return AddFilter(filter);
}

GLuint OpenglVideo::AddFragmentProgram(OpenglFilter name)
{
    if (!gl->FragProgSupport())
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Fragment programs not supported");
        return false;
    }
    GLuint ret;
    QString program = GetProgramString(name);
    program.replace("%1", gl->TextureSupport() ? "RECT" : "2D");
    if (gl->CreateProgram(&program, &ret))
    {
        VERBOSE(VB_PLAYBACK, LOC + QString("Created fragment program %1.")
                                      .arg(FilterToString(name)));
        return ret;
    }
    return 0;
}

bool OpenglVideo::AddFrameBuffer(GLuint *framebuffer,
                                 GLuint *texture, QSize size)
{
    if (!gl->FrameBufSupport())
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Offscreen rendering not supported.");
        return false;
    }
    GLuint tmp_buf, tmp_tex;
    gl->CreateTexture(&tmp_tex);
    if(gl->CreateFrameBuffer( &tmp_buf, &tmp_tex, size.width(), size.height()))
    {
        *framebuffer = tmp_buf;
        *texture = tmp_tex;
        return true;
    }
    gl->DeleteTexture(&tmp_tex);
    return false;
}

void OpenglVideo::SetViewPort(uint width, uint height)
{
    uint w = max((int)width, videoSize.width());
    uint h = max((int)height, videoSize.height());
    viewportSize = QSize(w, h);
    if (!viewportControl)
        return;
    VERBOSE(VB_PLAYBACK, LOC +
        QString("Viewport: %1x%2").arg(w).arg(h));
    ViewPort(w, h);
}

void OpenglVideo::ViewPort(uint width, uint height)
{
    glViewport( 0, 0, width, height);
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho( 0, width - 1, 0, height - 1, 1, -1 ); // aargh...
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
}

void OpenglVideo::InitOpengl(void)
{
    gl->MakeCurrent(true);
    glDisable( GL_BLEND );
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); // for gl osd
    glDisable( GL_DEPTH_TEST );
    glDepthMask( GL_FALSE );
    glDisable( GL_CULL_FACE );
    gl->EnableTextures();;
    glShadeModel( GL_FLAT );
    glDisable( GL_POLYGON_SMOOTH );
    glDisable( GL_LINE_SMOOTH );
    glDisable( GL_POINT_SMOOTH );
    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
    glClear( GL_COLOR_BUFFER_BIT );
    glFlush(); 
    gl->MakeCurrent(false);
}

GLuint OpenglVideo::CreateVideoTexture(QSize size, QSize *tex_size, bool YUV12)
{
    GLuint tmp_tex;
    QSize temp = size;
    gl->CreateTexture(&tmp_tex);
    GetTextureSize(&temp, size);
    if (YUV12)
    {
        if (gl->TextureSupport())
        {
            temp.rheight() = size.height() * 3 / 2;
        }
        else
        {
            if ((size.height() * 1.5) > temp.height())
                temp.rheight() *= 2;
        }
    }
    if (temp.width() > gl->GetMaxTexSize()  ||
        temp.height() > gl->GetMaxTexSize() ||
        !gl->SetupTexture(temp, &tmp_tex, GL_LINEAR))
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Could not create texture.");
        gl->DeleteTexture(&tmp_tex);
        return 0;
    }

    tex_size->setWidth(temp.width());
    tex_size->setHeight(temp.height());
    VERBOSE(VB_PLAYBACK, LOC + QString("Created main input texture %1x%2")
            .arg(temp.width()).arg(temp.height()));
    return tmp_tex;
}

void OpenglVideo::GetTextureSize(QSize *temp, QSize size)
{
    if (gl->TextureSupport())
        return;    glOrtho( 0, width, 0, height, 1, -1 ); // aargh...
    int w, h;
    w = h = 64;
    while (w < size.width())  { w *= 2; }
    while (h < size.height()) { h *= 2; }
    temp->setWidth(w);
    temp->setHeight(h);
}

void OpenglVideo::UpdateInputFrame(VideoFrame *frame)
{
    bool errored = false;
    if (frame->width  != videoSize.width()  ||
        frame->height != videoSize.height() ||
        frame->width  < 1 ||
        frame->height < 1)
        errored = true;

    if (filters.count(kYUV2RGB) && !errored && frame->codec == FMT_YV12)
        UpdateInput(frame->buf, 0, FMT_YV12, videoSize);
    else  // software yuv2rgb
    {
        if (errored)
        {
            ShutDownYUV2RGB();
            return;
        }
        if (convertSize != videoSize)
        {
            ShutDownYUV2RGB();
            VERBOSE(VB_PLAYBACK, LOC + "Init software conversion.");
            convertSize = videoSize;
            convertBuf = new unsigned char[(videoSize.width() *
                                     videoSize.height() * 3) + 4];
        }
        if (convertBuf)
        {
            AVPicture img_in, img_out;
            avpicture_fill(&img_out, (uint8_t *)convertBuf, PIX_FMT_RGB24,
                           convertSize.width(), convertSize.height());
            avpicture_fill(&img_in, (uint8_t *)frame->buf, PIX_FMT_YUV420P,
                           convertSize.width(), convertSize.height());
            img_convert(&img_out, PIX_FMT_RGB24,
                           &img_in,  PIX_FMT_YUV420P,
                           convertSize.width(), convertSize.height());
            UpdateInput(convertBuf, 0, FMT_RGB24, convertSize);
        }
    }
}

void OpenglVideo::UpdateInput(unsigned char *buf, uint texture,
                              int format, QSize size)
{
    inputUpdated = false;
    if (texture > (inputTextures.size() - 1))
        return;

    glBindTexture( gl->m_texture, inputTextures[texture] );
    switch (format)
    {
        case FMT_YV12:
            glTexSubImage2D( gl->m_texture, 0, 0, 0,
                             size.width(), size.height(),
                             GL_LUMINANCE, GL_UNSIGNED_BYTE, buf);
            glTexSubImage2D( gl->m_texture, 0, 0, size.height() ,
                             size.width() / 2, size.height() / 2,
                             GL_LUMINANCE, GL_UNSIGNED_BYTE,
                             buf + (size.width() * size.height()));
            glTexSubImage2D( gl->m_texture, 0, size.width() / 2,
                             size.height() , size.width() / 2,
                             size.height() / 2, GL_LUMINANCE,
                             GL_UNSIGNED_BYTE,buf + (size.width()
                             * size.height()) * 5 / 4);
            break;
        case FMT_RGB24:
            glTexSubImage2D( gl->m_texture, 0, 0, 0,
                             size.width(), size.height(),
                             GL_RGB, GL_UNSIGNED_BYTE, buf);
            break;
        case FMT_ALPHA:
            glTexSubImage2D( gl->m_texture, 0, 0, 0,
                             size.width(), size.height(),
                             GL_ALPHA, GL_UNSIGNED_BYTE,
                             buf);
            break;
        default:
            break;
    }
    inputUpdated = true;
}

void OpenglVideo::ShutDownYUV2RGB(void)
{
    if (convertBuf)
    {
        delete convertBuf;
        convertBuf = NULL;
    }
    convertSize = QSize(0,0);
}

void OpenglVideo::SetVideoResize(QRect *size)
{
    if (!size || size == NULL)
    {
        videoResize = false;
        videoResizeRect = QRect(0, 0, 0, 0);
        return;
    }

    bool abort = (size->right() > videoSize.width()   ||
                  size->bottom() > videoSize.height() ||
                  size->width() > videoSize.width()   ||
                  size->height() > videoSize.height());
    // if resize == existing frame, no need to carry on
    abort |= !size->left() && !size->top() && (size->size() == videoSize);

    if (abort)
    {
        videoResize = false;
        videoResizeRect = QRect(0, 0, 0, 0);
        return;
    }

    videoResize = true;
    videoResizeRect = *size;
}

void OpenglVideo::CalculateResize(float *left, float *top,
                                  float *right, float *bottom)
{
    // FIXME video aspect == display aspect
    float height = visibleRect.height();
    float new_top = height - ((float)videoResizeRect.bottom() /
                    (float)videoSize.height()) * height;
    float new_bottom = height - ((float)videoResizeRect.top() /
                    (float)videoSize.height()) * height;
    *left = ((float)videoResizeRect.left() / (float)videoSize.width()) *
            visibleRect.width();
    *right = ((float)videoResizeRect.right() / (float)videoSize.width()) *
            visibleRect.width();
    *top = new_top;
    *bottom = new_bottom;
}

void OpenglVideo::SetDeinterlacing(bool deinterlacing)
{
    if (deinterlacing == hardwareDeinterlacing)
        return;
    VERBOSE(VB_PLAYBACK, LOC + QString("Turning %1 deinterlacing.")
                                  .arg(deinterlacing ? "on" : "off"));
    hardwareDeinterlacing = deinterlacing;
    map<OpenglFilter, Filter>::iterator it;
    for (it = filters.begin(); it !=filters.end(); it++)
    {
        it->second.outputBuffer = kFrameBufferObject;
        if (it->first >= kKDEINT && it->first <= kPROGONEFIELD)
        {
            if (!deinterlacing)
                it->second.outputBuffer = kNoBuffer;
        }
    }
    map<OpenglFilter, Filter>::reverse_iterator rit;
    for (rit = filters.rbegin(); rit !=filters.rend(); rit++)
    {
        if (rit->second.outputBuffer == kFrameBufferObject)
        {
            rit->second.outputBuffer = kDefaultBuffer;
            break;
        }
    }
    gl->MakeCurrent(true);
    SetFiltering();
    gl->MakeCurrent(false);
}

void OpenglVideo::Show(FrameScanType scan, bool softwareDeinterlacing,
                       long long frame)
{
    if (inputTextures.empty() || filters.empty())
        return;

    vector<GLuint> inputs = inputTextures;
    QSize inputsize = inputTextureSize;
    uint  numfilters = filters.size();

    map<OpenglFilter, Filter>::iterator it;
    for (it = filters.begin(); it !=filters.end(); it++)
    {
        if (it->second.rotateFrameBuffers &&
            !(it->first == kYUV2RGB && scan == kScan_Intr2ndField))
        {
            Rotate(&(it->second.frameBufferTextures));
            Rotate(&(it->second.frameBuffers));
        }

        // skip disabled filters
        if (it->second.outputBuffer == kNoBuffer)
            continue;

        OpenglFilter type = it->first;
        Filter filter = it->second;

        // skip colour conversion for frames already in frame buffer
        if (!inputUpdated)
            if (frame == currentFrameNum && type == kYUV2RGB  && frame != 0)
                if (!(softwareDeinterlacing &&
                      softwareDeinterlacer == "bobdeint"))
                {
                    inputs = filter.frameBufferTextures;
                    inputsize = videoSize;
                    continue;
                }

        // texture coordinates
        float t_right = (float)videoSize.width();
        float t_bottom  = (float)videoSize.height();
        float t_top = 0.0f;

        if (!gl->TextureSupport())
        {
            t_right  /= inputsize.width();
            t_bottom /= inputsize.height();
        }

        float full_height = t_bottom;
        float line_height = (t_bottom / (float)videoSize.height());
        float bob = line_height / 2.0f;

        if (type == kBOBDEINT)
        {
            if (scan == kScan_Interlaced)
            {
                t_bottom += bob;
                t_top += bob;
            }
            if (scan == kScan_Intr2ndField)
            {
                t_bottom -= bob;
                t_top -= bob;
            }
        }
        if (softwareDeinterlacer == "bobdeint" &&
            softwareDeinterlacing &&
            type == kYUV2RGB)
        {
            bob = line_height / 4.0f;
            if (scan == kScan_Interlaced)
            {
                t_bottom /= 2;
                t_bottom += bob;
                t_top    += bob;
            }
            if (scan == kScan_Intr2ndField)
            {
                t_top = t_bottom / 2;
                t_bottom -= bob;
                t_top    -= bob;
            }
        }

        // vertex coordinates
        QRect display;
        if (filter.outputBuffer == kDefaultBuffer)
            display = videoRect;
        else
            display = frameBufferRect;

        float vleft  = display.left();
        float vright = display.right();
        float vtop   = display.top();
        float vbot   = display.bottom();

        // resize for interactive tv
        if (videoResize && filter.outputBuffer == kDefaultBuffer)
            CalculateResize(&vleft, &vtop, &vright, &vbot);

        if (invertVideo && (type == kYUV2RGB || type == kYUV2RGBA)
            || (type == kRESIZE && numfilters == 1))
        {
            float temp = vtop;
            vtop = vbot;
            vbot = temp;
        }

        // bind correct frame buffer (default onscreen) and set viewport
        switch (filter.outputBuffer)
        {
            case kDefaultBuffer:
                if (frameBuffer)
                    gl->m_glBindFramebufferEXT( GL_FRAMEBUFFER_EXT,
                                                frameBuffer);
                // clear the buffer
                if (viewportControl)
                {
                    glClear(GL_COLOR_BUFFER_BIT);
                    ViewPort(visibleRect.width(), visibleRect.height());
                }
                else
                    ViewPort(masterViewportSize.width(),
                             masterViewportSize.height());
                break;
            case kFrameBufferObject:
                gl->m_glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,
                                             filter.frameBuffers[0] );
                ViewPort(frameBufferRect.width(), frameBufferRect.height());
                break;
            case kNoBuffer:
                continue;
         }

        // bind correct textures
        for (uint i = 0; i < inputs.size(); i++)
        {
            glActiveTexture( GL_TEXTURE0 + i );
            glBindTexture( gl->m_texture, inputs[i] );
        }

        // enable fragment program and set any environment variables 
        if (!(type == kNOFILT || type == kRESIZE))
        {
            glEnable ( GL_FRAGMENT_PROGRAM_ARB );
            gl->m_glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB,
                                    filter.fragmentProgram );
            float field = -line_height;
            switch (type)
            {
                case kYUV2RGB:
                case kYUV2RGBA:
                    gl->m_glProgramEnvParameter4fARB (GL_FRAGMENT_PROGRAM_ARB,
                                1, t_right / 2.0f, full_height, 0.0f, 0.0f);
                    if (useColourControl)
                        gl->m_glProgramEnvParameter4fARB(
                            GL_FRAGMENT_PROGRAM_ARB, 0,
                            (pictureAttribs[kPictureAttribute_Brightness] / 50 ) - 0.5,
                            (pictureAttribs[kPictureAttribute_Contrast] / 50),
                            (pictureAttribs[kPictureAttribute_Colour] / 50),
                            0.0f);
                    break;
                case kBOBDEINT:
                case kPROGONEFIELD:
                case kPROGKDEINT:
                case kPROGLINBLEND:
                    if (scan == kScan_Intr2ndField)
                         field *= -1;
                case kONEFIELD:
                case kKDEINT:
                case kLINBLEND:
                    gl->m_glProgramEnvParameter4fARB ( GL_FRAGMENT_PROGRAM_ARB,
                        0, line_height * 2, field, 0.0f, 0.0f);
                    break;
                case kNOFILT:
                case kRESIZE:
                    break;
            }
        }

        // enable blending for osd
        if (type == kYUV2RGBA)
            glEnable(GL_BLEND);

        // draw quad
        glBegin( GL_QUADS );
            glTexCoord2f(0.0f,  t_top);      glVertex2f(vleft,  vtop);
            glTexCoord2f(t_right, t_top);    glVertex2f(vright, vtop);
            glTexCoord2f(t_right, t_bottom); glVertex2f(vright, vbot);
            glTexCoord2f(0.0f,  t_bottom);   glVertex2f(vleft,  vbot);
        glEnd();

        // disable blending
        if (type == kYUV2RGBA)
            glDisable(GL_BLEND);

        // disable fragment program
        if (!(type == kNOFILT || type == kRESIZE))
        {
            gl->m_glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, 0 );
            glDisable ( GL_FRAGMENT_PROGRAM_ARB );
        }

        // switch back to default framebuffer
        if (filter.outputBuffer != kDefaultBuffer || frameBuffer)
            gl->m_glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );

        inputs = filter.frameBufferTextures;
        inputsize = videoSize;
    }
    currentFrameNum = frame;
    inputUpdated = false;
}

void OpenglVideo::Rotate(vector<GLuint> *target)
{
    if (target->size() < 2)
        return;
    GLuint tmp = (*target)[target->size() - 1];
    for (uint i = target->size() - 1; i > 0;  i--)
        (*target)[i] = (*target)[i-1]; 
    (*target)[0] = tmp;
}

void OpenglVideo::SetPictureAttribute(int attributeType, int newValue)
{
    pictureAttribs[attributeType] = (float) newValue;
}

void OpenglVideo::SetTextureFilters(vector<GLuint> *textures, int filt)
{
    if (textures->empty())
        return;
    for (uint i = 0; i < textures->size(); i++)
        gl->SetupTextureFilters(&((*textures)[i]), filt);
}

OpenglFilter OpenglVideo::StringToFilter(QString filter)
{
    OpenglFilter ret = kNOFILT;
    if (filter.contains("master"))
        ret = kYUV2RGB;
    else if (filter.contains("osd"))
        ret = kYUV2RGBA;
    else if (filter.contains("openglkerneldeint"))
        ret = kKDEINT;
    else if (filter.contains("opengllinearblend"))
        ret = kLINBLEND;
    else if (filter.contains("openglonefield"))
        ret = kONEFIELD;
    else if (filter.contains("openglbobdeint"))
        ret = kBOBDEINT;
    else if (filter.contains("openglprogressivelinearblend"))
        ret = kPROGLINBLEND;
    else if (filter.contains("openglprogressivekerneldeint"))
        ret = kPROGKDEINT;
    else if (filter.contains("openglprogressiveonefield"))
        ret = kPROGONEFIELD;
    else if (filter.contains("resize"))
        ret = kRESIZE;
    return ret;
}

QString OpenglVideo::FilterToString(OpenglFilter filt)
{
    switch (filt)
    {
        case kNOFILT:
            break;
        case kYUV2RGB:
            return "master";
        case kYUV2RGBA:
            return "osd";
        case kKDEINT:
            return "openglkerneldeint";
        case kLINBLEND:
            return "opengllinearblend";
        case kONEFIELD:
            return "openglonefield";
        case kBOBDEINT:
            return "openglbobdeint";
        case kPROGLINBLEND:
            return "openglprogressivelinearblend";
        case kPROGKDEINT:
            return "openglprogressivekerneldeint";
        case kPROGONEFIELD:
            return "openglprogressiveonefield";
        case kRESIZE:
            return "resize";
    }
    return "";
}

static const QString yuv2rgb1a =
    "ATTRIB ytex = fragment.texcoord[0];"
    "PARAM  off  = program.env[1];"
    "TEMP res, tmp, tmp2;";

static const QString yuv2rgb1b =
    "TEMP alpha;"
    "TEX alpha, ytex, texture[1], %1;";

static const QString yuv2rgb1c =
    "TEX res, ytex, texture[0], %1;"
    "MAD tmp2, ytex, {0.5, 0.5}, off.wyww;"
    "TEX tmp.x, tmp2, texture[0], %1;"
    "ADD tmp2, tmp2, off.xwww;"
    "TEX tmp.y, tmp2, texture[0], %1;";

static const QString yuv2rgb2 =
    "PARAM  adj  = program.env[0];"
    "SUB res, res, 0.5;"
    "MAD res, res, adj.yyyy, adj.xxxx;"
    "SUB tmp, tmp, { 0.5, 0.5 };"
    "MAD tmp, adj.zzzz, tmp, 0.5;";

static const QString yuv2rgb3 =
    "MAD res, res, 1.164, -0.063;"
    "SUB tmp, tmp, { 0.5, 0.5 };"
    "MAD res, { 0, -.392, 2.017 }, tmp.xxxw, res;";

static const QString yuv2rgb4 =
    "MAD result.color, { 1.596, -.813, 0, 0 }, tmp.yyyw, res;";

static const QString yuv2rgb5 =
    "MAD result.color, { 0, -.813, 1.596, 0 }, tmp.yyyw, res.bgra;";

static const QString yuv2rgb6 =
    "MOV result.color.a, alpha.a;";


QString OpenglVideo::GetProgramString(OpenglFilter name)
{
    QString ret =
            "!!ARBfp1.0\n"
            "OPTION ARB_precision_hint_fastest;";
    switch (name)
    {
        case kYUV2RGB:
            ret = ret + yuv2rgb1a + yuv2rgb1c;
            if (useColourControl)
                ret += yuv2rgb2;
            ret += yuv2rgb3;
            ret += frameBuffer ? yuv2rgb5 : yuv2rgb4;
            break;
        case kYUV2RGBA:
            ret = ret + yuv2rgb1a + yuv2rgb1b + yuv2rgb1c;
            if (useColourControl)
                ret += yuv2rgb2;
            ret = ret + yuv2rgb3 + yuv2rgb4 + yuv2rgb6;
            break;
        case kKDEINT:
            ret +=
            "ATTRIB tex = fragment.texcoord[0];"
            "PARAM  off = program.env[0];"
            "TEMP sam, pos, cum, cur, field, mov;"
            "RCP field, off.x;"
            "MUL field, tex.yyyy, field;"
            "FRC field, field;"
            "SUB field, field, 0.5;"
            "TEX sam, tex, texture[1], %1;"
            "TEX cur, tex, texture[0], %1;"
            "SUB mov, cur, sam;"
            "MUL cum, sam, 0.125;"
            "MAD cum, cur, 0.125, cum;"
            "ABS mov, mov;"
            "SUB mov, mov, 0.12;"
            "ADD pos, tex, off.wyww;"
            "TEX sam, pos, texture[0], %1;"
            "MAD cum, sam, 0.5, cum;"
            "SUB pos, tex, off.wyww;"
            "TEX sam, pos, texture[0], %1;"
            "MAD cum, sam, 0.5, cum;"
            "MAD pos, off.wyww, 2.0, tex;"
            "TEX sam, pos, texture[0], %1;"
            "MAD cum, sam, -0.0625, cum;"
            "TEX sam, pos, texture[1], %1;"
            "MAD cum, sam, -0.0625, cum;"
            "MAD pos, off.wyww, -2.0, tex;"
            "TEX sam, pos, texture[0], %1;"
            "MAD cum, sam, -0.0625, cum;"
            "TEX sam, pos, texture[1], %1;"
            "MAD cum, sam, -0.0625, cum;"
            "CMP cum, mov, cur, cum;"
            "CMP result.color, field, cum, cur;";
        break;
        case kPROGLINBLEND:
            ret +=
            "ATTRIB tex = fragment.texcoord[0];"
            "PARAM  off  = program.env[0];"
            "TEMP field, top, bot, current, previous, next, other, mov;"
            "TEX next, tex, texture[0], %1;"
            "TEX current, tex, texture[1], %1;"
            "TEX previous, tex, texture[2], %1;"
            "ADD top, tex, off.wyww;"
            "TEX other, top, texture[1], %1;"
            "SUB top, tex, off.wyww;"
            "TEX bot, top, texture[1], %1;"
            "LRP other, 0.5, other, bot;"
            "RCP field, off.x;"
            "MUL field, tex.yyyy, field;"
            "FRC field, field;"
            "SUB field, field, 0.5;"
            "SUB top, current, next;"
            "SUB bot, current, previous;"
            "CMP mov, field, bot, top;"
            "ABS mov, mov;"
            "SUB mov, mov, 0.12;"
            "CMP other, mov, current, other;"
            "CMP top, field, other, current;"
            "CMP bot, field, current, other;"
            "CMP result.color, off.y, top, bot;";
            break;
        case kPROGONEFIELD:
            ret +=
            "ATTRIB tex = fragment.texcoord[0];"
            "PARAM  off  = program.env[0];"
            "TEMP field, top, bot, current, previous, next, other, mov;"
            "TEX next, tex, texture[0], %1;"
            "TEX current, tex, texture[1], %1;"
            "TEX previous, tex, texture[2], %1;"
            "ADD top, tex, off.wyww;"
            "TEX other, top, texture[1], %1;"
            "RCP field, off.x;"
            "MUL field, tex.yyyy, field;"
            "FRC field, field;"
            "SUB field, field, 0.5;"
            "SUB top, current, next;"
            "SUB bot, current, previous;"
            "CMP mov, field, bot, top;"
            "ABS mov, mov;"
            "SUB mov, mov, 0.12;"
            "CMP other, mov, current, other;"
            "CMP top, field, other, current;"
            "CMP bot, field, current, other;"
            "CMP result.color, off.y, top, bot;";
            break;
        case kPROGKDEINT:
            ret +=
            "ATTRIB tex = fragment.texcoord[0];"
            "PARAM  off = program.env[0];"
            "TEMP sam, pos, bot, top, cur, pre, nex, field, mov;"
            "RCP field, off.x;"
            "MUL field, tex.yyyy, field;"
            "FRC field, field;"
            "SUB field, field, 0.5;"
            "TEX pre, tex, texture[2], %1;" // -1,0
            "TEX cur, tex, texture[1], %1;" //  0,0
            "TEX nex, tex, texture[0], %1;" // +1,0
            "SUB top, nex, cur;"
            "SUB bot, pre, cur;"
            "CMP mov, field, bot, top;"
            "ABS mov, mov;"
            "SUB mov, mov, 0.12;"
            "MUL bot, pre, 0.125;"          // BOT -1,0
            "MAD bot, cur, 0.125, bot;"     // BOT +1,0
            "MUL top, cur, 0.125;"          // TOP -1,0
            "MAD top, nex, 0.125, top;"     // TOP +1,0
            "ADD pos, tex, off.wyww;"
            "TEX sam, pos, texture[1], %1;" // 0,+1
            "MAD bot, sam, 0.5, bot;"       // BOT 0,+1
            "MAD top, sam, 0.5, top;"       // TOP 0,+1
            "SUB pos, tex, off.wyww;"
            "TEX sam, pos, texture[1], %1;" // 0,-1
            "MAD bot, sam, 0.5, bot;"       // BOT 0,-1
            "MAD top, sam, 0.5, top;"       // TOP 0,-1
            "MAD pos, off.wyww, 2.0, tex;"
            "TEX sam, pos, texture[1], %1;" // 0,+2
            "MAD bot, sam, -0.0625, bot;"   // BOT +1,+2
            "MAD top, sam, -0.0625, top;"   // TOP -1,+2
            "TEX sam, pos, texture[2], %1;" // -1,+2
            "MAD bot, sam, -0.0625, bot;"   // BOT -1,+2
            "TEX sam, pos, texture[0], %1;" // +1,+2
            "MAD top, sam, -0.0625, top;"   // TOP +1,+2
            "MAD pos, off.wyww, -2.0, tex;"
            "TEX sam, pos, texture[1], %1;" // +1,-2
            "MAD bot, sam, -0.0625, bot;"   // BOT +1,-2
            "MAD top, sam, -0.0625, top;"   // TOP -1,-2
            "TEX sam, pos, texture[2], %1;" // -1, -2 row
            "MAD bot, sam, -0.0625, bot;"   // BOT -1,-2
            "TEX sam, pos, texture[0], %1;" // +1,-2
            "MAD top, sam, -0.0625, top;"   // TOP +1,-2
            "CMP top, mov, cur, top;"
            "CMP bot, mov, cur, bot;"
            "CMP top, field, top, cur;"
            "CMP bot, field, cur, bot;"
            "CMP result.color, off.y, top, bot;";
            break;
        case kBOBDEINT:
        case kONEFIELD:
            ret +=
            "ATTRIB tex = fragment.texcoord[0];"
            "PARAM  off = program.env[0];"
            "TEMP field, top, bottom, current, other;"
            "TEX current, tex, texture[0], %1;"
            "RCP field, off.x;"
            "MUL field, tex.yyyy, field;"
            "FRC field, field;"
            "SUB field, field, 0.5;"
            "ADD top, tex, off.wyww;"
            "TEX other, top, texture[0], %1;"
            "CMP top, field, other, current;"
            "CMP bottom, field, current, other;"
            "CMP result.color, off.y, top, bottom;";
            break;
        case kLINBLEND:
            ret +=
            "ATTRIB tex = fragment.texcoord[0];"
            "PARAM  off  = program.env[0];"
            "TEMP mov, field, cur, pre, pos;"
            "RCP field, off.x;"
            "MUL field, tex.yyyy, field;"
            "FRC field, field;"
            "SUB field, field, 0.5;"
            "TEX cur, tex, texture[0], %1;"
            "TEX pre, tex, texture[1], %1;"
            "SUB mov, cur, pre;"
            "ABS mov, mov;"
            "SUB mov, mov, 0.12;"
            "ADD pos, tex, off.wyww;"
            "TEX pre, pos, texture[0], %1;"
            "SUB pos, tex, off.wyww;"
            "TEX pos, pos, texture[0], %1;"
            "LRP pre, 0.5, pos, pre;"
            "CMP pre, field, pre, cur;"
            "CMP result.color, mov, cur, pre;";
            break;
        case kNOFILT:
        case kRESIZE:
            break;
        default:
            VERBOSE(VB_PLAYBACK, LOC_ERR + "Unknown fragment program.");
    }
    return ret + "END";
}

