/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: winsurf2.cpp,v 1.23.2.2 2004/07/26 10:28:48 pankajgupta Exp $
 *
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 *
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 *
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 *
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 *
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 *
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** */

#include "hxtypes.h"
#include <math.h>
#include "ddraw.h"
#include "hxcom.h"
#include "hxwintyp.h"
#include "hxvsurf.h"
#include "hxslist.h"
#include "ddhlpr.h"

#include "winsurf.h"
#include "vidsurf2.h"
#include "winsurf2.h"
#include "winsite.h"
#include "colormap.h"
#include "windraw2.h"
#include "hxprefs.h"
#include "hxtick.h"
#include "hxheap.h"
#include "hxevent.h"
#include "hwmemobj.h"


// these are defined in basesurf.cpp and should probably be moved to a .h file
#define HX_OVER_KEYDEST    1
#define HX_OVER_HIDE       2
#define HX_OVER_SHOW       4
#define OVERLAY_NOT_VISIBLE 0

#ifdef _DEBUG
#undef HX_THIS_FILE
static const char HX_THIS_FILE[] = __FILE__;

#include "winthrd.h"
#endif

#define OS_THREAD_QUANTUM 10
#define MAX_PREEMPTION_CHECK_ATTEMPTS 3
#define TIME_JITTER_FILTER_THRESHOLD 0.45

#define MIN_ALLOWED_REGRESSION  0.95
//#define HARDCODE_GRANULE
#define SCHEDULE_GRANULE        5
//#define LOG_JITTER_DATA
#define JITTER_LOG_FILE "c:\\temp\\ts.txt"

/****************************************************************************
 *  Debug
 */
//#define ENABLE_SYNC_TRACE
#ifdef ENABLE_SYNC_TRACE
#define MAX_SYNC_TRACE_ENTRIES	10000
ULONG32 ulSyncTraceIdx = 0;
double syncTraceArray[MAX_SYNC_TRACE_ENTRIES][3];

void DumpSyncEntries(void)
{
    FILE* pFile = NULL;
    ULONG32 ulIdx;

    if (ulSyncTraceIdx > 0)
    {
	pFile = fopen("/tmp/getscanline.txt", "wb"); /* Flawfinder: ignore */
    }

    if (pFile)
    {
	for (ulIdx = 0; ulIdx < (ulSyncTraceIdx < MAX_SYNC_TRACE_ENTRIES ?
             ulSyncTraceIdx : MAX_SYNC_TRACE_ENTRIES); ulIdx++)
	{
	    fprintf(pFile, "%lf\t%lf\t%lf\n", syncTraceArray[ulIdx][0],
					      syncTraceArray[ulIdx][1],
                                              syncTraceArray[ulIdx][2]
                                            );
	}

	fclose(pFile);
    }

    ulSyncTraceIdx = 0;
}
#endif	// ENABLE_SYNC_TRACE

//#define ENABLE_ERROR_TRACE
#ifdef ENABLE_ERROR_TRACE
#define MAX_ERROR_TRACE_ENTRIES	10000

double g_error[MAX_ERROR_TRACE_ENTRIES][3];
double g_alternateError[MAX_ERROR_TRACE_ENTRIES];
ULONG32 g_ulFrameCount;

UINT32 g_sleepError[MAX_ERROR_TRACE_ENTRIES];
ULONG32 g_ulSleepCount;

void DumpErrorEntries(void)
{
    FILE* pFile = NULL;
    ULONG32 ulIdx;

    if (g_ulFrameCount > 0)
    {
	pFile = fopen("/tmp/vblankerror.txt", "wb"); /* Flawfinder: ignore */
    }

    if (pFile)
    {
	for (ulIdx = 0; ulIdx < (g_ulFrameCount < MAX_SYNC_TRACE_ENTRIES ?
             g_ulFrameCount : MAX_SYNC_TRACE_ENTRIES); ulIdx++)
	{
	    fprintf(pFile, "%lf\t%lf\t%lf\n", g_error[ulIdx][0],
                                         g_error[ulIdx][1],
                                         g_error[ulIdx][2]);
	}

	fclose(pFile);
    }

    g_ulFrameCount = 0;
}
#endif	// ENABLE_ERROR_TRACE

// Using "this" in constructor initialzer
#pragma warning(disable:4355)

CWinSurface2::CWinSurface2(IUnknown* pContext, CHXBaseSite* pSite)
 :  CWinSurface(pContext,pSite)
 ,  CVideoSurface2((IUnknown*)(IHXVideoSurface*)this)
 ,  m_hRenThread(0)
 ,  m_hFrameSem(0)
 ,  m_hReinit(0)
 ,  m_hAbort(0)
 ,  m_hForceRender(0)
 ,  m_hSurfaceMutex(0)
 ,  m_bCont(FALSE)
 ,  m_bBoostRenderThread(TRUE)
 ,  m_bDoAlphaCheck(TRUE)
 ,  m_bQueryMonitorFreq(FALSE)
 ,  m_bUseVBlankFlip(TRUE)
 ,  m_bIngnorePresentIfReady(FALSE)
 ,  m_bScratchSurface(FALSE)
 ,  m_bGDISurface(FALSE)
 ,  m_nScratchIndex(0)
 ,  m_nLastDisplayTime(0)
 ,  m_pListOfFrames(NULL)
 ,  m_pTimeLine(NULL)
 ,  m_hFallbackEmpty(0)
 ,  m_nFallbackSurfaceCID(0)
 ,  m_bFallbackSurfaceCreated(FALSE)
 ,  m_pSysMemSurf(NULL)
 ,  m_nSysMemSurfSize(0)
 ,  m_nSysMemSurfCount(0)
 ,  m_nSysMemSurfPitch(0)
 ,  m_nSysMemSurfID(0)
 ,  m_nNextSysMemBuffer(0)
 ,  m_bUseSysMemSurface(FALSE)
 ,  m_nGdiSurfaceCount(0)
 ,  m_nLastGdiSurface(-1)
 ,  m_nNextGdiSurface(0)
 ,  m_nGdiSurfaceCID(0)
 ,  m_bGdiSurfaceCreated(FALSE)
 ,  m_ulFillColoryKeyCount(0)
 ,  m_bFlushing(FALSE)
 ,  m_bFrameHasHWAlphaBlend(FALSE)
 ,  m_bSurfaceRequiresOverlay(FALSE)
 ,  m_bSwitchingingOverlay(FALSE)
 ,  m_bUseDDColorControls(FALSE)
 ,  m_bWaitingForFlush(FALSE)
 ,  m_bWasInVidSurf2(FALSE)
 ,  m_dLastXScale(0.0)
 ,  m_dLastYScale(0.0)
 ,  m_nOverlayBackBuffersCreated(0)
 ,  m_dLastBlitError(0)
{
    // initialize data so we don't have invalid ptrs floating around.
    memset(&m_surface,0,sizeof(m_surface));
    memset(&m_GDIsurface,0,sizeof(m_GDIsurface));
    memset(&m_fallbackSurface,0,sizeof(m_fallbackSurface));
    memset(&m_allocatedFallbackSize, 0, sizeof(m_allocatedFallbackSize));
    memset(&m_allocatedSysMemSurfSize, 0, sizeof(m_allocatedSysMemSurfSize));
    memset(&m_allocatedFallbackSize, 0, sizeof(m_allocatedFallbackSize));

    memset(&m_rcLast, 0, sizeof(m_rcLast));

    InitializeCriticalSection(&m_csDisplay);
    InitializeCriticalSection(&m_csFallback);
    InitializeCriticalSection(&m_csList);
    InitializeCriticalSection(&m_csScale);

    m_hAbort = CreateEvent(NULL, TRUE, FALSE, NULL);
    m_hForceRender = CreateEvent(NULL, TRUE, FALSE, NULL);
    m_hReinit = CreateEvent(NULL, TRUE, TRUE, NULL);
    m_hFallbackEmpty = CreateEvent(NULL, TRUE, TRUE, NULL);

    m_hSurfaceMutex = CreateMutex(NULL, FALSE, NULL);
    m_pListOfFrames = new CHXSimpleList;

    // Read some preferences
    IHXPreferences*    pPreferences    = NULL;
    IHXBuffer*         pBuffer         = NULL;

    if (HXR_OK == m_pContext->QueryInterface(IID_IHXPreferences,(void**)&pPreferences))
    {
        // Use vblank flags for flipping
        if (pPreferences->ReadPref("VideoBoost\\DisableVBlankFlip", pBuffer) == HXR_OK)
        {
            m_bUseVBlankFlip = !(::atoi((char*) pBuffer->GetBuffer()) == 1);
        }
        HX_RELEASE(pBuffer);

        // Turn-off render thread priority boosting
        if (pPreferences->ReadPref("VideoBoost\\DisablePriorityBoost", pBuffer) == HXR_OK)
        {
            m_bBoostRenderThread = !(::atoi((char*) pBuffer->GetBuffer()) == 1);
        }
        HX_RELEASE(pBuffer);

        // Limit number of hw buffers
        if (pPreferences->ReadPref("VideoBoost\\MaxBuffers", pBuffer) == HXR_OK)
        {
            m_nMaxBackBuffers = ::atoi((char*) pBuffer->GetBuffer()) - 1;
            m_nMaxBackBuffers = max(1, m_nMaxBackBuffers);
        }
        HX_RELEASE(pBuffer);
    }


    HX_RELEASE(pPreferences);
}

CWinSurface2::~CWinSurface2()
{
    if (m_hRenThread)
    {
        m_bCont = FALSE;
        SetEvent(m_hAbort);

        WaitForSingleObject(m_hRenThread, INFINITE);

        CloseHandle(m_hFrameSem);
        m_hFrameSem = 0;

        CloseHandle(m_hRenThread);
        m_hRenThread = 0;

        DestroyFallbackSurface();
        DestroySysMemSurface();
        DestroyGdiSurface();
    }

    if (m_hAbort)
    {
        CloseHandle(m_hAbort);
        m_hAbort = 0;
    }

    if (m_hForceRender)
    {
        CloseHandle(m_hForceRender);
        m_hForceRender = 0;
    }

    if (m_hReinit)
    {
        CloseHandle(m_hReinit);
        m_hReinit = 0;
    }

    if (m_hFallbackEmpty)
    {
        CloseHandle(m_hFallbackEmpty);
        m_hFallbackEmpty = NULL;
    }

    if (m_hSurfaceMutex)
    {
        CloseHandle(m_hSurfaceMutex);
        m_hSurfaceMutex = 0;
    }

    HX_DELETE(m_pListOfFrames);

    DeleteCriticalSection(&m_csDisplay);
    DeleteCriticalSection(&m_csFallback);
    DeleteCriticalSection(&m_csList);
    DeleteCriticalSection(&m_csScale);

    HX_RELEASE(m_pTimeLine);
}

STDMETHODIMP CWinSurface2::QueryInterface(REFIID riid, void** ppvObj)
{
    if (IsEqualIID(riid, IID_IHXVideoSurface2))
    {
        return CVideoSurface2::QueryInterface(riid, ppvObj);
    }

    return CWinSurface::QueryInterface(riid, ppvObj);
}

STDMETHODIMP_(ULONG32) CWinSurface2::AddRef()
{
    return CWinSurface::AddRef();
}

STDMETHODIMP_(ULONG32) CWinSurface2::Release()
{
    return CWinSurface::Release();
}

STDMETHODIMP CWinSurface2::BeginOptimizedBlt(HXBitmapInfoHeader* pBitmapInfo)
{
    if (m_bWasInVidSurf2 &&
        pBitmapInfo &&
        m_pOptimizedFormat)
    {
        // Are the new parameters the same as the old paramaters? If not
        // then delete the old surfaces.
        if (pBitmapInfo->biWidth       != m_pOptimizedFormat->biWidth       ||
            pBitmapInfo->biHeight      != m_pOptimizedFormat->biHeight      ||
            pBitmapInfo->biPlanes      != m_pOptimizedFormat->biPlanes      ||
            pBitmapInfo->biBitCount    != m_pOptimizedFormat->biBitCount    ||
            pBitmapInfo->biCompression != m_pOptimizedFormat->biCompression ||
            m_bWasInVidSurf2)
        {
            if (m_bWasInVidSurf2)
                _WaitForFlush();

            DestroySurfaces();

            if (m_bWasInVidSurf2)
            {
                m_bVideoSurface2 = FALSE;
                m_bFlipOverlay = FALSE;
                m_nBackBufferCount = 0;
            }
        }
    }

    return CWinSurface::BeginOptimizedBlt(pBitmapInfo);
}

STDMETHODIMP CWinSurface2::Blt( UCHAR*               pImageData,
                                HXBitmapInfoHeader* pBitmapInfo,
                                REF(HXxRect)         rDestRect,
                                REF(HXxRect)         rSrcRect)
{
    m_pSite->_TLSLock();

    // Handle VS2->VS1 transions
    if (m_bWasInVidSurf2)
    {
        if (!m_pSite->_BordersActive() &&
            !m_pSite->_FadeTransitionActive() &&
            (!m_pSite->AreVideoControlsActive() || m_bUseDDColorControls) &&
            !m_pSite->IsHigherZOrderTranstitionActive() &&
            m_pSite->m_pUser && !m_LinkedSites.GetCount() &&
            !m_pLinkedOverlay)
        {
            HXxEvent event = {HX_SURFACE_MODE_CHANGE, NULL, NULL,
                              (void*)HX_VIDEOSURFACE1_NOT_RECOMMENDED, 0, 0};

            m_bWasInVidSurf2 = FALSE;

            // If we just turned off color controls inform the color converter
            m_pSite->CheckColorSettings();

            m_pSite->m_pUser->HandleEvent(&event);

            // The renderer switched to VideoSurface1 so don't give them a buffer
            if (event.handled)
            {
                m_pSite->_TLSUnlock();
                return HXR_FAIL;
            }
            else
                m_bWasInVidSurf2 = TRUE;
        }
        else if (m_bVideoSurface2)
        {
            m_pSite->_TLSUnlock();
            _WaitForFlush();
            m_bVideoSurface2 = FALSE;
            m_pSite->_TLSLock();
        }
    }

    HX_RESULT hr = CWinSurface::Blt(pImageData, pBitmapInfo, rDestRect, rSrcRect);

    m_pSite->_TLSUnlock();
    return hr;
}

void CWinSurface2::_CreateBuffer()
{
    if (m_hRenThread)
    {
        WaitForSingleObject(m_hSurfaceMutex, INFINITE);
        EnterCriticalSection(&m_csDisplay);
    }

    CWinSurface::_CreateBuffer();
    BOOL bCreated = FALSE;

    if (m_surface.fMode & WINDRAW_DIRECTDRAW)
    {
        bCreated = (m_surface.dd.lpDDSurface != NULL);
    }
    else
    {
        bCreated = (m_surface.gdi.lpGDIBackBuffer[0] != NULL);
    }

    if (bCreated)
    {
        if (m_hRenThread)
        {
            // We create our surface, start servicing GetVidMem calls
            SetEvent(m_hReinit);
        }
    }

    if (m_hRenThread)
    {
        ReleaseMutex(m_hSurfaceMutex);
        LeaveCriticalSection(&m_csDisplay);
    }

}

HX_RESULT CWinSurface2::_CreateOverlay(BOOL bOverlay, int cid, int x, int y)
{
    if (m_hRenThread)
    {
        WaitForSingleObject(m_hSurfaceMutex, INFINITE);
        EnterCriticalSection(&m_csDisplay);
    }

    HX_RESULT hr = CWinSurface::_CreateOverlay(bOverlay, cid, x, y);

    if (HXR_OK == hr && m_hRenThread)
    {
        // We created our surface, start servicing GetVidMem calls
        SetEvent(m_hReinit);
    }

    if (m_hRenThread)
    {
        LeaveCriticalSection(&m_csDisplay);
        ReleaseMutex(m_hSurfaceMutex);
    }

    return hr;
}

HX_RESULT
CWinSurface2::ByPassCompositionSurface(UCHAR*               pImageData,
                                       HXBitmapInfoHeader* pBitmapInfo,
                                       REF(HXxRect)         rDestRect,
                                       REF(HXxRect)         rSrcRect,
                                       UCHAR*               pSurface,
                                       INT32                nSurfPitch)

{
    // Check for vidsurf2-only dest surfaces
    if (CID_I420 == GETBITMAPCOLOR( pBitmapInfo) &&
        m_pHwMemObj)
    {
        HXxSize srcSize = {pBitmapInfo->biWidth, pBitmapInfo->biHeight};

        UCHAR* pYUV[3] = {pImageData,
                          pYUV[0] + pBitmapInfo->biHeight*nSurfPitch,
                          pYUV[1] + pBitmapInfo->biHeight*nSurfPitch/4};
        INT32 nYUVPitch[3] = {nSurfPitch, nSurfPitch/2, nSurfPitch/2};

        SourceInputStruct input = {pYUV, nYUVPitch, 3};

        INT32 nPitch;
        UCHAR* pVidMem =
         (UCHAR*)m_pHwMemObj->DeviceToRenderer(pImageData,
                                               nPitch,
                                               WRITE_VIDEO_TO_STRUCT);

        AlignRect(&rSrcRect, pBitmapInfo->biWidth, pBitmapInfo->biHeight);
        AlignRect(&rDestRect, m_surfaceSize.cx, m_surfaceSize.cy);

        HX_RESULT hr =
            ColorConvert(MapCIDtoFourCC(GETBITMAPCOLOR(pBitmapInfo)),
                         &srcSize, &rSrcRect,
                         &input, MapCIDtoFourCC(m_nSurfaceCID), pVidMem,
                         &m_surfaceSize, &rDestRect, nSurfPitch);

        m_pHwMemObj->RendererToDevice(pVidMem);

        return hr;
    }

    return CWinSurface::ByPassCompositionSurface(pImageData,
                                                 pBitmapInfo,
                                                 rDestRect,
                                                 rSrcRect,
                                                 pSurface,
                                                 nSurfPitch);


}

void CWinSurface2::_ReleaseSurface(CBaseRootSurface* pSurface)
{
    if (m_hRenThread)
    {
        Flush();

        // Make the next GetVidMem wait on new surface creation
        WaitForSingleObject(m_hSurfaceMutex, INFINITE);

        ResetEvent(m_hReinit);

        Flush();
        EnterCriticalSection(&m_csDisplay);
    }

    WINDRAW* pWindraw = ((CWinBaseRootSurface*)pSurface)->GetWinDraw();
    DestroyFallbackSurface();
    DestroyGdiSurface();

    CWinSurface::_ReleaseSurface(pSurface);

    memset(&m_lastSrcRect, 0, sizeof(m_lastSrcRect));

    if (m_hRenThread)
    {
        LeaveCriticalSection(&m_csDisplay);
        ReleaseMutex(m_hSurfaceMutex);
    }

    m_dLastXScale = 0.0;
    m_dLastYScale = 0.0;
}

void CWinSurface2::_ReleaseSurface(CWinBaseRootSurface* pSurface)
{
    if (m_hRenThread)
    {
        Flush();

        // Make the next GetVidMem wait on new surface creation
        WaitForSingleObject(m_hSurfaceMutex, INFINITE);
        ResetEvent(m_hReinit);

        Flush();
        EnterCriticalSection(&m_csDisplay);
    }

    WINDRAW* pWindraw = pSurface->GetWinDraw();
    DestroyFallbackSurface();
    DestroyGdiSurface();

    CWinSurface::_ReleaseSurface(pSurface);


    memset(&m_lastSrcRect, 0, sizeof(m_lastSrcRect));

    if (m_hRenThread)
    {
        LeaveCriticalSection(&m_csDisplay);
        ReleaseMutex(m_hSurfaceMutex);
    }

    m_dLastXScale = 0.0;
    m_dLastYScale = 0.0;
}

void CWinSurface2::_ReleaseSurface()
{
    if (m_hRenThread)
    {
        Flush();

        // Make the next GetVidMem wait on new surface creation
        WaitForSingleObject(m_hSurfaceMutex, INFINITE);
        ResetEvent(m_hReinit);

        Flush();
        EnterCriticalSection(&m_csDisplay);
    }

    CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    if (pSurface)
    {
        WINDRAW* pWindraw = pSurface->GetWinDraw();
        if (pWindraw)
        {
            DestroyFallbackSurface();
            DestroyGdiSurface();
            memset(&m_lastSrcRect, 0, sizeof(m_lastSrcRect));

            CWinSurface::_ReleaseSurface();
        }
    }

    if (m_hRenThread)
    {
        LeaveCriticalSection(&m_csDisplay);
        ReleaseMutex(m_hSurfaceMutex);
    }

    m_dLastXScale = 0.0;
    m_dLastYScale = 0.0;
}

void CWinSurface2::ReInitSurfaces()
{
    if ( (m_surface.fMode & WINDRAW_DIRECTDRAW) &&  m_surface.dd.lpDDSurface && m_bVideoSurface2)
    {
        return;
    }

    // Set yuv priority for vidsurf2 mode
    if (m_hRenThread)
    {
        // Lock the surface mutex
        WaitForSingleObject(m_hSurfaceMutex, INFINITE);

        m_pSite->_TLSLock();

        int cid = GetBitmapColor((LPBITMAPINFO)m_pOptimizedFormat);
        BOOL bRestore = SetYUVPriorityList(cid);

        m_nBackBufferCount = m_nOverlayBackBuffersCreated;

        CWinSurface::ReInitSurfaces();

        if (bRestore)
            RestoreColorspacePriorities(cid);

        // We switched to a mode that does not support overlay...yikes!
        if (m_bVideoSurface2 &&
            (m_nBltMode != HX_OVERLAY_BLT || !m_nBackBufferCount))
        {
            m_bMultipleOverlay = TRUE;
            ForceGDIMode(TRUE);
        }
        else
        {
            m_bMultipleOverlay = FALSE;
        }

        HXxSize rcSize = m_surfaceSize;
        CreateFallbackSurface(m_nSurfaceCID, &rcSize);

        m_pSite->_TLSUnlock();

        ResetBufferTimes(0);
        ReleaseMutex(m_hSurfaceMutex);

    }
    else
        CWinSurface::ReInitSurfaces();
}

BOOL CWinSurface2::_IsDisplaySurfaceYuv()
{
    if (!m_bVideoSurface2)
        return CWinSurface::_IsDisplaySurfaceYuv();

    return IsYUV(MapFourCCtoCID(m_bmi.biCompression));
}

BOOL CWinSurface2::_AllowsOverlayShrinking()
{
    if (m_bSurfaceRequiresOverlay)
        return TRUE;
    else
        return CWinSurface::_AllowsOverlayShrinking();
}

void CWinSurface2::_WaitForQueuedFrames()
{
    WaitForQueuedFrames();
}

void CWinSurface2::_LockBlitter()
{
    if (m_hSurfaceMutex)
    {
        // In the unlikely event that all of our hw buffers are
        // full AND GetVideoMem is blocking with m_hSurfaceMutex
        // while waiting for the time line to start, break
        // out of the time line wait loop.
        BOOL bOldValue = m_bWaitingForFlush;
        m_bWaitingForFlush = TRUE;

        WaitForSingleObject(m_hSurfaceMutex, INFINITE);

        m_bWaitingForFlush = bOldValue;
    }
}
void CWinSurface2::_UnlockBlitter()
{
    if (m_hSurfaceMutex)
        ReleaseMutex(m_hSurfaceMutex);
}

void CWinSurface2::_FlushSurfaces()
{
    Flush();
}

STDMETHODIMP CWinSurface2::SetProperties(HXBitmapInfoHeader* bmi,
                                        REF(UINT32) ulNumBuffers,
                                        IHXRenderTimeLine* pTimeLine)
{
    // Block GetVideoMem calls
    WaitForSingleObject(m_hSurfaceMutex, INFINITE);

    ulNumBuffers = min(m_nMaxBackBuffers+1, (INT32)ulNumBuffers);

    CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    if (!pSurface)
    {
        ReleaseMutex(m_hSurfaceMutex);
        return HXR_FAIL;
    }

    WINDRAW* pWindraw = pSurface->GetWinDraw();

    // If we already have surfaces, destroy and recreate if necessary
    if (m_hRenThread)
    {
        if (bmi->biWidth != m_bmi.biWidth ||
            bmi->biHeight != m_bmi.biHeight ||
            m_surface.dwBackBufferCount == 0 ||
            ulNumBuffers != (UINT32)m_nBackBufferCount+1 ||
            WAIT_OBJECT_0 != WaitForSingleObject(m_hReinit, 0))
        {
            // Wait for display buffers to render
            if (HXR_OK != WaitForQueuedFrames())
                Flush();

            DestroySurfaces();

            m_nBackBufferCount = 0;
            memset(&m_bmi, 0, sizeof(m_bmi));
            memset(&m_lastSrcRect, 0, sizeof(m_lastSrcRect));
        }
        else
        {
            HX_RELEASE(m_pTimeLine);

            m_pTimeLine = pTimeLine;
            if (m_pTimeLine)
                m_pTimeLine->AddRef();

            m_bVideoSurface2 = TRUE;

            // If we are keeping our existing video buffers, reset
            // the flipping chain indexes if we have DirectDraw.
            if (!m_bLostHWAcceleration)
                WinDraw_ResetSurfaceIndexes(pWindraw, &m_surface);

            ReleaseMutex(m_hSurfaceMutex);
            return HXR_OK;
        }
    }

    // If we do not have a render thread and we have an optimized
    // format, we are in VS1 mode so fail the VS2 init.
    else if (m_pOptimizedFormat)
    {
        ReleaseMutex(m_hSurfaceMutex);
        return HXR_FAIL;
    }

    // Try and create an overlay flpping chain of pRequest->nNumBuffers buffers
    m_nBackBufferCount = ulNumBuffers - 1;

    if (m_nBackBufferCount)
        m_bFlipOverlay = TRUE;

    m_bSpamUpdateOverlay = FALSE;

    int cid = GetBitmapColor((LPBITMAPINFO)bmi);
    BOOL bRestore = SetYUVPriorityList(cid);

    BeginOptimizedBlt(bmi);
    int nBltMode = m_nBltMode;

    if (bRestore)
        RestoreColorspacePriorities(cid);

    if (nBltMode != HX_OVERLAY_BLT ||
        !m_nBackBufferCount)
    {
        DestroySurfaces();
        ulNumBuffers = 0;
        m_bVideoSurface2 = FALSE;

        ReleaseMutex(m_hSurfaceMutex);
        return HXR_FAIL;
    }

    m_nOverlayBackBuffersCreated = m_nBackBufferCount;
    bmi->biCompression = MapCIDtoFourCC(m_nSurfaceCID);

    // How odd, a dd driver that returns the wrong caps.
    // I have not seen anything like since the last time I tried
    // to use the caps.
    //
    // We will always try interval flips and swith to flip
    // waits if flip returns an error.

#if DIRECTDRAW_VERSION > 0x0500
    // Can we use the flip interval flag
    if ((pWindraw->dd.m_caps.dwCaps2 & DDCAPS2_FLIPINTERVAL) != DDCAPS2_FLIPINTERVAL)
    {
        m_bUseVBlankFlip = FALSE;
    }
#else
    m_bUseVBlankFlip = FALSE;
#endif

    if (pWindraw->dwMaxScanLine && pWindraw->dMsPerVBlank > 0.0)
        m_bOptimalVideoScheduler = TRUE;

    // If we are using a DirectDraw surface represented by a structure,
    // several of our post-processing functions will not work. Set the
    // various modes here.  While we are generalizing here, most of
    // these surface types are proprietary so disable anything suspicious.
    if (IsStructured(m_nSurfaceCID))
    {
        m_bUseDDColorControls = TRUE;
        m_bSurfaceRequiresOverlay = TRUE;

        // Set default DD color controls
        m_Brightness = DEF_BRIGHTNESS;
        m_Contrast = DEF_CONTRAST;
        m_Saturation = DEF_SATURATION;
        m_Hue = DEF_HUE;
        m_Sharpness = DEF_SHARPNESS;
    }
    else
    {
        m_bUseDDColorControls = FALSE;
        m_bSurfaceRequiresOverlay = FALSE;
    }

    memset(&m_rcLast, 0, sizeof(m_rcLast));

    // Store actual settings
    m_bmi = *bmi;

    // for _ConstructRects
    m_bmiLastBlt = m_bmi;

    m_bVideoSurface2 = TRUE;

    // Schedule a recompute clip after I set m_bmiLastBlt to remove
    // deafault alpha site.  This prevents us from playing in alpha
    // blending mode for local clips.
    if(m_pSite->m_pTopLevelSite)
        m_pSite->m_pTopLevelSite->ScheduleCallback(CLIP, 0);

    m_bmi.biSizeImage = m_nDDSurfaceSize;
    m_bmi.biWidth = m_surfaceSize.cx;
    m_bmi.biHeight = m_surfaceSize.cy;
    m_bmi.biCompression = m_nSurfaceCID;
    ulNumBuffers = m_nBackBufferCount+1;

#ifdef _DEBUG
    char szTmp[128]; /* Flawfinder: ignore */
    char acid[5][5] = {{"I420"}, {"YV12"}, {"YVU9"}, {"YUY2"}, {"UYVY"}}; /* Flawfinder: ignore */
    char dvpf[5] = "DVPF"; /* Flawfinder: ignore */
    char *pCurrent = dvpf;

    if (m_nSurfaceCID < 5)
        pCurrent = acid[m_nSurfaceCID];

    wsprintf(szTmp, "SetProperties Create %ld %s buffers\n", ulNumBuffers, pCurrent);
    OutputDebugString(szTmp);
#endif

    HXxSize rcSize = {m_bmi.biWidth, m_bmi.biHeight};
    CreateFallbackSurface(m_nSurfaceCID, &rcSize);

    // Kill render thread if running
    if (m_hRenThread)
    {
        m_bCont = FALSE;
        SetEvent(m_hAbort);

        WaitForSingleObject(m_hRenThread, INFINITE);

        CloseHandle(m_hRenThread);
        m_hRenThread = 0;

        CloseHandle(m_hFrameSem);
        m_hFrameSem = 0;
    }

    HX_RELEASE(m_pTimeLine);

    m_pTimeLine = pTimeLine;
    if (m_pTimeLine)
        m_pTimeLine->AddRef();

    // Create the render thread
    DWORD   dwId;
    m_bCont = TRUE;
    ResetEvent(m_hAbort);
    m_hFrameSem = CreateSemaphore(NULL, 0, ulNumBuffers+1, NULL);

    m_hRenThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE )ThreadProc,
                                this, 0, &dwId);

    if (m_bBoostRenderThread)
        SetThreadPriority(m_hRenThread, THREAD_PRIORITY_TIME_CRITICAL);

    ReleaseMutex(m_hSurfaceMutex);
    return HXR_OK;
}

BOOL CWinSurface2::SetYUVPriorityList(int cidIn)
{
    IHXPreferences*    pPreferences    = NULL;
    IHXBuffer*         pBuffer         = NULL;

    BOOL bUsePriorityScheme = TRUE;
    BOOL bRestore = FALSE;
    BOOL bUseHWPF = FALSE;

    if (HXR_OK == m_pContext->QueryInterface(IID_IHXPreferences,(void**)&pPreferences))
    {
        // Disalbe our surface priority scheme?
        if (pPreferences->ReadPref("VideoBoost\\DisableSurfacePriority", pBuffer) == HXR_OK)
        {
            bUsePriorityScheme = !(::atoi((char*) pBuffer->GetBuffer()) == 1);
        }
        HX_RELEASE(pBuffer);

        HX_RELEASE(pPreferences);
    }

    // Set the priority for the yuv format we use
    if (bUsePriorityScheme)
    {
        int aList[5] = {0,0,0,0,0};
        int *pList = aList;
        int nListSize = 5;

        bRestore = FALSE;

        if (NOERROR == WinDraw2_GetColorspacePriorities(cidIn, pList, nListSize) && nListSize)
        {
            SetColorspacePriorities(aList, nListSize, cidIn);
            bRestore = TRUE;
        }
    }

    return bRestore;
}

STDMETHODIMP CWinSurface2::GetVideoMem(VideoMemStruct* pVidStruct,
                                      UINT32 ulFlags)
{

    memset(pVidStruct, 0, sizeof(*pVidStruct));

    HXBitmapInfoHeader* bmi = &pVidStruct->bmi;
    UCHAR* pVidMem;
    INT32 nPitch = 0;

    // If we lost our surface, re-create them (if we are not
    // in the process of switching overlay via the OverlayManager or
    // in gdi because we lost DirectDraw).
    if (!m_surface.dd.lpDDSurface &&
        !m_bFallbackSurfaceCreated &&
        !m_bSwitchingingOverlay &&
        !m_bLostHWAcceleration)
    {
        // Make sure we have no locked surfaces
        if (WAIT_OBJECT_0 != WaitForSingleObject(m_hSurfaceMutex, 0))
        {
            if (ulFlags & HX_WAIT_NEVER)
                return HXR_BUFFER_NOT_AVAILABLE;

            WaitForSingleObject(m_hSurfaceMutex, INFINITE);
        }

        ReInitSurfaces();

        // Did not work...return an error
        if (!m_surface.dd.lpDDSurface &&
            !m_bFallbackSurfaceCreated &&
            !m_bGdiSurfaceCreated)
        {
            ReleaseMutex(m_hSurfaceMutex);
            return HXR_BUFFER_NOT_AVAILABLE;
        }

        ReleaseMutex(m_hSurfaceMutex);
    }

    // Return an error if there are not buffers available
    if (ulFlags & HX_WAIT_NEVER)
    {
        // Check m_hReinit is signaled.  If not, return an error
        if (WAIT_OBJECT_0 != WaitForSingleObject(m_hReinit, 0))
            return HXR_BUFFER_NOT_AVAILABLE;

        // Make sure we have no locked surfaces
        if (WAIT_OBJECT_0 != WaitForSingleObject(m_hSurfaceMutex, 0))
            return HXR_BUFFER_NOT_AVAILABLE;

        // If we lost our surface, return an error
        if (!m_surface.dd.lpDDSurface &&
            !m_bFallbackSurfaceCreated &&
            !m_bGdiSurfaceCreated)
        {
            ReleaseMutex(m_hSurfaceMutex);
            return HXR_BUFFER_NOT_AVAILABLE;
        }
    }
    // Wait until a buffer is available
    else
    {
        // Wait until our surfaces are created
        WaitForSingleObject(m_hReinit, INFINITE);

        // Wait for last lock
        WaitForSingleObject(m_hSurfaceMutex, INFINITE);

        // If we lost our surface, return an error
        if (!m_surface.dd.lpDDSurface &&
            !m_bFallbackSurfaceCreated &&
            !m_bGdiSurfaceCreated)
        {
            ReleaseMutex(m_hSurfaceMutex);
            return HXR_BUFFER_NOT_AVAILABLE;
        }
    }

    if (m_bFlushing || m_bWasInVidSurf2)
    {
        ReleaseMutex(m_hSurfaceMutex);
        return HXR_BUFFER_NOT_AVAILABLE;
    }

    INT32 nIndex = -1;
    HX_RESULT hr = HXR_OK;

    pVidMem = NULL;
    nPitch = 0;

    if (m_pSite->_BordersActive() |
        m_pSite->_FadeTransitionActive() |
        (m_pSite->AreVideoControlsActive() && !m_bUseDDColorControls) |
        m_pSite->IsHigherZOrderTranstitionActive() |
        (m_LinkedSites.GetCount() > 0) |
        (m_pLinkedOverlay != NULL))
    {
        // Drop to VideoSurface1 for transitions
        if (m_pSite->m_pUser)
        {
            //Can't satisfy a HX_WAIT_NEVER and block without a
            //response object. :-)
            if( ulFlags & HX_WAIT_NEVER )
            {
                ReleaseMutex(m_hSurfaceMutex);
                return HXR_BUFFER_NOT_AVAILABLE;
            }

            HXxEvent event = {HX_SURFACE_MODE_CHANGE, NULL, NULL,
                              (void*)HX_VIDEOSURFACE1_RECOMMENDED, 0, 0};

            m_bWasInVidSurf2 = TRUE;

            _WaitForFlush();

            // Unlock the surface mutex so we can be torn down
            ReleaseMutex(m_hSurfaceMutex);

            m_pSite->m_pUser->HandleEvent(&event);

            // The renderer switched to VideoSurface1 so don't give them a buffer
            if (event.handled)
            {
                // Reset or last scale since we are not bltting for a time
                m_dLastXScale = 0.0;
                m_dLastYScale = 0.0;

                return HXR_BUFFER_NOT_AVAILABLE;
            }
            else
            {
                m_bWasInVidSurf2 = FALSE;

                // Need to lock the mutex since the switch failed
                WaitForSingleObject(m_hSurfaceMutex, INFINITE);

                CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
                WINDRAW* pWindraw = pSurface->GetWinDraw();

                WinDraw_ResetSurfaceIndexes(pWindraw, &m_surface);
            }
        }
    }

    // Can we restore DirectDraw
    if (m_bLostHWAcceleration)
    {
        CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
        m_bLostHWAcceleration = !pSurface->_IsHardwareAccelerationAvail();

        if (!m_bLostHWAcceleration)
        {
            if (HXR_OK != WaitForQueuedFrames())
                Flush();

            DestroyGdiSurface();
            pSurface->_AcquireHardwareAcceleration();
            ReInitSurfaces();
        }
    }

LOCK_VIDEO_BUFFER:

    int nBltMode = GetBestBltMode();

    BOOL bLockTLSMutex = FALSE;
    bLockTLSMutex = CanLockSurface(nIndex, nBltMode, (ulFlags & HX_WAIT_NEVER) != HX_WAIT_NEVER);

    if (m_bFlushing)
    {
        ReleaseMutex(m_hSurfaceMutex);
        return HXR_BUFFER_NOT_AVAILABLE;
    }

    if (bLockTLSMutex)
    {
        BOOL bRet = m_pSite->_TLSIsLocked();

        if( bRet && (ulFlags&HX_WAIT_NEVER) )
        {
            ReleaseMutex(m_hSurfaceMutex);
            return HXR_BUFFER_NOT_AVAILABLE;
        }
        m_pSite->_TLSLock();
    }
    else if (ulFlags & HX_WAIT_NEVER)
    {
        ReleaseMutex(m_hSurfaceMutex);
        return HXR_BUFFER_NOT_AVAILABLE;
    }
    else
    {
        //XXXgfw Removing a noisy assert for now. It appears that
        //NVidea GForce 2 MX 100/200 cards will fail to lock a surface
        //while resizing a video. This failure does not lead to video
        //artifacts or any other visible problems. However, with the
        //assert it makes it nearly impossible to debug.
        //HX_ASSERT(!bLockTLSMutex && (ulFlags & HX_WAIT_FOREVER));

	// Some "error" is preventing us from locking the surface
        ReleaseMutex(m_hSurfaceMutex);
        return HXR_BUFFER_NOT_AVAILABLE;
    }

    // Use system mem if selected
    if (m_bUseSysMemSurface)
    {
        hr = LockSysMemSurface(pVidMem, nPitch, (ulFlags & HX_WAIT_NEVER) != HX_WAIT_NEVER);

        if (HXR_OK == hr)
        {
            //m_bmi.biCompression = m_nSysMemSurfID;
            m_bmi.biWidth = m_allocatedSysMemSurfSize.cx;
            m_bmi.biHeight = m_allocatedSysMemSurfSize.cy;

            *bmi = m_bmi;
            bmi->biCompression = m_nSysMemSurfID;
        }
    }
    // If DD is not avail, use rgb blts
    else if (nBltMode == HX_BASIC_BLT)
    {
        hr = LockGdiSurface(pVidMem, nPitch, (ulFlags & HX_WAIT_NEVER) != HX_WAIT_NEVER);

        if (HXR_OK == hr)
        {
            MakeBitmap((LPBITMAPINFO)&m_bmi, sizeof(BMI), m_nGdiSurfaceCID,
             m_allocatedGdiSize.cx, m_allocatedGdiSize.cy, NULL, NULL);

            *bmi = m_bmi;
            m_bmiLastBlt = m_bmi;
        }
        else if (DDERR_WRONGMODE == hr)
        {
            HandleDisplayModeChange();
        }
    }

    // If overlay is not enabled, use offscreen memory
    else if (nBltMode == HX_BLT_YUV_STRETCH)
    {
        hr = LockFallbackSurface(pVidMem, nPitch, (ulFlags & HX_WAIT_NEVER) != HX_WAIT_NEVER);

        if (HXR_OK == hr)
        {
            m_nScratchIndex = nIndex;

            MakeBitmap((LPBITMAPINFO)&m_bmi, sizeof(BMI), m_nFallbackSurfaceCID,
             m_allocatedFallbackSize.cx, m_allocatedFallbackSize.cy, NULL, NULL);

            *bmi = m_bmi;
            m_bmiLastBlt = m_bmi;

            // Restore thread priority
            if (m_nLastBltMode == HX_BASIC_BLT && m_bBoostRenderThread)
                SetThreadPriority(m_hRenThread, THREAD_PRIORITY_TIME_CRITICAL);

        }
        else if (DDERR_WRONGMODE == hr)
        {
            HandleDisplayModeChange();
        }
        // Check if we lost DirectDraw
        else
        {
            if (HandleDirectDrawLoss(bLockTLSMutex))
            {
                if (bLockTLSMutex)
                    bLockTLSMutex = FALSE;

                goto LOCK_VIDEO_BUFFER;
            }
        }
    }
    else
    {
        CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
        WINDRAW* pWindraw = pSurface->GetWinDraw();

        hr = WinDraw_GetLockedSurface(pWindraw, &m_surface, pVidMem, nIndex,
                                      (ulFlags & HX_WAIT_NEVER) != HX_WAIT_NEVER);

        if (pVidMem)
        {
            nPitch = m_surface.dd.lPitch;

            MakeBitmap((LPBITMAPINFO)&m_bmi, sizeof(BMI), m_nSurfaceCID,
             m_surfaceSize.cx, m_surfaceSize.cy, NULL, NULL);

            *bmi = m_bmi;
            m_bmiLastBlt = m_bmi;

            if (m_pHwMemObj)
            {
                pVidMem = (UCHAR*)m_pHwMemObj->DeviceToRenderer(pVidMem, nPitch);
            }

            // Restore thread priority
            if (m_nLastBltMode == HX_BASIC_BLT && m_bBoostRenderThread)
                SetThreadPriority(m_hRenThread, THREAD_PRIORITY_TIME_CRITICAL);
        }

        // If the display mode was changed on us, destory and recreate the surfaces
        else if (DDERR_WRONGMODE == hr)
        {
            HandleDisplayModeChange();
        }

        // Could not lock a surface
        else if (DDERR_WASSTILLDRAWING == hr)
        {
            hr = HXR_BUFFER_NOT_AVAILABLE;
        }
        // Check if we lost DirectDraw
        else
        {
            if (HandleDirectDrawLoss(bLockTLSMutex))
            {
                if (bLockTLSMutex)
                    bLockTLSMutex = FALSE;

                goto LOCK_VIDEO_BUFFER;
            }
            else
            {
                // Turn off overlay
                m_bMultipleOverlay = TRUE;

                if (bLockTLSMutex)
                    m_pSite->_TLSUnlock();

                goto LOCK_VIDEO_BUFFER;
            }
        }
    }

    if (HXR_OK == hr)
    {
        // TLS mutex should be locked here...
        HX_ASSERT(bLockTLSMutex);

        // Since RGB formats are not supported in MapFourCCtoCID,
        // store the CID in biCompression.
        if (!IsYUV(MapFourCCtoCID(m_bmi.biCompression)))
        {
            bmi->biCompression = GetBitmapColor((LPBITMAPINFO)&m_bmi);
        }

        // Do we need an alpha blending region
        if (m_bDoAlphaCheck && !m_pHwMemObj)
        {
            CreateAlphaList(pVidStruct);
        }

        pVidStruct->pVidMem = pVidMem;
        pVidStruct->lPitch = nPitch;
    }
    else
    {
        // Did not lock a surface
        ReleaseMutex(m_hSurfaceMutex);
        hr = HXR_FAIL;

        if (bLockTLSMutex)
        {
            m_pSite->_TLSUnlock();
        }
    }

    return hr;
}

STDMETHODIMP CWinSurface2::ColorConvert(INT32  fourCCIn,
                                       HXxSize *pSrcSize,
                                       HXxRect *prSrcRect,
                                       SourceInputStruct *pInput,
                                       INT32   fourCCOut,
                                       UCHAR   *pDestBuffer,
                                       HXxSize *pDestSize,
                                       HXxRect *prDestRect,
                                       int     nDestPitch)
{
    // If the dest surface is a normal buffer, perform the transfer
    if (!IsStructured(MapFourCCtoCID(fourCCOut)))
        return CVideoSurface2::ColorConvert(fourCCIn, pSrcSize, prSrcRect,
                                            pInput, fourCCOut, pDestBuffer,
                                            pDestSize, prDestRect, nDestPitch);

    // Handle this proprietary surface write
    HX_ASSERT(m_pHwMemObj);
    m_pHwMemObj->ProcessCommand((UCHAR*)pInput,
                                fourCCIn,
                                pSrcSize,
                                prSrcRect,
                                pDestBuffer,
                                fourCCOut,
                                pDestSize,
                                prDestRect);

    // Need to set pItem->bAlpha if we blended the frame!!!
    if (m_pHwMemObj->QueryOperation() & PERFORMED_HW_ALPHA_BLEND)
    {
        m_bFrameHasHWAlphaBlend = TRUE;
    }

    if (m_bUseDDColorControls)
    {
        m_pSite->CheckColorSettings();

        // Did color controls change
        float fBright = m_pSite->GetBrightness();
        float fContrast = m_pSite->GetContrast();
        float fSaturation = m_pSite->GetSaturation();
        float fHue = m_pSite->GetHue();
        float fSharpness = m_pSite->GetSharpness();

        if (m_Brightness != fBright ||
            m_Contrast != fContrast ||
            m_Saturation != fSaturation ||
            m_Hue != fHue ||
            m_Sharpness != fSharpness)
        {
            CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
            WINDRAW* pWindraw = pSurface->GetWinDraw();

            // Change DD color controls
            WinDrawSurface_SetColorControls(pWindraw,
                                            &m_surface,
                                            fBright,
                                            fContrast,
                                            fSaturation,
                                            fHue,
                                            fSharpness);
            m_Brightness = fBright;
            m_Contrast = fContrast;
            m_Saturation = fSaturation;
            m_Hue = fHue;
            m_Sharpness = fSharpness;
        }
    }

    return HXR_OK;
}


BOOL CWinSurface2::CanLockSurface(INT32 nIndex, int nBltMode, BOOL bBlock)
{
    UCHAR* pVidMem;
    HRESULT hr = HXR_FAIL;

    if (m_bUseSysMemSurface)
    {
        if (m_pSysMemSurf)
        {
            if (bBlock)
            {
                HANDLE  aWait[2] = {m_pSysMemSurf[m_nNextSysMemBuffer].hEmpty, m_hAbort};
                hr = WaitForMultipleObjects(2, aWait, FALSE, INFINITE);
            }
            else
                hr = WaitForSingleObject(m_pSysMemSurf[m_nNextSysMemBuffer].hEmpty, 0);

            if (WAIT_OBJECT_0 == hr)
                hr = HXR_OK;
        }
        else
            hr = HXR_OK;
    }
    // If DD is not avail, use rgb blts
    else if (HX_BASIC_BLT == nBltMode)
    {
        if (m_bGdiSurfaceCreated)
        {
            if (bBlock)
            {
                HANDLE  aWait[2] = {m_GDIsurface.gdi.lpGDIBackBuffer[m_nNextGdiSurface]->hEmpty, m_hAbort};
                hr = WaitForMultipleObjects(2, aWait, FALSE, INFINITE);
            }
            else
                hr = WaitForSingleObject(m_GDIsurface.gdi.lpGDIBackBuffer[m_nNextGdiSurface]->hEmpty, 0);

            if (WAIT_OBJECT_0 == hr)
                hr = HXR_OK;
        }
        else
            hr = HXR_OK;
    }
    else if (HX_BLT_YUV_STRETCH == nBltMode)
    {
        if (bBlock)
        {
            HANDLE  aObjects[2] = {m_hFallbackEmpty, m_hAbort};
            hr = WaitForMultipleObjects(2, aObjects, FALSE, INFINITE);
        }
        else
            hr = WaitForSingleObject(m_hFallbackEmpty, 0);

        if (WAIT_OBJECT_0 == hr)
            hr = HXR_OK;
    }
    else
    {
        CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
        WINDRAW* pWindraw = pSurface->GetWinDraw();

        int nNextIndex = nIndex;
        hr = WinDraw_CanLockSurface(pWindraw, &m_surface, pVidMem, nNextIndex, bBlock);
    }

    if (hr == HXR_OK)
        return TRUE;
    else
        return FALSE;
}

STDMETHODIMP CWinSurface2::ReleaseVideoMem(VideoMemStruct* pVidStruct)
{
    return ReleaseSurface(pVidStruct, TRUE);
}


STDMETHODIMP CWinSurface2::Present(VideoMemStruct *pVidStruct,
                                   INT32 lTime,
                                   UINT32 ulFlags,
                                   HXxRect *prDestRect,
                                   HXxRect *prSrcRect)
{
//#define _DEBUG_TIMEAHEAD
#ifdef _DEBUG_TIMEAHEAD
    UINT32 ulCurrentTime;
    HRESULT timelineResult = m_pTimeLine->GetTimeLineValue(ulCurrentTime);

    static INT32 lMaxQueueAhead = 0;

    if ((timelineResult != HXR_TIMELINE_SUSPENDED)
        && (lTime - (INT32) ulCurrentTime) > lMaxQueueAhead)
    {
        lMaxQueueAhead = lTime - (INT32) ulCurrentTime;
        HX_TRACE("New max queue ahead: %d", lMaxQueueAhead);
    }
#endif // _DEBUG_TIMEAHEAD

    if (m_bWasInVidSurf2)
        return HXR_FAIL;

    m_pSite->m_bBltHasBeenCalled = TRUE;

    if (ulFlags & HX_MODE_IMMEDIATE)
    {
        if (m_pListOfFrames->GetCount())
            return HXR_FAIL;

        else if( (HXEmptyRegion(m_pSite->m_Region) && !pVidStruct->ulCount  && !m_bFrameHasHWAlphaBlend)
                 || !m_pSite->IsSiteVisible())
            return HXR_COULD_NOT_DISPLAY;
        else
        {
            lTime = 0;
        }
    }
    else if (ulFlags & HX_MODE_REFRESH)
    {
        HRESULT hr = HXR_FAIL;

        // We do not have a visible region so we can not refresh
        if( (HXEmptyRegion(m_pSite->m_Region) ) || !m_pSite->IsSiteVisible())
            return HXR_COULD_NOT_DISPLAY;

        // We have not displayed a frame yet so we can not refresh
        else if (HX_NO_BLT == m_nLastBltMode)
            return HXR_COULD_NOT_DISPLAY;

        // If we are waiting on a surface re-init, fail
        else if (WAIT_OBJECT_0 != WaitForSingleObject(m_hReinit, 0))
            return HXR_BUFFER_NOT_AVAILABLE;

        else if (HX_OVERLAY_BLT == m_nLastBltMode)
        {
            // Need to lose overlay...so fail the refresh
            if ((IsShrinking() && !_AllowsOverlayShrinking()) ||
                m_bMultipleOverlay)
                return HXR_FAIL;
        }
        else if (HX_BLT_YUV_STRETCH == m_nLastBltMode)
        {
            // Need to restore overlay...so fail the refresh
            if ((!IsShrinking() || _AllowsOverlayShrinking()) &&
                !m_bMultipleOverlay)
                return HXR_FAIL;
        }

        int      nNumRects   = 0;
        HXxRect* paSrcRects  = NULL;
        HXxRect* paDestRects = NULL;

        // Try to display a queued frame
        if (m_pListOfFrames->GetCount())
        {
            hr = PresentIfReady();
        }

        if (hr != HXR_OK)
        {
            CWinBaseRootSurface *pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();

            if (!pSurface)
                return HXR_FAIL;

            WINDRAW *pWindraw = pSurface->GetWinDraw();

            // If no dest rect is specified, use surface
            if (!prDestRect->right && !prDestRect->bottom)
            {
                HXxSize rcSize = {0,0};
                m_pSite->GetSize(rcSize);

                prDestRect->right = rcSize.cx;
                prDestRect->bottom = rcSize.cy;
            }

            // Refresh the last rgb image
            //if (m_bLostHWAcceleration)
            if (HX_BASIC_BLT == m_nLastBltMode)
            {
                // Do we have a gdi surface to refresh
                if (m_nLastGdiSurface >= 0)
                {
                    // If no src rect is specified, use surface
                    if (!prSrcRect->right && !prSrcRect->bottom)
                    {
                        prSrcRect->right = m_allocatedGdiSize.cx;
                        prSrcRect->bottom = m_allocatedGdiSize.cy;
                    }

                    SetLastScale(prSrcRect, prDestRect);

                    _ConstructRects( *prSrcRect,
                                     *prDestRect,
                                     FALSE,
                                     nNumRects,
                                     &paSrcRects,
                                     &paDestRects);

                    memcpy(&m_lastSrcRect, paSrcRects, sizeof(*paSrcRects)); /* Flawfinder: ignore */

                    // Do an rgb blt for each display rect
                    for( int j=0 ; j<nNumRects ; j++ )
                    {
                        hr = WinDrawSurface_Blt(pWindraw, &m_GDIsurface, (RECT*)&paDestRects[j], (RECT*)&paSrcRects[j], m_nLastGdiSurface);
                    }

                    HX_VECTOR_DELETE(paSrcRects);
                    HX_VECTOR_DELETE(paDestRects);
                }
            }

            // If we have a visible overlay, update it
            else if (WinDraw2_IsSurfaceVisible(pWindraw, &m_surface) && !m_bYUVBlending)
            {
                // If no src rect is specified, use surface
                if (!prSrcRect->right && !prSrcRect->bottom)
                {
                    prSrcRect->right = m_surfaceSize.cx;
                    prSrcRect->bottom = m_surfaceSize.cy;
                }

                SetLastScale(prSrcRect, prDestRect);

                _ConstructRects( *prSrcRect,
                                 *prDestRect,
                                 FALSE,
                                 nNumRects,
                                 &paSrcRects,
                                 &paDestRects);

                if (nNumRects != 1)
                {
                    HXxPoint screenOffset = m_pSite->GetScreenOffset();

                    paSrcRects[0] = *prSrcRect;
                    paDestRects[0] = *prDestRect;

                    paSrcRects[0].left += screenOffset.x;
                    paSrcRects[0].top += screenOffset.y;
                    paSrcRects[0].right += screenOffset.x;
                    paSrcRects[0].bottom += screenOffset.y;
                }

                hr = ForceUpdateOverlay( &paDestRects[0],
                                         &paSrcRects[0], HX_OVER_SHOW|HX_OVER_KEYDEST);

                if (HX_NO_BLT == m_nLastBltMode)
                    hr = HXR_FAIL;
                else
                {
                    memcpy(&m_lastSrcRect, paSrcRects, sizeof(*paSrcRects)); /* Flawfinder: ignore */

                    if (hr == HXR_OK)
                    {
                        if (m_bNeedColorKeyFilled && m_pSite->IsSiteVisible())
                        {
                            _FillColorKey();
                        }
                    }
                }

                HX_VECTOR_DELETE(paSrcRects);
                HX_VECTOR_DELETE(paDestRects);

#if !defined(_GOLD) && 0
                m_TimeStamps.AddSample();
#endif
            }
            else
            {
                // If we are in fallback mode, make them manually redraw
                if (m_bFallbackSurfaceCreated && HX_OVERLAY_BLT!=m_nBltMode && !m_bYUVBlending)
                {
                    // If no src rect is specified, use surface
                    if (!prSrcRect->right && !prSrcRect->bottom)
                    {
                        prSrcRect->right = m_allocatedFallbackSize.cx;
                        prSrcRect->bottom = m_allocatedFallbackSize.cy;
                    }

                    SetLastScale(prSrcRect, prDestRect);

                    _ConstructRects( *prSrcRect,
                                     *prDestRect,
                                     FALSE,
                                     nNumRects,
                                     &paSrcRects,
                                     &paDestRects);

                    memcpy(&m_lastSrcRect, paSrcRects, sizeof(*paSrcRects)); /* Flawfinder: ignore */

                    EnterCriticalSection(&m_csFallback);

                    for( int j=0 ; j<nNumRects ; j++ )
                    {
                        hr = WinDrawSurface_Blt(pWindraw, &m_fallbackSurface, (RECT*)&paDestRects[j], (RECT*)&paSrcRects[j]);
                    }

                    LeaveCriticalSection(&m_csFallback);

                    HX_VECTOR_DELETE(paSrcRects);
                    HX_VECTOR_DELETE(paDestRects);

#if !defined(_GOLD) && 0
                    m_TimeStamps.AddSample();
#endif
                }
            }
        }

        return hr;
    }

    if (m_bFlushing)
    {
        return HXR_COULD_NOT_DISPLAY;
    }

    INT32   nIndex = -1;

    // Add this frame to the list and alert the render thread
    tFrameElement *pItem = new tFrameElement;

    memset(pItem, 0, sizeof(*pItem));
    pItem->lTimeStamp = lTime;
    pItem->nBltMode = HX_OVERLAY_BLT;
    pItem->ulFlags = ulFlags;
    pItem->rcSrc = *prSrcRect;
    pItem->rcDst = *prDestRect;
    pItem->bAlpha = m_bFrameHasHWAlphaBlend;
    pItem->count  = pVidStruct->ulCount;
    pItem->bmi = m_bmi;

    // If we are in system memory mode, we must blt to hardware ourselves
    if (m_bUseSysMemSurface)
    {
        m_bUseSysMemSurface = FALSE;

        BOOL bAlphaCheck = m_bDoAlphaCheck;

        // Grab a hw buffer and convert the sys mem image to hw yuv.
        UCHAR *pScratchVidMem = NULL;
        INT32 nPitch = 0;
        HXBitmapInfoHeader bmiTemp;
        memset( &bmiTemp, 0, sizeof( bmiTemp ) );

        // We need a hw mem buffer, so disable the alpha blending check
        // that returns system mem.
        m_bDoAlphaCheck = FALSE;

        VideoMemStruct vs;
        memset(&vs, 0, sizeof(vs));
        GetVideoMem(&vs, HX_WAIT_FOREVER);

        pScratchVidMem = vs.pVidMem;
        nPitch = vs.lPitch;
        bmiTemp = vs.bmi;

        if (pScratchVidMem)
        {
            HXxSize srcSize = {bmiTemp.biWidth, bmiTemp.biHeight};
            SourceInputStruct input = {&m_pSysMemSurf[m_nNextSysMemBuffer].pBuffer,
                                       &m_nSysMemSurfPitch, 1};

            ColorConvert(m_nSysMemSurfID,
                         &m_allocatedSysMemSurfSize,
                         prSrcRect,
                         &input,
                         bmiTemp.biCompression,
                         pScratchVidMem,
                         &srcSize,
                         prDestRect,
                         nPitch);

            ReleaseSurface(&vs, FALSE);
        }

        m_bDoAlphaCheck = bAlphaCheck;

        if (!pScratchVidMem)
            goto FAILURE;

        pItem->nIndex = m_nNextSysMemBuffer;
        if (++m_nNextSysMemBuffer == m_nSysMemSurfCount)
            m_nNextSysMemBuffer = 0;
    }

    // Alpha blend if necessary
    if (pVidStruct->pAlphaList && pVidStruct->ulCount)
    {
        HXxSize size = {pVidStruct->bmi.biWidth, pVidStruct->bmi.biHeight};

        YuvAlphaBlend(pVidStruct->pAlphaList,
                      pVidStruct->ulCount,
                      pVidStruct->pVidMem,
                      pVidStruct->bmi.biCompression,
                      pVidStruct->lPitch,
                      &size);
    }

    // If our last lock was not using overlay, use our offscreen blt
    if (m_bScratchSurface)
    {
        if (!m_fallbackSurface.dd.lpDDSurface)
            goto FAILURE;

        pItem->nBltMode = HX_BLT_YUV_STRETCH;
        pItem->pAlphaList = pVidStruct->pAlphaList;

        ResetEvent(m_hFallbackEmpty);
    }
    // Our last lock was using gdi
    else if (m_bGDISurface)
    {
        pItem->nBltMode = HX_BASIC_BLT;
        pItem->pAlphaList = pVidStruct->pAlphaList;
        pItem->nIndex = m_nNextGdiSurface;
    }
    else
    {
        // Advance the overlay surface list
        int nNewIndex = nIndex;

        if (!m_surface.dd.lpDDSurface)
            goto FAILURE;

        CWinBaseRootSurface *pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();

        if (!pSurface)
            goto FAILURE;

        WINDRAW *pWindraw = pSurface->GetWinDraw();

        if (HXR_OK != WinDraw_AdvanceSurface (pWindraw, &m_surface, nNewIndex))
            goto FAILURE;

        pItem->nIndex = nNewIndex;
        pItem->nBltMode = HX_OVERLAY_BLT;
    }

    // Increase the mutex lock count so another thread does not lock
    // it before we queue this frame or prevent us from drawing immediately.
    WaitForSingleObject(m_hSurfaceMutex, INFINITE);

    // We delete the alphalist in DisplayFrame in yuv_stretch mode.
    ReleaseSurface(pVidStruct, FALSE, (pItem->nBltMode!=HX_BLT_YUV_STRETCH));

    // Immediate display...don't queue
    if (ulFlags & HX_MODE_IMMEDIATE)
    {
        HRESULT hr;
        tFrameElement lastItem;

        for (int i=0; i<5; i++)
        {
            pItem->dwDDFlags = DDFLIP_WAIT;

            lastItem = *pItem;
            hr = DisplayFrame(pItem);

            // Need to try again
            if (DDERR_WASSTILLDRAWING == hr)
            {
                *pItem = lastItem;
                WaitForSingleObject(m_hAbort, 10);
            }
            // Probably can not async flip, so turn it off
            else if (DDERR_INVALIDPARAMS == hr && m_bUseVBlankFlip)
            {
                *pItem = lastItem;
                m_bUseVBlankFlip = FALSE;
            }
            else
            {
                // Abort this overaly surface on a failure
                if (DD_OK != hr && pItem->nBltMode == HX_OVERLAY_BLT)
                {
                    CWinBaseRootSurface *pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
                    if (!pSurface)
                        goto FAILURE;

                    WINDRAW *pWindraw = pSurface->GetWinDraw();

                    WinDraw_DisplaySurface(pWindraw, &m_surface, pItem->dwDDFlags, pItem->nIndex, TRUE);
                }

                break;
            }
        }

        ReleaseMutex(m_hSurfaceMutex);
        delete pItem;

#if !defined(_GOLD) && 0
        m_TimeStamps.AddSample();
#endif
        return HXR_OK;
    }

    EnterCriticalSection(&m_csList);
    m_pListOfFrames->AddTail((void*)pItem);
    LeaveCriticalSection(&m_csList);

    ReleaseSemaphore(m_hFrameSem, 1, NULL);
    ReleaseMutex(m_hSurfaceMutex);

#if !defined(_GOLD) && 0
    m_TimeStamps.AddSample();
#endif
    return HXR_OK;

  FAILURE:
    delete pItem;

    return HXR_FAIL;

}

STDMETHODIMP_(void) CWinSurface2::Flush()
{
    m_bFlushing = TRUE;

    //This needs to be before the wait on m_hSurfaceMutex because if
    //you hit stop before we start blt'ing frames you could be waiting
    //a long time before it wakes up. At least in the case for live
    //streams where we don't have the server/ff seek back to the
    //nearest keyframe and send data. Some streams have keyframes
    //every 10seconds or so.
    PulseEvent(m_hAbort);

    WaitForSingleObject(m_hSurfaceMutex, INFINITE);

    CWinBaseRootSurface *pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    WINDRAW *pWindraw = pSurface->GetWinDraw();

    // Cancel and pending frames
    WinDraw_CacelPendingDisplay (pWindraw, &m_surface);
    PulseEvent(m_hForceRender);

    Sleep(5);

    // Wait until our queue count reaches 0
    int nCount = m_pListOfFrames->GetCount();

    while (nCount)
    {
        WinDraw_CacelPendingDisplay (pWindraw, &m_surface);
        PulseEvent(m_hAbort);
        PulseEvent(m_hForceRender);

        Sleep(5);

        nCount = m_pListOfFrames->GetCount();
    }

    ResetBufferTimes(0);
    ReleaseMutex(m_hSurfaceMutex);

    m_bFlushing = FALSE;
}

void CWinSurface2::ResetBufferTimes(INT32 nTime)
{
    // Reset all buffer times
    if (m_surface.dwBackBufferCount)
    {
        for (UINT32 i=0; i<m_surface.dwBackBufferCount+1; i++)
            m_surface.dd.lpChain[i].dTimeAvailable = nTime;
    }
}

STDMETHODIMP CWinSurface2::PresentIfReady()
{
    HX_RESULT hr = E_FAIL;

    if (!m_pTimeLine | m_bIngnorePresentIfReady)
        return hr;

    UINT32 ulTime = 0;
    if (HXR_TIMELINE_SUSPENDED != m_pTimeLine->GetTimeLineValue(ulTime))
    {
        if (((m_nLastDisplayTime > 0) && (m_nLastDisplayTime - (INT32)ulTime < 0)))
        {
            #ifdef LOG_JITTER_DATA
            FILE *fp = fopen(JITTER_LOG_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "PresentIfReady Interrupt clock %ld last display %ld\n", ulTime, m_nLastDisplayTime);
                fclose(fp);
            }

            #endif

            PulseEvent(m_hForceRender);
            Sleep(0);

#ifdef _DEBUG
            //OutputDebugString("PresentIfReady Interrupt\n");
#endif

            hr = HXR_OK;
        }
    }

    return hr;
}

HX_RESULT CWinSurface2::DisplayFrame(tFrameElement* pItem)
{
    HRESULT hr = HXR_OK,
            hrTimeLine = HXR_OK;
    HXxSize rcSize = {0,0};

    int     nNumRects   = 0;
    BOOL    bSub1x = FALSE;

    m_pSite->_TLSLock();

    CWinBaseRootSurface *pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();

    if (!pSurface)
    {
        m_pSite->_TLSUnlock();
        return HXR_FAIL;
    }

    WINDRAW *pWindraw = pSurface->GetWinDraw();

    // Ensure our site is visible before displaying
    BOOL    bVisibleSite = (!HXEmptyRegion(m_pSite->m_Region)|| pItem->count || pItem->bAlpha)
                           && m_pSite->IsSiteVisible()
                           && !m_pSite->_FadeTransitionActive();

    if (pItem->nBltMode == HX_OVERLAY_BLT)
    {
        // If no src rect is specified, use surface
        if (!pItem->rcSrc.right && !pItem->rcSrc.bottom)
        {
            pItem->rcSrc.right = m_surfaceSize.cx;
            pItem->rcSrc.bottom = m_surfaceSize.cy;
        }

        // Put the frame on the screen
        int nIndex = pItem->nIndex-1;
        if (nIndex < 0)
            nIndex = m_nBackBufferCount;

        if (pItem->dwDDFlags & DDFLIP_WAIT == DDFLIP_WAIT)
        {
            // Immediate mode blits have no calculated timaAvailable; use
            // the current time:
            pItem->dTimeAvailable = GetMSTickDouble32();
        }

        m_surface.dd.lpChain[nIndex].dTimeAvailable = pItem->dTimeAvailable;
        m_surface.dd.lpChain[pItem->nIndex].dTimeAvailable = 0;

        hr = WinDraw_DisplaySurface(pWindraw, &m_surface, pItem->dwDDFlags, pItem->nIndex, m_bFlushing);

#ifdef LOG_JITTER_DATA
        UINT32 ulTimeAfter = 0;
        m_pTimeLine->GetTimeLineValue(ulTimeAfter);

        FILE *fp = fopen(JITTER_LOG_FILE, "a+");
        if (fp)
        {
            UINT32 dwScanLine = 0;
            WinDraw2_GetScanLine(pWindraw, &dwScanLine);

            fprintf(fp, "Calling flip: scan line %ld clock after %ld ts %ld jitter %ld\n\n", dwScanLine, ulTimeAfter, pItem->lTimeStamp, (INT32)(pItem->lTimeStamp-ulTimeAfter));
            fclose(fp);
        }
#endif //LOG_JITTER_DATA

        if (DD_OK == hr && bVisibleSite)
        {
            m_nLastBltMode = HX_OVERLAY_BLT;

            // Make sure we are painting colorkey in fullscreen mode.
            // We will fix this when fillcolor key is fixed.
            if (m_pSite->m_pTopLevelSite &&
                m_pSite->m_pTopLevelSite->m_bInFullScreen)
            {
                if (++m_ulFillColoryKeyCount < 15)
                    memset(&m_rcLast, 0, sizeof(m_rcLast));
            }
            else
                m_ulFillColoryKeyCount = 0;

            if (!IsShrinking() || _AllowsOverlayShrinking())
            {
                // Restore overlay
                if (HX_OVERLAY_BLT != m_nBltMode)
                {
                    ForceGDIMode(FALSE);
                }

                // If no src rect is specified, use surface
                if (!pItem->rcDst.right && !pItem->rcDst.bottom)
                {
                    m_pSite->GetSize(rcSize);

                    pItem->rcDst.right = rcSize.cx;
                    pItem->rcDst.bottom = rcSize.cy;
                }

                SetLastScale(&pItem->rcSrc, &pItem->rcDst);

                double dScaleX = m_scaleFactorX,
                       dScaleY = m_scaleFactorY;

                // Setup new src and dest rects....
                _ConstructRects( pItem->rcSrc,
                                 pItem->rcDst,
                                 FALSE,
                                 nNumRects,
                                 &m_paSrcRects,
                                 &m_paDestRects);

                if (nNumRects != 1)
                {
                    HXxPoint screenOffset = m_pSite->GetScreenOffset();

                    if (!m_paSrcRects)
                        m_paSrcRects = (HXxRect*)malloc(sizeof(HXxRect));

                    if (!m_paDestRects)
                        m_paDestRects = (HXxRect*)malloc(sizeof(HXxRect));;

                    m_paSrcRects[0] = pItem->rcSrc;
                    m_paDestRects[0] = pItem->rcDst;

                    m_paSrcRects[0].left += screenOffset.x;
                    m_paSrcRects[0].top += screenOffset.y;
                    m_paSrcRects[0].right += screenOffset.x;
                    m_paSrcRects[0].bottom += screenOffset.y;
                }

                if (m_paSrcRects && m_paDestRects)
                {
                    ForceUpdateOverlay(&m_paDestRects[0], &m_paSrcRects[0], HX_OVER_SHOW|HX_OVER_KEYDEST, TRUE);
                }

                if (m_bNeedColorKeyFilled)
                {
                    m_bDisableFillColorKey = FALSE;
                    _FillColorKey();
                }

                // Alpha blended images are drawn before the video so the scaling gets messed up on
                // static images.  Force a redraw to correct the problem.
                if ((dScaleX != m_scaleFactorX || dScaleY != m_scaleFactorY) && m_pSite->IsFullScreen())
                {

                    HXxRect dirty = {m_pSite->m_pTopLevelSite->m_topleft.x,
                                     m_pSite->m_pTopLevelSite->m_topleft.y,
                                     m_pSite->m_pTopLevelSite->m_topleft.x+m_pSite->m_pTopLevelSite->m_size.cx,
                                     m_pSite->m_pTopLevelSite->m_topleft.y+m_pSite->m_pTopLevelSite->m_size.cy};

                    m_pSite->m_pTopLevelSite->_RecursiveDamageRect(&dirty, TRUE);
                    m_pSite->m_pTopLevelSite->_ForceRedrawAll();
                }
            }
        }
        else if (HXR_FAIL == hr)
        {
            // Turn off overlay
            m_bMultipleOverlay = TRUE;
        }
    }
    else if (pItem->nBltMode == HX_BLT_YUV_STRETCH)
    {
        // If no src rect is specified, use surface
        if (!pItem->rcSrc.right && !pItem->rcSrc.bottom)
        {
            pItem->rcSrc.right = m_allocatedFallbackSize.cx;
            pItem->rcSrc.bottom = m_allocatedFallbackSize.cy;
        }

        // If no src rect is specified, use surface
        if (!pItem->rcDst.right && !pItem->rcDst.bottom)
        {
            m_pSite->GetSize(rcSize);

            pItem->rcDst.right = rcSize.cx;
            pItem->rcDst.bottom = rcSize.cy;
        }

        SetLastScale(&pItem->rcSrc, &pItem->rcDst);

        if (bVisibleSite && !m_bFlushing)
        {
            m_nLastBltMode = HX_BLT_YUV_STRETCH;

            // Ok, this is great. We need to add the regions from our
            // yuv alphablending rects to our m_Region so that
            // constructrects will return the correct scaled rects for
            // us to blt. We have to do this here because
            // _ConstructRects doesn't know anything about YUV
            // optimized alphablending.  It works in overlay because
            // we have m_pAdditionalColorKey
            //
            HXREGION* pTmp = NULL;
            if( pItem->pAlphaList && m_pSite->m_Region )
            {
                pTmp = HXCreateRegion();
                HXUnionRegion( pTmp, m_pSite->m_Region, pTmp );

                for( int w=0 ; w<pItem->count; w++)
                {
                    HXRECTANGLE pRect;
                    pRect.x      = (short)pItem->pAlphaList[w].rcImageRect.left;
                    pRect.y      = (short)pItem->pAlphaList[w].rcImageRect.top;
                    pRect.width  = (short)HXxRECT_WIDTH(pItem->pAlphaList[w].rcImageRect);
                    pRect.height = (short)HXxRECT_HEIGHT(pItem->pAlphaList[w].rcImageRect);

                    //Add in our offsets from the corner of the
                    //toplevel site.
                    pRect.x += (short)m_pSite->m_topleft.x;
                    pRect.y += (short)m_pSite->m_topleft.y;

                    // We must scale this rect to the scale factor of
                    // this surface. This really only comes into play
                    // if we are shrinking the site to sub 1x and we
                    // are still BLTing a YUV surface.
                    pRect.x      = (int)((double)pRect.x*m_scaleFactorX+.5);
                    pRect.y      = (int)((double)pRect.y*m_scaleFactorY+.5);

                    HXUnionRectWithRegion( &pRect, pTmp, pTmp );
                }
            }
            // Setup new src and dest rects....
            _ConstructRects( pItem->rcSrc,
                             pItem->rcDst,
                             FALSE,
                             nNumRects,
                             &m_paSrcRects,
                             &m_paDestRects,
                             pTmp);

            if( pTmp )
            {
                HXDestroyRegion(pTmp);
            }

            bSub1x = IsShrinking() && !_AllowsOverlayShrinking();

            BOOL bLoseOverlay = FALSE;

            // Lose overlay
            if (HX_OVERLAY_BLT == m_nBltMode && bSub1x)
            {
                bLoseOverlay = TRUE;
                m_bDisableFillColorKey = TRUE;
            }

            CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
            WINDRAW* pWindraw = pSurface->GetWinDraw();

            EnterCriticalSection(&m_csFallback);

            // Do an offscreen blt for each display rect
            for( int j=0 ; j<nNumRects ; j++ )
            {
                hr = WinDrawSurface_Blt(pWindraw, &m_fallbackSurface, (RECT*)&m_paDestRects[j], (RECT*)&m_paSrcRects[j]);

                if (hr != DD_OK)
                {
                    // Some boards fail on yuv blts, so try a new format on strange errors
                    if (DDERR_INVALIDRECT   != hr &&
                        DDERR_INVALIDPARAMS != hr &&
                        DDERR_SURFACEBUSY   != hr &&
                        DDERR_SURFACELOST   != hr)
                    {
                        if (HXR_OK == TryNewFallback())
                        {
                            // we created a new surface, discard this frame
                            break;
                        }
                    }

                    break;
                }
            }

            LeaveCriticalSection(&m_csFallback);

            if (bLoseOverlay)
            {
                m_bDisableFillColorKey = TRUE;
                ForceUpdateOverlay(&m_paDestRects[0], &m_paSrcRects[0], HX_OVER_HIDE|HX_OVER_KEYDEST);

                ForceGDIMode(TRUE);
            }

        }

        if (pItem->pAlphaList)
        {
            delete [] pItem->pAlphaList;
        }

        SetEvent(m_hFallbackEmpty);
    }
    else
    {
        INT32 nLastGdiSurface = m_nLastGdiSurface;

        // If no src rect is specified, use surface
        if (!pItem->rcSrc.right && !pItem->rcSrc.bottom)
        {
            pItem->rcSrc.right = m_allocatedGdiSize.cx;
            pItem->rcSrc.bottom = m_allocatedGdiSize.cy;
        }

        // If no src rect is specified, use surface
        if (!pItem->rcDst.right && !pItem->rcDst.bottom)
        {
            m_pSite->GetSize(rcSize);

            pItem->rcDst.right = rcSize.cx;
            pItem->rcDst.bottom = rcSize.cy;
        }

        SetLastScale(&pItem->rcSrc, &pItem->rcDst);

        if (bVisibleSite && !m_bFlushing)
        {
            m_nLastBltMode = HX_BASIC_BLT;

            // Setup new src and dest rects....
            _ConstructRects( pItem->rcSrc,
                             pItem->rcDst,
                             FALSE,
                             nNumRects,
                             &m_paSrcRects,
                             &m_paDestRects);

            // Do a gdi blt for each display rect
            for( int j=0 ; j<nNumRects ; j++ )
            {
                hr = WinDrawSurface_Blt(pWindraw, &m_GDIsurface, (RECT*)&m_paDestRects[j], (RECT*)&m_paSrcRects[j], pItem->nIndex);
            }

            if (HX_OVERLAY_BLT == m_nBltMode)
            {
                m_bDisableFillColorKey = TRUE;

                m_bDisableFillColorKey = TRUE;
                ForceUpdateOverlay(&m_paDestRects[0], &m_paSrcRects[0], HX_OVER_HIDE|HX_OVER_KEYDEST);

                ForceGDIMode(TRUE);
            }
        }

        m_nLastGdiSurface = pItem->nIndex;

        // Release the lock on this surface after we updated it.  We keep around the last
        // image for refreshing.
        if (nLastGdiSurface >= 0)
        {
            SetEvent(m_GDIsurface.gdi.lpGDIBackBuffer[nLastGdiSurface]->hEmpty);
        }
    }

    //We are done blt'ing all our rects. Lets notify everyone that
    //might be interested in alpha blending that we changed.
    int nTot = m_pSite->m_AlphaBlendNotifiers.GetCount();
    if( nTot>0 && !m_pSite->IsCompositionMode() && m_bOffBecauseofShinking)
    {
        //XXXgfw Once we have optimized YUV blenders, do even bother
        //calling this as YUV blends from the bottom up and all this
        //has already been done.
        if( m_pSite && m_pSite->m_pTopLevelSite )
            m_pSite->m_pTopLevelSite->ScheduleCallback(REDRAW_ALL, 0);
    }


    m_pSite->_TLSUnlock();

    if (DDERR_WRONGMODE == hr)
        hr = HXR_OK;

    // Update blt stats
    if (HXR_OK == hr && !m_bFlushing)
    {
        UpdateBltStats(&pItem->rcDst);
    }

    return hr;
}

DWORD WINAPI CWinSurface2::ThreadProc(CWinSurface2* pThis)
{
    return pThis->RenThread();
}

DWORD CWinSurface2::RenThread()
{
    BOOL    bMeasureJitter = FALSE;
    HRESULT hr;
    tFrameElement *pItem;
    HANDLE  aObjects[2] = {m_hFrameSem, m_hAbort},
    aRenderObjects[2] = {m_hForceRender, m_hAbort};
    UINT32  nResolution = SCHEDULE_GRANULE;
    tFrameElement lastItem;

#ifdef ENABLE_ERROR_TRACE
    g_ulFrameCount = 0;
    g_ulSleepCount = 0;
#endif // ENABLE_ERROR_TRACE

    // Read some preferences
    IHXPreferences*    pPreferences    = NULL;
    IHXBuffer*         pBuffer         = NULL;

#if !defined(_GOLD) && 0
    CTimeSample  sample;
    sample.Init(m_pContext, (void*)this);
#endif

    if (HXR_OK == m_pContext->QueryInterface(IID_IHXPreferences,(void**)&pPreferences))
    {
        // Use vblank flags for flipping
        if (pPreferences->ReadPref("VideoBoost\\MeasureJitter", pBuffer) == HXR_OK)
            bMeasureJitter = ::atoi((char*) pBuffer->GetBuffer());

        HX_RELEASE(pBuffer);

        if (pPreferences->ReadPref("VideoBoost\\SchedulerResolution", pBuffer) == HXR_OK)
            nResolution = ::atoi((char*) pBuffer->GetBuffer());

        if (pPreferences->ReadPref("VideoBoost\\IgnorePresentIfReady", pBuffer) == HXR_OK)
            m_bIngnorePresentIfReady = ::atoi((char*) pBuffer->GetBuffer());

        HX_RELEASE(pPreferences);
    }

    CWinBaseRootSurface *pSurface = NULL;
    WINDRAW *pWindraw = NULL;

    pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    if (pSurface)
    {
        pWindraw = pSurface->GetWinDraw();
        pWindraw->nSchedulerResolution = nResolution;
    }

#ifdef HARDCODE_GRANULE
    if (nResolution)
        timeBeginPeriod(nResolution);
#endif //HARDCODE_GRANULE

    // Make sure timeline is active. If Wait returns and error,
    // an abort message work it up, so check if our thread is
    // supposed to keep running, and if so, wait again.
    while (HXR_OK != WaitForTimeLine(0) && m_bCont && !m_bFlushing)
        continue;

    while (m_bCont)
    {
        m_nLastDisplayTime = -1;

        // Wait for a frame or an abort
        hr = WaitForMultipleObjects(2, aObjects, FALSE, INFINITE);

        if (hr - WAIT_OBJECT_0 == 0 || hr - WAIT_OBJECT_0 == 1)
        {
            if (m_pListOfFrames->GetCount())
            {
                // Grab the first frame off the list
                EnterCriticalSection(&m_csList);
                pItem = (tFrameElement*)m_pListOfFrames->GetHead();
                LeaveCriticalSection(&m_csList);

                EnterCriticalSection(&m_csDisplay);

TIME_AND_BLT_AGAIN:
                pItem->dwDDFlags = 0;

                pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
                HX_ASSERT(pSurface);

                // The root surface may have been destroyed
                if (pSurface)
                {
                    pWindraw = pSurface->GetWinDraw();

                    if (m_bQueryMonitorFreq)
                    {
                        HX_ASSERT(pWindraw->dRefreshRate != 0);
                        HX_ASSERT(pWindraw->dwReportedHeight);
                    }

                    // Blocks until display time
                    _ScheduleFrameForDisplay(pWindraw, pItem, nResolution);

                    // Preserve our frame descriptor
                    lastItem = *pItem;
                    hr = DisplayFrame(pItem);

                    // Check if the root surface was destroyed during the scheduling
                    pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
                    if (pSurface)
                        pWindraw = pSurface->GetWinDraw();

                    // Need to try again
                    if (DDERR_WASSTILLDRAWING == hr)
                    {
                        // Restore our frame descriptor
                        *pItem = lastItem;
                        WaitForMultipleObjects(2, aRenderObjects, FALSE, 10);
                        goto TIME_AND_BLT_AGAIN;
                    }
                    // Probably can not async flip, so turn it off
                    else if (DDERR_INVALIDPARAMS == hr && m_bUseVBlankFlip)
                    {
                        // Restore our frame descriptor
                        *pItem = lastItem;
                        m_bUseVBlankFlip = FALSE;
                        goto TIME_AND_BLT_AGAIN;
                    }
                    else if (DD_OK != hr)
                    {
                        // Need to discard the overlay
                        if (pItem->nBltMode == HX_OVERLAY_BLT && pSurface)
                        {
                            WinDraw_DisplaySurface(pWindraw, &m_surface, pItem->dwDDFlags, pItem->nIndex, TRUE);
                        }

                        hr = DD_OK;
                    }

                    if (m_bQueryMonitorFreq &&
                        DD_OK == hr &&
                        pSurface)
                    {
                        m_bQueryMonitorFreq = FALSE;
                        WinDraw2_GetMonitorProperties(pWindraw);
                    }

                    if (HXR_OK == hr)
                    {
                        // Delete the frame element
                        EnterCriticalSection(&m_csList);
                        pItem = (tFrameElement*)m_pListOfFrames->RemoveHead();
                        LeaveCriticalSection(&m_csList);

                        delete pItem;
                        pItem = NULL;
                    }
                }

                LeaveCriticalSection(&m_csDisplay);
            }
        }
    }

#ifdef ENABLE_ERROR_TRACE

    // Calculate mean and std. dev:
    double dSum = 0;
    double dAbsSum = 0;
    double dMin = 1000000;
    double dMax = -1000000;

    UINT32 i;
    // Skip first and last ten frames:
    for (i = 10; i < min(g_ulFrameCount - 10, 10000); i++)
    {
        dSum += g_error[i][1];
        dAbsSum += abs(g_error[i][1]);

        if (g_error[i][1] < dMin)
        {
            dMin = g_error[i][1];
        }
        if (g_error[i][1] > dMax)
        {
            dMax = g_error[i][1];
        }
    }

    HX_TRACE("Min:\t%f\n", dMin);
    HX_TRACE("Max:\t%f\n", dMax);

    double dMean = dSum / min(g_ulFrameCount - 20, 10000);
    HX_TRACE("Mean:\t%f\n", dMean);
    HX_TRACE("Abs. Mean:\t%f\n", dAbsSum / min(g_ulFrameCount - 20, 10000));

    double dStdDev = 0;
    for (i = 10; i < min(g_ulFrameCount - 10, 10000); i++)
    {
        dStdDev += pow(dMean - g_error[i][1], 2);
    }

    dStdDev /= min(g_ulFrameCount -20, 10000) - 1;
    dStdDev = sqrt(dStdDev);

    HX_TRACE("Std. dev:\t%f\n", dStdDev);

    // Calculate alternate mean and std. dev:
    dSum = 0;
    dMin = 1000000;
    dMax = -1000000;

    for (i = 10; i < min(g_ulFrameCount - 10, 10000); i++)
    {
        dSum += g_alternateError[i];

        if (g_alternateError[i] < dMin)
        {
            dMin = g_alternateError[i];
        }
        if (g_alternateError[i] > dMax)
        {
            dMax = g_alternateError[i];
        }
    }

    HX_TRACE("Alternate min:\t%f\n", dMin);
    HX_TRACE("Alternate Max:\t%f\n", dMax);

    dMean = dSum / min(g_ulFrameCount - 20, 10000);
    HX_TRACE("Alternate mean:\t%f\n", dMean);

    dStdDev = 0;
    for (i = 10; i < min(g_ulFrameCount - 10, 10000); i++)
    {
        dStdDev += pow(dMean - g_alternateError[i], 2);
    }

    dStdDev /= min(g_ulFrameCount - 20, 10000) - 1;
    dStdDev = sqrt(dStdDev);

    HX_TRACE("Alternate std. dev:\t%f\n", dStdDev);

    DumpErrorEntries();
#endif // ENABLE_ERROR_TRACE

#ifdef ENABLE_SYNC_TRACE
    DumpSyncEntries();
#endif // ENABLE_SYNC_TRACE

#ifdef HARDCODE_GRANULE
    if (nResolution)
        timeEndPeriod(nResolution);
#endif //HARDCODE_GRANULE

    return 0;
}

BOOL CWinSurface2::GetColorConverter(int cIdIn, int cIdOut)
{
    //grab the non padded converters...
    BOOL bConverter = zm_pColorAcc->CheckColorConverter(cIdIn, cIdOut);
    m_pSite->ColorConverterRequest(cIdIn, cIdOut, bConverter);

    return bConverter;
}

BOOL CWinSurface2::GetColorConverter2(int cIdIn, int cIdOut)
{
    //grab the padded converters...
    BOOL bConverter = zm_pColorAcc->CheckColorConverter2(cIdIn, cIdOut);
    m_pSite->ColorConverterRequest(cIdIn, cIdOut, bConverter);

    return bConverter;
}

int CWinSurface2::ColorConvert(INT32 cidOut, unsigned char *dest_ptr,
                               int dest_width, int dest_height,
                               int dest_pitch, int dest_x, int dest_y,
                               int dest_dx, int dest_dy,
                               INT32 cidIn, unsigned char *src_ptr,
                               int src_width, int src_height,
                               int src_pitch, int src_x, int src_y,
                               int src_dx, int src_dy)
{
    return zm_pColorAcc->ColorConvert(cidOut, dest_ptr,
                                      dest_width, dest_height,
                                      dest_pitch, dest_x, dest_y,
                                      dest_dx, dest_dy,
                                      cidIn, src_ptr,
                                      src_width, src_height,
                                      src_pitch, src_x, src_y,
                                      src_dx, src_dy);
}

int CWinSurface2::ColorConvert2(INT32 cidOut, unsigned char *dest_ptr,
                                int dest_width, int dest_height,
                                int dest_pitch, int dest_x, int dest_y,
                                int dest_dx, int dest_dy,
                                INT32 cidIn, unsigned char *pY,
                                unsigned char *pU, unsigned char *pV,
                                int src_width, int src_height,
                                int yPitch, int uPitch, int vPitch,
                                int src_x, int src_y, int src_dx, int src_dy)
{
    return zm_pColorAcc->ColorConvert2(cidOut, dest_ptr,
                                       dest_width, dest_height,
                                       dest_pitch, dest_x, dest_y,
                                       dest_dx, dest_dy,
                                       cidIn, pY, pU, pV,
                                       src_width, src_height,
                                       yPitch, uPitch, vPitch,
                                       src_x, src_y, src_dx, src_dy);
}

HX_RESULT CWinSurface2::RelinquishOverlay()
{
    // Prevent access to video memory while we tear down our surfaces
    WaitForSingleObject(m_hSurfaceMutex, INFINITE);

    m_bSwitchingingOverlay = TRUE;
    WaitForQueuedFrames();

    HX_RESULT res = CWinSurface::RelinquishOverlay();

    if (res == HXR_OK)
    {
        // Create a fallback and always use it
        if (m_bVideoSurface2)
        {
            CreateFallbackSurface(m_nFallbackSurfaceCID, &m_allocatedFallbackSize);
            m_bMultipleOverlay = TRUE;
        }

        // Force the renderer who lost overlay to redraw to cover the colorkey
        HXxRect dirty = {m_pSite->m_topleft.x,
                         m_pSite->m_topleft.y,
                         m_pSite->m_topleft.x+m_pSite->m_size.cx,
                         m_pSite->m_topleft.y+m_pSite->m_size.cy};

        m_pSite->m_pTopLevelSite->_RecursiveDamageRect(&dirty, TRUE);
        m_pSite->m_pTopLevelSite->ScheduleCallback(REPAINT, 0);
    }

    m_bSwitchingingOverlay = FALSE;

    ReleaseMutex(m_hSurfaceMutex);

    return res;
}

HX_RESULT CWinSurface2::AcquireOverlay()
{
    m_bSwitchingingOverlay = TRUE;

    HX_RESULT res = HXR_FAIL;

    if (m_bVideoSurface2)
    {
        _ReleaseSurface();
        ReInitSurfaces();

        res = HXR_OK;
    }
    else
        res = CWinSurface::AcquireOverlay();

    m_bSwitchingingOverlay = FALSE;

    return res;
}

HX_RESULT CWinSurface2::CreateFallbackSurface(int nSurfaceCID, HXxSize* pSize, int *pcidList, int nListSize)
{
    if (m_bFallbackSurfaceCreated)
    {
        if (pSize->cx == m_allocatedFallbackSize.cx &&
            pSize->cy == m_allocatedFallbackSize.cy)
        {
            return HXR_OK;
        }
        else
        {
            DestroyFallbackSurface();
        }
    }

    //Get our windraw object from the root surface
    CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();

    if( !pSurface )
        return HXR_FAIL;

    EnterCriticalSection(&m_csFallback);

    pSurface->OpenWindraw();
    WINDRAW* pWindraw = pSurface->GetWinDraw();
    BMI bmi;
    memset(&bmi, 0, sizeof(BMI));

    BOOL    bSuccess = FALSE;
    int     fFlags = WINDRAWSURFACE_DIRECTDRAW | WINDRAWSURFACE_VIDEOMEMORY;
    int     acid[4] = {nSurfaceCID, CID_YV12, CID_YUY2, CID_UYVY};
    int     dwId;

    if (!pcidList)
    {
        pcidList = &acid[0];
        nListSize = sizeof(acid)/sizeof(acid[0]);
    }

    // Try video memory, then system memory
    for (int j=0; j<2; j++, fFlags = WINDRAWSURFACE_DIRECTDRAW)
    {
        // If the dd driver can do color space conversion blts, create a yuv surface
        if (pWindraw->dd.m_caps.dwCaps & DDCAPS_BLTFOURCC)
        {
            // Try to create an offscreen yuv surface
            for (int i=0; i<nListSize && IsYUV(pcidList[i]); i++)
            {
                MakeBitmap((LPBITMAPINFO)&bmi, sizeof(BMI), pcidList[i], pSize->cx, pSize->cy, NULL, NULL);
                memset(&m_fallbackSurface, 0, sizeof(m_fallbackSurface));

                if (NOERROR == WinDraw2_CreateSurface(pWindraw, &m_fallbackSurface, &bmi, fFlags, 0, 0))
                {
                    // Test if blitting to the primary works (but don't let the user see it)
                    RECT rcSrc = {0,0,pSize->cx, pSize->cy};
                    RECT rcDst = {-pSize->cx, -pSize->cy, 0, 0};

                    if (NOERROR == WinDrawSurface_Blt(pWindraw, &m_fallbackSurface, &rcDst, &rcSrc))
                    {
                        bSuccess = TRUE;
                        dwId = pcidList[i];

#ifdef _DEBUG
                        char szTmp[128]; /* Flawfinder: ignore */
                        char cid[6][5] = {{"I420"}, {"YV12"}, {"YVU9"}, {"YUY2"}, {"UYVY"}, {"DVPF"}}; /* Flawfinder: ignore */
                        if (dwId == CID_DVPF)
                            dwId = 5;
                        wsprintf(szTmp, "CreateFallbackSurface Created 1 %s buffers\n", cid[dwId]);
                        OutputDebugString(szTmp);
#endif

                        break;
                    }
                }
            }
        }

        // Try to create an offscreen surface matching the primary format if
        // yuv surface failed.
        if (!bSuccess)
        {
            memset(&bmi, 0, sizeof(BMI));
            WinDraw2_GetDisplayFormat(pWindraw, &bmi);
            int cid = GetBitmapColor((LPBITMAPINFO)&bmi);

            MakeBitmap((LPBITMAPINFO)&bmi, sizeof(BMI), cid, m_pOptimizedFormat->biWidth, m_pOptimizedFormat->biHeight, NULL, NULL);

            // Don't use hardware rgb...it kills performance on some cards
            fFlags = WINDRAWSURFACE_DIRECTDRAW;

            if (NOERROR == WinDraw2_CreateSurface(pWindraw, &m_fallbackSurface, &bmi, fFlags, 0, 0))
            {
                bSuccess = TRUE;
                dwId = cid;

#ifdef _DEBUG
                OutputDebugString("Created RGB offscreen surface\n");
#endif
            }
        }

        if (bSuccess)
        {
            m_allocatedFallbackSize.cx = pSize->cx;
            m_allocatedFallbackSize.cy = pSize->cy;
            m_nFallbackSurfaceCID = dwId;

            m_bFallbackSurfaceCreated = TRUE;

            if (!m_nBitsPerPixel ||
                !m_nHozRes ||
                !m_nVertRes)
            {
                // Store the display mode we were created in.
                m_pSite->_GetDeviceCaps(NULL, m_nBitsPerPixel, m_nHozRes, m_nVertRes);
            }

            if (m_hRenThread)
            {
                // We create our surface, start servicing GetVidMem calls
                SetEvent(m_hReinit);
            }

            LeaveCriticalSection(&m_csFallback);
            return HXR_OK;
        }
    }

#if 1
    LeaveCriticalSection(&m_csFallback);
    return HXR_FAIL;
#else
    // Result to sys mem buffers
    return CreateSysMemSurface(nSurfaceCID, pSize, 1);
#endif
}

HX_RESULT CWinSurface2::DestroyFallbackSurface()
{
    if (!m_bFallbackSurfaceCreated)
        return HXR_OK;

    CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    if (!pSurface)
        return HXR_FAIL;

    WINDRAW* pWindraw = pSurface->GetWinDraw();
    if (!pWindraw)
        return HXR_FAIL;

    EnterCriticalSection(&m_csFallback);

    WinDraw2_ReleaseSurface(pWindraw, &m_fallbackSurface);

    m_bFallbackSurfaceCreated = FALSE;

    LeaveCriticalSection(&m_csFallback);

    return HXR_OK;
}

HX_RESULT CWinSurface2::LockFallbackSurface(UCHAR* &pVidMem, INT32 &nPitch, BOOL bBlock)
{
    if (!bBlock)
    {
        if (WAIT_OBJECT_0 != WaitForSingleObject(m_hFallbackEmpty, 0))
            return HXR_BUFFER_NOT_AVAILABLE;
    }

    HANDLE  aObjects[2] = {m_hFallbackEmpty, m_hAbort};
    if (WaitForMultipleObjects(2, aObjects, FALSE, INFINITE) - WAIT_OBJECT_0 == 0)
    {
        HRESULT hr;
        CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
        WINDRAW* pWindraw = pSurface->GetWinDraw();

        HXxSize rcSize = {m_surfaceSize.cx,m_surfaceSize.cy};

        if (HXR_OK != CreateFallbackSurface(m_nSurfaceCID, &rcSize))
            return HXR_FAIL;

        LONG lPitch;
        hr = WinDrawSurface_Lock (pWindraw, &m_fallbackSurface, 0, (void**)&pVidMem, &lPitch);

        if (hr != HXR_OK)
            return hr;

        m_bScratchSurface = TRUE;
        nPitch = lPitch;
    }
    else
        return HXR_FAIL;

    return HXR_OK;
}

HX_RESULT CWinSurface2::TryNewFallback()
{
    int nNewCID = 0;
    int acid[5] = {m_nFallbackSurfaceCID, 0,0,0,0};

    for (int i=0; i<5; i++)
    {
        // Try a new format for our offscreen buffer ending in rgb
        switch (acid[i])
        {
           case CID_I420:  acid[i+1] = CID_YV12; nNewCID++; break;
           case CID_YV12:  acid[i+1] = CID_YUY2; nNewCID++; break;
           case CID_YUY2:  acid[i+1] = CID_UYVY; nNewCID++; break;
           case CID_UYVY:  acid[i+1] = CID_RGB32; nNewCID++; break;

           default:
               i=5;    // jump out of loop
        }
    }

    if (!nNewCID)
        return HXR_FAIL;

    EnterCriticalSection(&m_csFallback);

    DestroyFallbackSurface();
    HRESULT hr = CreateFallbackSurface(acid[1], &m_allocatedFallbackSize, &acid[1], nNewCID);

    LeaveCriticalSection(&m_csFallback);

    return hr;
}

HX_RESULT CWinSurface2::CreateSysMemSurface(int nSurfaceCID,
                                            HXxSize* pSize,
                                            int nBuffers)
{
    // Do we need to create new buffers?
    if (m_pSysMemSurf)
    {
        if (nBuffers == m_nSysMemSurfCount &&
            pSize->cx == m_bmi.biWidth &&
            pSize->cy == m_bmi.biHeight)
        {
            return HXR_OK;
        }

        DestroySysMemSurface();
    }

    // Calculate the size of each frame buffer
    m_nSysMemSurfSize = pSize->cx * pSize->cy * GetBitsPerPixel(CID_I420) / 8;

    // Allocate the surface list
    m_pSysMemSurf = new tSysMemSurf[nBuffers];
    if (!m_pSysMemSurf)
        return HXR_OUTOFMEMORY;

    memset(m_pSysMemSurf, 0, sizeof(tSysMemSurf)*nBuffers);

    // Allocate each buffer
    for (int i=0; i<nBuffers; i++)
    {
        m_pSysMemSurf[i].pBuffer = new UCHAR[m_nSysMemSurfSize + 31 & ~31];

        if (!m_pSysMemSurf[i].pBuffer)
        {
            DestroySysMemSurface();
            return HXR_OUTOFMEMORY;
        }

        m_pSysMemSurf[i].hEmpty = CreateEvent(NULL, TRUE, TRUE, NULL);

        ++m_nSysMemSurfCount;
    }

    m_allocatedSysMemSurfSize = *pSize;
    m_nSysMemSurfID = CID_I420;
    m_bUseSysMemSurface = TRUE;
    m_nSysMemSurfPitch = pSize->cx;

    if (!m_nBitsPerPixel ||
        !m_nHozRes ||
        !m_nVertRes)
    {
        // Store the display mode we were created in.
        m_pSite->_GetDeviceCaps(NULL, m_nBitsPerPixel, m_nHozRes, m_nVertRes);

    }

    return HXR_OK;
}

HX_RESULT CWinSurface2::LockSysMemSurface(UCHAR* &pVidMem, INT32 &nPitch, BOOL bBlock)
{
    HXxSize rcSize = {m_bmi.biWidth,m_bmi.biHeight};

    if (HXR_OK != CreateSysMemSurface(m_nSurfaceCID, &rcSize, 1))
        return HXR_FAIL;

    if (!bBlock)
    {
        if (WAIT_OBJECT_0 != WaitForSingleObject(m_pSysMemSurf[m_nNextSysMemBuffer].hEmpty, 0))
            return HXR_BUFFER_NOT_AVAILABLE;
    }

    HANDLE  aWait[2] = {m_pSysMemSurf[m_nNextSysMemBuffer].hEmpty, m_hAbort};

    if (WaitForMultipleObjects(2, aWait, FALSE, INFINITE) - WAIT_OBJECT_0 == 0)
    {
        pVidMem = m_pSysMemSurf[m_nNextSysMemBuffer].pBuffer;
        nPitch = m_nSysMemSurfPitch;

        return HXR_OK;
    }
    else
        return HXR_FAIL;
}

HX_RESULT CWinSurface2::DestroySysMemSurface()
{
    if (!m_pSysMemSurf)
        return HXR_OK;

    for (int i=0; i<m_nSysMemSurfCount; i++)
    {
        if (m_pSysMemSurf[i].pBuffer)
        {
            delete [] m_pSysMemSurf[i].pBuffer;
            m_pSysMemSurf[i].pBuffer = NULL;
        }

        if (m_pSysMemSurf[i].hEmpty)
        {
            CloseHandle(m_pSysMemSurf[i].hEmpty);
            m_pSysMemSurf[i].hEmpty = 0;
        }
    }

    delete [] m_pSysMemSurf;
    m_pSysMemSurf = NULL;

    m_nSysMemSurfSize = 0;
    m_nSysMemSurfCount = 0;
    m_nNextSysMemBuffer = 0;
    m_nSysMemSurfPitch = 0;

    m_bUseSysMemSurface = FALSE;

    return HXR_OK;
}

INT32 CWinSurface2::GetBitsPerPixel(int nSurfaceID)
{
    switch (nSurfaceID)
    {
       case CID_YV12:
       case CID_I420:
           return 12;

       case CID_YUY2:
       case CID_UYVY:
           return 16;

       default:
           return 24;
    }
}

HX_RESULT CWinSurface2::CreateGdiSurface(HXxSize* pSize)
{
    HX_RESULT hr = HXR_OK;

    if (m_bGdiSurfaceCreated)
        return hr;

    // Create GDI buffers
    BMI bmi;
    memset(&bmi, 0, sizeof(BMI));
    CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    WINDRAW* pWindraw = pSurface->GetWinDraw();

    WinDraw2_GetDisplayFormat(pWindraw, &bmi);
    int cid = GetBitmapColor((LPBITMAPINFO)&bmi);
    memset(&bmi, 0, sizeof(BMI));
    MakeBitmap((LPBITMAPINFO)&bmi, sizeof(BMI), cid, pSize->cx, pSize->cy, NULL, NULL);

    for (int nBuffers = m_nOverlayBackBuffersCreated; nBuffers; nBuffers--)
    {
        hr = WinDraw2_CreateSurface(pWindraw, &m_GDIsurface, &bmi, 0, nBuffers, 0);

        if (NOERROR == hr)
        {
            m_allocatedGdiSize = *pSize;
            m_nGdiSurfaceCID = cid;
            m_nGdiSurfaceCount = nBuffers+1;
            m_bGdiSurfaceCreated = TRUE;
            m_nLastGdiSurface = -1;

            if (m_hRenThread)
                SetEvent(m_hReinit);

            break;
        }
    }

    return hr;
}

HX_RESULT CWinSurface2::LockGdiSurface(UCHAR* &pVidMem, INT32 &nPitch, BOOL bBlock)
{
    HX_RESULT hr = HXR_FAIL;

    // Create the gdi surface if necessary
    if (!m_bGdiSurfaceCreated)
    {
        HXxSize size = {m_pOptimizedFormat->biWidth,
                        m_pOptimizedFormat->biHeight};
        hr = CreateGdiSurface(&size);

        if (HXR_OK != hr)
            return hr;
    }

    // Do we have a surface available
    if (!bBlock)
    {
        if (WAIT_OBJECT_0 != WaitForSingleObject(m_GDIsurface.gdi.lpGDIBackBuffer[m_nNextGdiSurface]->hEmpty, 0))
            return HXR_BUFFER_NOT_AVAILABLE;
    }

    CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    WINDRAW* pWindraw = pSurface->GetWinDraw();

    // Wait for the next surface
    HANDLE  aWait[2] = {m_GDIsurface.gdi.lpGDIBackBuffer[m_nNextGdiSurface]->hEmpty, m_hAbort};

    if (WaitForMultipleObjects(2, aWait, FALSE, INFINITE) - WAIT_OBJECT_0 == 0)
    {
        LPVOID  pTemp = NULL;
        LONG    lPitch = 0;

        hr = WinDrawSurface_Lock(pWindraw, &m_GDIsurface, m_nNextGdiSurface, &pTemp, &lPitch);

        if (HXR_OK == hr)
        {
            pVidMem = (UCHAR*)pTemp;
            nPitch = lPitch;
            m_bGDISurface = TRUE;
        }
    }

    return hr;
}

HX_RESULT CWinSurface2::DestroyGdiSurface()
{
    if (m_bGdiSurfaceCreated)
    {
        CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
        WINDRAW* pWindraw = pSurface->GetWinDraw();

        if (m_allocatedGdiSize.cx && m_allocatedGdiSize.cy)
        {
            WinDraw2_ReleaseSurface(pWindraw, &m_GDIsurface);
            memset(&m_GDIsurface,0,sizeof(m_GDIsurface));
            memset(&m_allocatedGdiSize,0,sizeof(m_allocatedGdiSize));
            m_nGdiSurfaceCID = 0;
        }

        m_bGdiSurfaceCreated = FALSE;
        m_nLastGdiSurface = -1;
    }

    return HXR_OK;
}

void CWinSurface2::SetLastScale(HXxRect *pSrc, HXxRect *pDest)
{
    EnterCriticalSection(&m_csScale);

    m_dLastXScale = (((double)pDest->right - (double)pDest->left) /
                     ((double)pSrc->right - (double)pSrc->left));
    m_dLastYScale = (((double)pDest->bottom - (double)pDest->top) /
                     ((double)pSrc->bottom - (double)pSrc->top));

    LeaveCriticalSection(&m_csScale);
}

int CWinSurface2::GetBestBltMode()
{
    // Here is our blt mode prority
    // 1. DD overlay
    // 2. DD offscreen (yuv then rgb)
    // 3. GDI
    BOOL    bSub1x = FALSE;

    int     nBltMode = HX_OVERLAY_BLT;
    int     nPreferredBltMode = nBltMode;

    if (IsShrinking() && !_AllowsOverlayShrinking())
        bSub1x = TRUE;

    // Drop to yuv stretch blts if
    // sub1x OR
    // another app has the overlay
    if (bSub1x | m_bMultipleOverlay)
    {
        nBltMode = HX_BLT_YUV_STRETCH;
    }

    // Drop to GDI if
    // DD not available
    if (m_bLostHWAcceleration)
    {
        nPreferredBltMode = nBltMode;
        nBltMode = HX_BASIC_BLT;
    }

    // Can we restore DirectDraw
    /*if (m_bLostHWAcceleration)
    {
        LPWINDRAWSURFACE lpwds = NULL;
        switch(nPreferredBltMode)
        {
            case HX_OVERLAY_BLT: lpwds = &m_surface; break;
            case HX_BLT_YUV_STRETCH: lpwds = &m_fallbackSurface; break;
            default: lpwds = &m_GDIsurface;
        }

        CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
        m_bLostHWAcceleration = !pSurface->_IsHardwareAccelerationAvail(lpwds);

        if (!m_bLostHWAcceleration)
        {
            if (HXR_OK != WaitForQueuedFrames())
                Flush();

            DestroyGdiSurface();
            pSurface->_AcquireHardwareAcceleration();
            ReInitSurfaces();
        }
    }*/

    return nBltMode;
}

BOOL CWinSurface2::IsShrinking()
{
    BOOL bShrink = FALSE;

    EnterCriticalSection(&m_csScale);

    if (m_dLastXScale != 0.0 && m_dLastYScale != 0.0 &&
        (m_dLastXScale < 1.0 || m_dLastYScale < 1.0))
    {
        bShrink = TRUE;
    }

    LeaveCriticalSection(&m_csScale);

    return bShrink;
}

void CWinSurface2::_WaitForFlush()
{
    m_bWaitingForFlush = TRUE;

    // In the transition for VideoSurface2 to VideoSurface1, wait
    // until all frames are displayed.
    if (m_bWasInVidSurf2 && m_pListOfFrames)
    {
        while (m_pListOfFrames->GetCount())
        {
            Sleep(5);
        }

        // When switching to vidsurf1, set the backbuffer
        // to NULL so we do not flip the overlay.  The flipping
        // chain seems to get out of whack in the transition from
        // vidsurf1 to 2.
        m_surface.dd.lpDDBackBuffer = NULL;
    }

    m_bWaitingForFlush = FALSE;
}

HX_RESULT CWinSurface2::ForceUpdateOverlay(HXxRect *pDst, HXxRect *pSrc, DWORD dwFlags, BOOL bCheckPos)
{
    HXxRect destRect,
    srcRect = *pSrc;

    BOOL bUpdate = FALSE;

    if (HX_OVER_SHOW | dwFlags)
    {
        memset( &destRect, 0, sizeof(HXxRect) );

        _GetWindowDeviceCords(&destRect);
        destRect.right = destRect.left + pDst->right;
        destRect.bottom = destRect.top + pDst->bottom;
        destRect.left += pDst->left;
        destRect.top += pDst->top;

        // Ensure we limit update coordinates to screen res
        UINT16 uBitesPerPixel;
        UINT16 uHorzRes;
        UINT16 uVertRes;

        m_pSite->_GetDeviceCaps(NULL, uBitesPerPixel, uHorzRes, uVertRes);

        double ratio;
        if (destRect.left < 0)
        {
            ratio = ((double)abs(destRect.left)) / (double)(destRect.right - destRect.left);
            destRect.left = 0;
            srcRect.left = (int) (((double) m_surfaceSize.cx) * ratio);
        }

        if (destRect.right> uHorzRes)
        {
            ratio = ((double)(destRect.right - uHorzRes)) / (double)(destRect.right - destRect.left);
            destRect.right = uHorzRes;
            srcRect.right = (int) (((double) m_surfaceSize.cx) * (1.0 - ratio));
        }

        if (destRect.top < 0)
        {
            ratio = ((double)abs(destRect.top)) / (double)(destRect.bottom - destRect.top);
            destRect.top = 0;
            srcRect.top = (int) (((double) m_surfaceSize.cy) * ratio);
        }

        if (destRect.bottom > uVertRes)
        {
            ratio = ((double)(destRect.bottom - uVertRes)) / (double)(destRect.bottom - destRect.top);
            destRect.bottom = uVertRes;
            srcRect.bottom = (int) (((double) m_surfaceSize.cy) * (1.0 - ratio));
        }

        if (uBitesPerPixel != m_oldOverlayColorDepth)
        {
            m_oldOverlayColorDepth = uBitesPerPixel;
            // XXXAH Probably do not need this, but keeping it to make sure.
            m_convertedOverlayColor = _InsureColorMatch(GetOverlayColor());
        }

        if (bCheckPos)
        {
            CWinBaseRootSurface *pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
            WINDRAW *pWindraw = pSurface->GetWinDraw();

            // If the overlay is invisible or if it nees to be
            // moved update its position.
            if (!WinDraw2_IsSurfaceVisible(pWindraw, &m_surface) ||

                ((m_lastUpdateDestRect.left   != destRect.left)   |
                 (m_lastUpdateDestRect.right  != destRect.right)  |
                 (m_lastUpdateDestRect.top    != destRect.top)    |
                 (m_lastUpdateDestRect.bottom != destRect.bottom) |
                 (m_lastUpdateSrcRect.left    != srcRect.left)    |
                 (m_lastUpdateSrcRect.right   != srcRect.right)   |
                 (m_lastUpdateSrcRect.top     != srcRect.top)     |
                 (m_lastUpdateSrcRect.bottom  != srcRect.bottom)))
            {
                bUpdate = TRUE;
            }
        }
        else
        {
            bUpdate = TRUE;
        }

        memcpy(&m_lastUpdateDestRect, &destRect, sizeof(HXxRect)); /* Flawfinder: ignore */
        memcpy(&m_lastUpdateSrcRect,  &srcRect,  sizeof(HXxRect)); /* Flawfinder: ignore */
    }

    if (bUpdate)
    {
        _SetColorKey(m_convertedOverlayColor, m_convertedOverlayColor);
        _UpdateOverlay(&destRect, &srcRect, dwFlags);
    }

    return HXR_OK;
}

HX_RESULT CWinSurface2::HandleDisplayModeChange()
{
    UINT16  nBitDepth = 0,
        nHozRes = 0,
        nVertRes = 0;

    m_pSite->_GetDeviceCaps(NULL, nBitDepth, nHozRes, nVertRes);

    // The site handles surface reinit when bitdepth and
    // dimensions change.  If the mode changed in some other
    // way (like refresh rate), handle it here.
    if (m_pSite->zm_bInFullScreenTest ||

        (nBitDepth == m_nBitsPerPixel &&
         nHozRes == m_nHozRes &&
         nVertRes == m_nVertRes &&
         WAIT_OBJECT_0 == WaitForSingleObject(m_hReinit, 0)))
    {
        // Wait for display buffers to render
        Flush();

        DestroySurfaces();
        ReInitSurfaces();

        m_bQueryMonitorFreq = TRUE;
    }
    else
    {
        // Wait on the next GetVidMem call for new surface creation
        ResetEvent(m_hReinit);

        m_nBitsPerPixel = nBitDepth;
        m_nHozRes = nHozRes;
        m_nVertRes = nVertRes;
    }

    return HXR_OK;
}

BOOL CWinSurface2::HandleDirectDrawLoss(BOOL bLockedTLSMutex)
{
    BOOL bRet = FALSE;

    CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    m_bLostHWAcceleration = !pSurface->_IsHardwareAccelerationAvail();

    if (m_bLostHWAcceleration)
    {
        if (bLockedTLSMutex)
            m_pSite->_TLSUnlock();

        DestroySurfaces();
        pSurface->_LoseHardwareAcceleration();

        // RGB blitting is expensive, so drop the render thread priority
        if (m_bBoostRenderThread)
            SetThreadPriority(m_hRenThread, THREAD_PRIORITY_NORMAL);

        bRet = TRUE;
    }

    return bRet;
}

HX_RESULT CWinSurface2::ReleaseSurface(VideoMemStruct* pVidStruct, BOOL bDiscard, BOOL bDeleteAlphaList)
{
    UCHAR* pVidMem = pVidStruct->pVidMem;

    if (pVidStruct->pAlphaList && bDeleteAlphaList)
    {
        delete [] pVidStruct->pAlphaList;

    }

    m_bFrameHasHWAlphaBlend = FALSE;

    CWinBaseRootSurface* pSurface = (CWinBaseRootSurface*)m_pSite->GetRootSurface();
    WINDRAW* pWindraw = pSurface->GetWinDraw();

    if (!m_bUseSysMemSurface)
    {
        if (m_bScratchSurface)
        {
            if (!bDiscard && m_pSite->HasFocusRect())
            {
                pSurface->DrawFocusRect(//&m_fallbackSurface,
                    m_nFallbackSurfaceCID,
                    &m_allocatedFallbackSize,
                    pVidMem,
                    m_pSite);
            }

            WinDrawSurface_Unlock (pWindraw, &m_fallbackSurface, 0);

            if (bDiscard)
                SetEvent(m_hFallbackEmpty);

            m_bScratchSurface = FALSE;
        }
        else if (m_bGDISurface)
        {
            if (!bDiscard && m_pSite->HasFocusRect())
            {
                pSurface->DrawFocusRect(//&m_GDIsurface,
                    m_nGdiSurfaceCID,
                    &m_allocatedGdiSize,
                    pVidMem,
                    m_pSite);
            }

            WinDrawSurface_Unlock (pWindraw, &m_GDIsurface, m_nNextGdiSurface);

            if (!bDiscard)
            {
                ResetEvent(m_GDIsurface.gdi.lpGDIBackBuffer[m_nNextGdiSurface]->hEmpty);

                if (++m_nNextGdiSurface == m_nGdiSurfaceCount)
                    m_nNextGdiSurface = 0;
            }

            m_bGDISurface = FALSE;

        }
        else
        {
            if (m_pHwMemObj)
            {
                pVidMem = (UCHAR*)m_pHwMemObj->RendererToDevice(pVidMem);
                m_bFrameHasHWAlphaBlend = FALSE;
            }

            WinDraw_ReleaseLockedSurface (pWindraw, &m_surface, pVidMem, bDiscard);
        }
    }

    // Unlocked the surface
    ReleaseMutex(m_hSurfaceMutex);
    m_pSite->_TLSUnlock();

    return HXR_OK;
}

HX_RESULT CWinSurface2::WaitForTimeLine(INT32 lTimeStamp)
{
    HX_RESULT hr = HXR_OK;

    if (m_pTimeLine)
    {
        DWORD dwRet = 0;
        UINT32 ulTime = 0;
        HX_RESULT hrTimeLine = m_pTimeLine->GetTimeLineValue(ulTime);

        while (HXR_TIMELINE_SUSPENDED == hrTimeLine &&
               m_bCont &&
               !m_bFlushing &&
               !m_bWaitingForFlush)
        {
            dwRet = WaitForSingleObject(m_hAbort, 5);

            if (WAIT_TIMEOUT != dwRet)
            {
                hr = HXR_FAIL;
                break;
            }
            hrTimeLine = m_pTimeLine->GetTimeLineValue(ulTime);
        }
    }

    return hr;
}

HX_RESULT CWinSurface2::WaitForQueuedFrames()
{
    if (m_pTimeLine)
    {
        UINT32 ulTime = 0;

        if (HXR_TIMELINE_SUSPENDED == m_pTimeLine->GetTimeLineValue(ulTime))
            return HXR_FAIL;
    }

    // Wait until our queue count reaches 0
    int nCount = m_pListOfFrames->GetCount();

    while (nCount)
    {
        Sleep(5);

        nCount = m_pListOfFrames->GetCount();
    }

    return HXR_OK;
}

void CWinSurface2::CreateAlphaList(VideoMemStruct* pVidStruct)
{
    _SetUpBlendRects();

    if(m_imageBlocks.GetCount())
    {
        UCHAR*  pSysBuf = NULL;
        INT32   nPitch = 0;

        HRESULT hr = LockSysMemSurface(pSysBuf, nPitch, TRUE);

        // yuck..."sys mem" mode means we are passing system memory
        // buffers for the video but here we are using it for alpha blending,
        // so we need to set it to false.
        m_bUseSysMemSurface = FALSE;

        if (HXR_OK == hr)
        {
            ImageBlock* pBlock = NULL;

            // Create an alpha struct for every region rect
            pVidStruct->ulCount = m_imageBlocks.GetCount();
            pVidStruct->pAlphaList = new AlphaStruct[pVidStruct->ulCount];

            // Set each alpha struct rect as a pointer to a sys mem buffer
            // the size of one video frame.  rcImageRect is the subrect
            // we need copied.
            CHXSimpleList::Iterator j = m_imageBlocks.Begin();
            for (int i=0; j != m_imageBlocks.End(); ++i, ++j)
            {
                pBlock = (ImageBlock*)*j;

                pVidStruct->pAlphaList[i].rcImageRect = pBlock->rect;
                pVidStruct->pAlphaList[i].ulImageWidth = m_allocatedSysMemSurfSize.cx;
                pVidStruct->pAlphaList[i].ulImageHeight = m_allocatedSysMemSurfSize.cy;

                pVidStruct->pAlphaList[i].ulFourCC = HX_I420;
                pVidStruct->pAlphaList[i].lPitch = m_nSysMemSurfPitch;
                pVidStruct->pAlphaList[i].pBuffer = pSysBuf;

                // The alpha source is greater than our video...yikes
                if (pBlock->rect.right > m_allocatedSysMemSurfSize.cx ||
                    pBlock->rect.bottom > m_allocatedSysMemSurfSize.cy ||
                    pBlock->rect.right-pBlock->rect.left > m_allocatedSysMemSurfSize.cx ||
                    pBlock->rect.bottom-pBlock->rect.top > m_allocatedSysMemSurfSize.cy)
                {
                    pVidStruct->ulCount = 0;
                    HX_VECTOR_DELETE(pVidStruct->pAlphaList);

                    return;
                }
            }
        }
    }
    m_bImageBlocksGood = TRUE;
}

void CWinSurface2::YuvAlphaBlend(AlphaStruct* pList,
                                 UINT32 ulCount,
                                 UCHAR* pDest,
                                 UINT32 ulFourCC,
                                 INT32  lPitch,
                                 HXxSize* pDestDim)
{
    // Alpha blend each list entry and blt on the video frame
    if (m_bImageBlocksGood)
    {
        if( _BlendYUVRects(pList, ulCount, HX_I420) )
        {
            BOOL bConverter;
            INT32 ulDestCID = MapFourCCtoCID(ulFourCC);

            if (CID_UNKNOWN == ulDestCID)
                ulDestCID = ulFourCC;

            CHXSimpleList::Iterator gg = m_imageBlocks.Begin();
            for (int i=0; gg != m_imageBlocks.End(); ++i, ++gg)
            {
                ImageBlock* pBlock = (ImageBlock*)*gg;
                Image*      pTmp   = pBlock->pImage;

                // Get a converter
                bConverter = GetColorConverter(MapFourCCtoCID(pList[i].ulFourCC), ulDestCID);

                if (bConverter)
                {
                    ColorConvert( ulDestCID,
                                  pDest,
                                  pDestDim->cx,
                                  pDestDim->cy,
                                  lPitch,
                                  pBlock->rect.left,
                                  pBlock->rect.top,
                                  pBlock->rect.right-pBlock->rect.left,
                                  pBlock->rect.bottom-pBlock->rect.top,

                                  MapFourCCtoCID(pList[i].ulFourCC),
                                  pTmp->pucImage,
                                  pTmp->bmiImage.biWidth,
                                  pTmp->bmiImage.biHeight,
                                  GETBITMAPPITCH(&pTmp->bmiImage),
                                  0,
                                  0,
                                  pBlock->rect.right-pBlock->rect.left,
                                  pBlock->rect.bottom-pBlock->rect.top);
                }
            }
        }
    }
}

/************************************************************************
 *  Method:
 *      _ScheduleFrameForDisplay
 *  Purpose:
 */
HX_RESULT CWinSurface2::_ScheduleFrameForDisplay(void* pOsData,
                                                 tFrameElement* pItem,
                                                 UINT32 nResolution)
{
    // Display immediately when we do not have a clock
    if (!m_pTimeLine)
        return HXR_OK;

    HX_RESULT   hr = HXR_OK;
    UINT32      ulTime = 0;
    HANDLE      aWaitObjects[2] = {m_hForceRender, m_hAbort};

    // Number of ms the frame is ahead of the clock
    INT32       nTimeAhead = 0;

    // Use default scheduler
    if (!m_bOptimalVideoScheduler)
    {
        if (HXR_OK != CWinSurface::_ScheduleFrameForDisplay(pOsData, pItem, nResolution))
        {
            m_pTimeLine->GetTimeLineValue(ulTime);
            nTimeAhead = pItem->lTimeStamp - ulTime;

            // Ignore scheduling during a flush
            if (!m_bFlushing &&
                hr != HXR_TIMELINE_SUSPENDED &&
                nTimeAhead > 0)
            {
                m_nLastDisplayTime = ulTime + nTimeAhead;

                if (nResolution)
                    timeBeginPeriod(nResolution);

                WaitForMultipleObjects(2, aWaitObjects, FALSE, nTimeAhead);

                if (nResolution)
                    timeEndPeriod(nResolution);

                m_nLastDisplayTime = -1;
            }
        }

        pItem->dTimeAvailable = GetMSTickDouble32() + 15;
        return HXR_OK;
    }

    WINDRAW*    pWindraw = (WINDRAW*)pOsData;
    double      dTickAtStreamTime = 0;
    double      dPreemptionCheckTick = 0;

    // Number of ms to the next vertical retrace
    double      dMsToNextVBlank = 0;
    double      dCurrentError = 0;

    // Number of vblanks the display device can schedule the blt
    INT32       nMaxVBlanksPerSchedule = 0;

    // The display device's current vertical scan line
    UINT32      dwScanLine = 0;
    double      dFilteredScanLine = 0.0;

    // Number of vblanks until we need to display the frame
    double      dVblanksToWait = 0.0;
    double      dIdealVBlank = 0.0;
    INT32       nVblanksToWait = 0;

    // Fraction of a vertical trace the video starts on
    double      dWindowPosScanLine = 0;

    // Final window placement:
    HXxRect     windowRect = { 0, 0, 0, 0 };

    // Window of error for picking a vblank to flip on.
    double      dWindowOfError = .2;

    if (pItem->nBltMode == HX_OVERLAY_BLT)
    {
        // Some cards can schedule an overlay flip 4 vblanks in advance
        if (m_bUseVBlankFlip)
            nMaxVBlanksPerSchedule = 4;
        else
            nMaxVBlanksPerSchedule = 1;
    }

    while (1)
    {
        dTickAtStreamTime = GetMSTickDouble32();
        hr = m_pTimeLine->GetTimeLineValue(ulTime);
        dPreemptionCheckTick = GetMSTickDouble32();

        // Calculate how far the video is ahead of the clock in ms
        UINT32 i;
        for (i = 0; (dPreemptionCheckTick - dTickAtStreamTime > OS_THREAD_QUANTUM / 5.0)
                           && i < MAX_PREEMPTION_CHECK_ATTEMPTS; ++i)
        {
            HX_TRACE("Preemption!\n");
            dTickAtStreamTime = GetMSTickDouble32();
            hr = m_pTimeLine->GetTimeLineValue(ulTime);
            dPreemptionCheckTick = GetMSTickDouble32();
        }

        nTimeAhead = pItem->lTimeStamp - ulTime;

        WinDraw2_GetScanLine(pWindraw, &dwScanLine);

        // GetScanLine is terribly noisy on the G400 - one can get better
        // results by using high precision timers!
        // dFilteredScanLine = FilterScanLine(dwScanLine, dTickAtStreamTime - dVblankTime, pWindraw);
        dFilteredScanLine = dwScanLine;
        HX_ASSERT(dFilteredScanLine < pWindraw->dwMaxScanLine + 1);

#ifdef ENABLE_SYNC_TRACE
        syncTraceArray[ulSyncTraceIdx % MAX_SYNC_TRACE_ENTRIES][0] = dTickAtStreamTime;
        syncTraceArray[ulSyncTraceIdx % MAX_SYNC_TRACE_ENTRIES][1] = dwScanLine;
        syncTraceArray[ulSyncTraceIdx++ % MAX_SYNC_TRACE_ENTRIES][2] = GetMSTickDouble32() - dPreemptionCheckTick;
#endif // ENABLE_SYNC_TRACE

        _GetWindowDeviceCords(&windowRect);
        dWindowPosScanLine = windowRect.top / (double) (pWindraw->dwMaxScanLine + 1);

        // How many ms until the next vblank
        dMsToNextVBlank = (1.0 - (dFilteredScanLine / (double) (pWindraw->dwMaxScanLine + 1))) * pWindraw->dMsPerVBlank;

        // How many vblanks after the current one, until display time
        dVblanksToWait = (nTimeAhead - dMsToNextVBlank) / pWindraw->dMsPerVBlank;

#ifdef KAYA_CODE
        // Pick the appropriate vblank to display our frame.  First truncate
        // dVblanksToWait then round up and down and pick the smallest error.
        // In the rounding code, use nWindowPosScanLine to increase the a/v sync
        // accuracy.  We want to draw the first line of video as close to
        // the time give to us in pItem.

        double dPrevVBlank = ((INT32) dVblanksToWait) + dWindowPosScanLine - 1;
        double dCurrentVBlank = dPrevVBlank + 1;
        double dNextVBlank = dCurrentVBlank + 1;

        dIdealVBlank = fabs(dPrevVBlank - dVblanksToWait)
            < fabs(dCurrentVBlank - dVblanksToWait) ? dPrevVBlank : dCurrentVBlank;

        dIdealVBlank = fabs(dIdealVBlank - dVblanksToWait)
            < fabs(dNextVBlank - dVblanksToWait) ? dIdealVBlank : dNextVBlank;

        // If the refresh rate is a close multiple of the frame rate, we may
        // see rapid +/- oscillation of choice of vblanks due to timing error.
        // We only want to snap once per beat, so bias to the previous frame's
        // error in this case.
        dCurrentError = dVblanksToWait - (((INT32) dIdealVBlank <= 0 ? 0 : (INT32) dIdealVBlank)
                  + dWindowPosScanLine);

        if (fabs(dCurrentError) > TIME_JITTER_FILTER_THRESHOLD)
        {
            if ((m_dLastBlitError < 0) && (dCurrentError > 0))
            {
                //HX_TRACE("Error sign change: %f\n", dCurrentError);
                // Decrease error:
                dIdealVBlank++;
            }
            else if ((m_dLastBlitError > 0) && (dCurrentError < 0))
            {
                //HX_TRACE("Error sign change: %f\n", dCurrentError);
                // Increase error:
                dIdealVBlank--;
            }

            // An error of zero indicates an unitialized value on
            // start of playback or an unlikely coincidence.  Don't dampen
            // arbitrarily on the first frame to reduce the odds
            // of an initial, preventable snap; coincidences are harmless
        }

        nVblanksToWait = (INT32) dIdealVBlank;

        if (nVblanksToWait < 0)
        {
            nVblanksToWait = 0;
        }

#else
        // Determine the appropriate time to schedule this frame for display.
        // The basic idea is to map the time stamp with a vertical retrace.
        // When the refresh rate is evenly divisible by video frame rate there
        // will be an even map but the rest of the time we must approximate.
        //
        // The approximation is simple, find the vtrace the time stamps maps
        // to, calculate the time of this vtrace and compare this value with
        // the next vtrace.  Select the one that is closest to the time stamp.

        double dBltTime1 = pItem->lTimeStamp - (dVblanksToWait-(INT32)dVblanksToWait)*pWindraw->dMsPerVBlank;
        double dBltTime2 = dBltTime1 + pWindraw->dMsPerVBlank;
        double dBltTime;

        if ( fabs(dBltTime1-pItem->lTimeStamp) < fabs( dBltTime2-pItem->lTimeStamp))
        {
            dBltTime = dBltTime1;
            nVblanksToWait = (INT32)dVblanksToWait;
        }
        else
        {
            dBltTime = dBltTime2;
            nVblanksToWait = (INT32)(dVblanksToWait + 1);
        }

        INT32 nScheduleTime = dBltTime - nMaxVBlanksPerSchedule*pWindraw->dMsPerVBlank;

        // Ignore scheduling when the clock stops updating to avoid an infinite loop
        if (m_bFlushing || hr == HXR_TIMELINE_SUSPENDED)
        {
            nVblanksToWait = max(min(nVblanksToWait,nMaxVBlanksPerSchedule-1), 0);
            ulTime = nScheduleTime+1;
        }

#endif //KAYA_CODE

        // If the clock is within our allowed scheduling time, schedule the blt
        if (ulTime > nScheduleTime)
        {
            #ifdef LOG_JITTER_DATA
            FILE *fp = fopen(JITTER_LOG_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "Ready to flip: ts %ld clock %ld scan line %ld mstvb %.3f vbw %.3f jitter %ld\n",
                             pItem->lTimeStamp, ulTime, dwScanLine, dMsToNextVBlank, dVblanksToWait, (INT32)(pItem->lTimeStamp-(ulTime+dMsToNextVBlank+(INT32)nVblanksToWait*pWindraw->dMsPerVBlank)));
                fclose(fp);
            }
            #endif //LOG_JITTER_DATA

            m_dLastBlitError = pItem->lTimeStamp-(ulTime+dMsToNextVBlank+(INT32)nVblanksToWait*pWindraw->dMsPerVBlank);
            nVblanksToWait = 0;

            break;
        }
        else
        {
            // We want to wake up as soon as we can display the frame
            long lSleep = nScheduleTime - ulTime + 1;

            m_nLastDisplayTime = ulTime + lSleep;

            #ifdef LOG_JITTER_DATA
            INT32 lB4Sleep = GetBetterTickCount();
            #endif //LOG_JITTER_DATA

            if (lSleep > 0)
            {
                #ifndef HARDCODE_GRANULE
                if (nResolution)
                    timeBeginPeriod(nResolution);
                #endif //HARDCODE_GRANULE

                WaitForMultipleObjects(2, aWaitObjects, FALSE, lSleep);

                #ifndef HARDCODE_GRANULE
                if (nResolution)
                    timeEndPeriod(nResolution);
                #endif //HARDCODE_GRANULE
            }

            m_nLastDisplayTime = -1;

            #ifdef LOG_JITTER_DATA
            FILE *fp = fopen(JITTER_LOG_FILE, "a+");
            if (fp)
            {
                fprintf(fp, "ts %ld clock %ld scan line %ld mstvb %.3f vbw %.3f sleep %ld sleep dur %ld\n",
                              pItem->lTimeStamp, ulTime, dwScanLine, dMsToNextVBlank, dVblanksToWait, lSleep, GetBetterTickCount()-lB4Sleep);
                fclose(fp);
            }
            #endif //LOG_JITTER_DATA

            if (lSleep < 0)
                break;
        }
    }

#ifdef ENABLE_ERROR_TRACE
    //char szTmp[1000];

    //HX_TRACE("%lu\t:: blit error: %f\n", pItem->lTimeStamp, (dIdealVBlank - dVblanksToWait)*pWindraw->dMsPerVBlank);
    //sprintf(szTmp, "Timing error: %f\n", (dIdealVBlank - dVblanksToWait)*pWindraw->dMsPerVBlank);
    g_error[g_ulFrameCount % 10000][0] = dTickAtStreamTime;
    g_error[g_ulFrameCount % 10000][1] = m_dLastBlitError*pWindraw->dMsPerVBlank;
    g_error[g_ulFrameCount % 10000][2] = dIdealVBlank;
    g_alternateError[g_ulFrameCount++ % 10000] = pItem->lTimeStamp - (INT32) ulTime;

    // Get the DC and render this to the screen:
    //OutputDebugString(szTmp);
#endif // ENABLE_ERROR_TRACE

    // Set the minimum time this frame needs to be displayed (1 vblank)
    pItem->dTimeAvailable = dTickAtStreamTime + dMsToNextVBlank + (nVblanksToWait+1)*pWindraw->dMsPerVBlank + 1;
    HX_ASSERT(pItem->dTimeAvailable);

    // Set the DD flags for interval flipping
    if (pItem->nBltMode == HX_OVERLAY_BLT && m_bUseVBlankFlip)
    {
        switch((INT32)nVblanksToWait+1)
        {
            case 4: pItem->dwDDFlags = DDFLIP_INTERVAL4; break;
            case 3: pItem->dwDDFlags = DDFLIP_INTERVAL3; break;
            case 2: pItem->dwDDFlags = DDFLIP_INTERVAL2; break;
            default:pItem->dwDDFlags = 0;
        }
    }

    return hr;
}

double CWinSurface2::FilterScanLine(UINT32 ulScanLine, double dTickAtStreamTime,
                                    WINDRAW* pWindraw, BOOL bResetFilter)
{
#ifdef FILTER_SCAN_LINE
    // No mutex needed (for now) - this function is only called from the
    // render thread.

    // On reset, wait for vertical blank and reset m_ulScanLineFilterDepth:
    if (bResetFilter)
    {
        m_dMaxRegression = 0;
        m_ulScanLineFilterDepth = 0;

        return 0;
    }

    // This filter should adjust the max scan line (i.e. vertical refresh time),
    // refresh rate, and current scan line.  It is critical that we at least get
    // the phase (scan line) accurate.

    double currentRefresh = ulScanLine / pWindraw->dMsPerVBlank;
    UINT32 ulSamplesUsed = 0;

    double m;
    double b;


    // Ignore both zero and max scan line; some drivers play games with these.
    if (ulScanLine == 0 && ulScanLine != pWindraw->dwMaxScanLine)
    {
        // Add to filter:
        m_scanLineFilter[m_ulScanLineFilterDepth % SCAN_LINE_FILTER_DEPTH][0] = dTickAtStreamTime;
        m_scanLineFilter[m_ulScanLineFilterDepth++ % SCAN_LINE_FILTER_DEPTH][1]= currentRefresh;

#ifdef _DEBUG
        double dLastPhaseError;
#endif // _DEBUG

        // If we assume our refresh + scan line error is +/- 5%, then our
        // phase error should be monotonic to 20 past vblanks.
        // Perform a linear regression to correct our sample, if needed.

        double  sumX,
                sumY,
                sumXY,
                sumXSquared,
                sumYSquared = 0;

        for (int i = 1; i < m_ulScanLineFilterDepth && i < SCAN_LINE_FILTER_DEPTH; ++i)
        {
            double elapsedTime = m_scanLineFilter[(m_ulScanLineFilterDepth - i - 1) % SCAN_LINE_FILTER_DEPTH][0] -
                                 m_scanLineFilter[(m_ulScanLineFilterDepth - i) % SCAN_LINE_FILTER_DEPTH][0];

            if (elapsedTime >= 10*pWindraw->dMsPerVBlank)
            {
                continue;
            }

#ifdef _DEBUG
            // Monotonic?
#endif // _DEBUG

            ulSamplesUsed++;

            double refresh = (m_scanLineFilter[(m_ulScanLineFilterDepth - i - 1) % SCAN_LINE_FILTER_DEPTH][1] / pWindraw->dMsPerVBlank) + fmod(elapsedTime, pWindraw->dMsPerVBlank);
            refresh = refresh - (INT32) refresh;

            double error = m_scanLineFilter[(m_ulScanLineFilterDepth - i) % SCAN_LINE_FILTER_DEPTH][1] - refresh;

            //HX_TRACE("\terror : %lf\n", error);

            sumX += elapsedTime;
            sumY += error;
            sumXY = elapsedTime*error;
            sumXSquared += elapsedTime*elapsedTime;
            sumYSquared += error*error;
        }

        if (ulSamplesUsed < 100)
        {
            //HX_TRACE("skip\n");
            goto nofilter;
        }

        double meanX = sumX / ulSamplesUsed;
        double meanY = sumY / ulSamplesUsed;

        double sumSquaredErrorX = 0;
        double sumSquaredErrorY = 0;

        double sumErrorProduct = 0;

        for (i = 1; i < m_ulScanLineFilterDepth && i < SCAN_LINE_FILTER_DEPTH; ++i)
        {
            double elapsedTime = m_scanLineFilter[(m_ulScanLineFilterDepth - i - 1) % SCAN_LINE_FILTER_DEPTH][0] -
                                 m_scanLineFilter[(m_ulScanLineFilterDepth - i) % SCAN_LINE_FILTER_DEPTH][0];

            if (elapsedTime >= 10*pWindraw->dMsPerVBlank)
            {
                continue;
            }

            double refresh = (m_scanLineFilter[(m_ulScanLineFilterDepth - i - 1) % SCAN_LINE_FILTER_DEPTH][1] / pWindraw->dMsPerVBlank) + fmod(elapsedTime, pWindraw->dMsPerVBlank);
            refresh = refresh - (INT32) refresh;

            double error = m_scanLineFilter[(m_ulScanLineFilterDepth - i) % SCAN_LINE_FILTER_DEPTH][1] - refresh;

            sumSquaredErrorX += (elapsedTime - meanX)*(elapsedTime - meanX);
            sumSquaredErrorY += (error - meanY)*(error - meanY);

            sumErrorProduct += (elapsedTime - meanX)*(error - meanY);
        }

        double rho = sumErrorProduct / sqrt(sumSquaredErrorX*sumSquaredErrorY);

        //double rho = (sumXY - ((sumX*sumY) / ulSamplesUsed)) /
        //             sqrt((sumXSquared - ((sumX*sumX) / ulSamplesUsed))*(sumYSquared - ((sumY*sumY) / ulSamplesUsed)));

        double stdDevX = sumSquaredErrorX / ulSamplesUsed - 1;
        double stdDevY = sumSquaredErrorY / ulSamplesUsed - 1;

        m = rho*stdDevY/stdDevX;
        b = meanY - (m*meanX);

        //HX_TRACE("current phase error: %lf\n");
        //HX_TRACE("current refresh error: %lf\n");

        // Do we have enough data to update our phase point
#if 0
        if (rho >= MIN_ALLOWED_REGRESSION)
        {
            m_dMaxRegression = rho;

            HX_TRACE("current rho: %lf\n", rho);
            HX_TRACE("current m: %lf\n", m);
            HX_TRACE("current b: %lf\n", b);

            // Regression still has a sample delay; let's not worry about it for
            // now; nor any error due to an inaccurate max scan line.

            // Update refresh rate:
            // A negative slope = error decreasing back in time, increasing forwards in time.
            // Refresh rate should increase.
            // Positive slope = error increasing back in time, decreasing forwards in time.
            // Refresh rate should decrease.
            pWindraw->dRefreshRate -= m*1000;
            pWindraw->dMsPerVBlank = 1 / pWindraw->dRefreshRate;

            HX_TRACE("new refresh: %lf\n", pWindraw->dRefreshRate);

            // Update phase point:
            currentRefresh -= b;

            if (currentRefresh > 0)
            {
                m_dPhasePoint = dTickAtStreamTime - (currentRefresh)/pWindraw->dMsPerVBlank;
            }
            else
            {
                m_dPhasePoint = dTickAtStreamTime - ((currentRefresh)+1)/pWindraw->dMsPerVBlank;
            }
        }
#endif // 0
    }

    //if (m_dMaxRegression >= MIN_ALLOWED_REGRESSION)
    {
        double dRefreshes = (dTickAtStreamTime - m_dPhasePoint) / pWindraw->dMsPerVBlank;

        HX_TRACE("current b: %lf\n", b);
        dRefreshes -= b;

        if (dRefreshes < 0)
        {
            dRefreshes++;
        }

        double dScanLine = (dRefreshes - ((UINT32) dRefreshes))*(pWindraw->dwMaxScanLine + 1);

        // If we're off by less than 15%, let it slide?
        return fabs(dScanLine - ulScanLine) / (pWindraw->dwMaxScanLine + 1) > 15
            ? dScanLine : ulScanLine;
    }
#endif // FILTER_SCAN_LINE

#ifdef FILTER_SCAN_LINE
nofilter:
#endif

    return ulScanLine;
}
