#include "pch.h"

//////////////////////////////////////////////////////////////////////////////
//
// Surface Modes
//
//////////////////////////////////////////////////////////////////////////////

enum SurfaceMode {
    SurfaceModeDD,
    SurfaceModeLocked
};

//////////////////////////////////////////////////////////////////////////////
//
// VideoSurface
//
//////////////////////////////////////////////////////////////////////////////

class DDSurfaceImpl : public DDSurface {
public:
    //////////////////////////////////////////////////////////////////////////////
    //
    // Types
    //
    //////////////////////////////////////////////////////////////////////////////

    class SurfaceData {
    public:
        int                       m_id;
        TRef<PixelFormat>         m_ppf;
        TRef<IDirectDrawSurfaceX> m_pdds;
        TRef<IDirect3DTextureX>   m_pd3dtexture;
    };

    //////////////////////////////////////////////////////////////////////////////
    //
    // Members
    //
    //////////////////////////////////////////////////////////////////////////////

    TRef<PrivateEngine>       m_pengine;
    TRef<DDDevice>            m_pdddevice;
    TRef<IDirectDrawSurfaceX> m_pddsZBuffer;
    WinPoint                  m_size;
    WinPoint                  m_sizeSurface;
    SurfaceType               m_stype;

    DDSDescription            m_ddsdLocked;

    BYTE*                     m_pbits;
    int                       m_pitch;
    Color                     m_colorKey;
    TRef<PrivatePalette>      m_ppalette;
    Color                     m_colorText;

    bool                      m_bTextureSize;
    bool                      m_bColorKey;
    bool                      m_bSharedMemory;

    //
    // Mip-Mapped levels
    //

    SurfaceData               m_data;
    TVector<SurfaceData>      m_datas;

    //
    // Converted detail levels
    //

    SurfaceData               m_dataConverted;
    TVector<SurfaceData>      m_datasConverted;

    //
    // Surface invalidation and modes
    //

    SurfaceMode               m_mode;

    //////////////////////////////////////////////////////////////////////////////
    //
    //
    //
    //////////////////////////////////////////////////////////////////////////////

    void CalculateSurfaceSize()
    {
        m_sizeSurface = m_size;

        if (m_size.X() < 1) {
            ZAssert(m_size.X() == 0);
            m_sizeSurface.SetX(1);
        }

        if (m_size.Y() < 1) {
            ZAssert(m_size.Y() == 0);
            m_sizeSurface.SetY(1);
        }
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    //
    //
    //////////////////////////////////////////////////////////////////////////////

    void Initialize()
    {
        CalculateSurfaceSize();

        m_bTextureSize =
               m_sizeSurface.Y() == (int)NextPowerOf2((DWORD)m_sizeSurface.Y())
            && m_sizeSurface.X() == (int)NextPowerOf2((DWORD)m_sizeSurface.X());

        m_bColorKey = false;
        m_colorKey  = Color(0, 0, 0);
        m_data.m_id = 0;
        m_mode      = SurfaceModeDD;
        m_pbits     = NULL;
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // 
    //
    //////////////////////////////////////////////////////////////////////////////

    void FinishConstruction()
    {
        //
        // Set the palette if there is one
        //

        if (m_ppalette) {
            m_data.m_pdds->SetPalette(m_ppalette->GetDDPal());
        }

        //
        // Get a texture handle if needed
        //
        if (m_bTextureSize && !m_stype.Test(SurfaceTypeVideo())) {
#ifdef USEDX7
            m_data.m_pd3dtexture = m_data.m_pdds;
#else
            DDCall(m_data.m_pdds->QueryInterface(IID_IDirect3DTextureX, (void**)&(m_data.m_pd3dtexture)));
#endif
        }
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Create a video memory surface
    //
    //////////////////////////////////////////////////////////////////////////////

    DDSurfaceImpl(
              DDDevice*       pdddevice,
              SurfaceType     stype,
              PixelFormat*    ppf,
              PrivatePalette* ppalette,
        const WinPoint&       size
    ) :
        m_pdddevice(pdddevice),
        m_pengine(pdddevice->GetEngine()),
        m_stype(stype),
        m_ppalette(ppalette),
        m_size(size),
        m_bSharedMemory(false)
    {
        m_data.m_ppf   = ppf;

        Initialize();

        //
        // Figure out the DD caps
        //

        DWORD caps = DDSCAPS_VIDEOMEMORY;

        if (Is3D()) {
            caps |= DDSCAPS_3DDEVICE;
        } else {
            if (m_bTextureSize) {
                caps |= DDSCAPS_TEXTURE;
            } else {
                caps |= DDSCAPS_OFFSCREENPLAIN;
            }
        }

        //
        // Create the surface
        //

        m_data.m_pdds = m_pdddevice->CreateSurface(size, caps, ppf, true);

        //
        // Was there enough memory to allocate the surface? 
        //

        if (m_data.m_pdds != NULL) {
            //
            // Get the pitch
            //

            DDSDescription ddsd;
            DDCall(m_data.m_pdds->GetSurfaceDesc(&ddsd));
            m_pitch = ddsd.lPitch;

            //
            // Do any other common construction tasks
            //

            FinishConstruction();
        }
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Wrap an previously created DirectDraw surface
    //
    //////////////////////////////////////////////////////////////////////////////

    DDSurfaceImpl(
        DDDevice*            pdddevice,
        IDirectDrawSurfaceX* pdds,
        IDirectDrawSurfaceX* pddsZBuffer,
        PixelFormat*         ppf,
        PrivatePalette*      ppalette,
        SurfaceType          stype
    ) :
        m_pdddevice(pdddevice),
        m_pengine(pdddevice->GetEngine()),
        m_pddsZBuffer(pddsZBuffer),
        m_ppalette(ppalette),
        m_stype(stype | SurfaceType2D()),
        m_bSharedMemory(false)
    {
        DDSDescription ddsd;

        DDCall(pdds->GetSurfaceDesc(&ddsd));

        m_data.m_pdds  = pdds;
        m_data.m_ppf   = ppf;
        m_size         = WinPoint(ddsd.XSize(), ddsd.YSize());
        m_pitch        = ddsd.Pitch();

        Initialize();
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Create a surface from a binary representation
    //
    //////////////////////////////////////////////////////////////////////////////

    DDSurfaceImpl(
              DDDevice*       pdddevice,
              SurfaceType     stype,
              PixelFormat*    ppf,
              PrivatePalette* ppalette,
        const WinPoint&       size,
              int             pitch,
              BYTE*           pbits
    ) :
        m_pdddevice(pdddevice),
        m_pengine(pdddevice->GetEngine()),
        m_stype(stype),
        m_ppalette(ppalette),
        m_size(size)
    {
        //
        // Is the memory writable?
        //

        MEMORY_BASIC_INFORMATION meminfo;
        ZVerify(VirtualQuery(pbits, &meminfo, sizeof(meminfo)) == sizeof(meminfo));
        m_bSharedMemory = 
            (
                   (
                          meminfo.Protect 
                        & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY)
                   )
                != 0
            );

        //
        // initialize
        //

        m_pitch        = pitch;
        m_data.m_ppf   = ppf;

        Initialize();

        //
        // !!! Dreamcast doesn't support client allocated memory
        //     what to do about that?
        //

        //
        // Figure out the DD caps
        //

        DWORD caps = DDSCAPS_SYSTEMMEMORY;

        if (Is3D()) {
            caps |= DDSCAPS_3DDEVICE;
        }

        if (m_bTextureSize) {
            caps |= DDSCAPS_TEXTURE;
        } else {
            caps |= DDSCAPS_OFFSCREENPLAIN;
        }

        //
        // Create the surface
        //

        DDSDescription ddsd;

        ddsd.dwFlags         =
              DDSD_HEIGHT
            | DDSD_WIDTH
            | DDSD_CAPS
            | DDSD_PIXELFORMAT
            | DDSD_PITCH;
        ddsd.dwWidth         = m_size.X();
        ddsd.dwHeight        = m_size.Y();
        ddsd.lPitch          = pitch;
        ddsd.ddsCaps.dwCaps  = caps;
        ddsd.ddpfPixelFormat = m_data.m_ppf->GetDDPF();


        if (m_bSharedMemory) {
            //
            // use the passed in memory
            //

            ddsd.dwFlags   = ddsd.dwFlags | DDSD_LPSURFACE;
            ddsd.lpSurface = pbits;
            HRESULT hr = m_pdddevice->GetDD()->CreateSurface(&ddsd, &m_data.m_pdds, NULL);
            DDCall(hr);

            #ifdef _DEBUG
                DDSDescription ddsd;
                DDCall(m_data.m_pdds->GetSurfaceDesc(&ddsd));
                ZAssert(pitch == ddsd.lPitch);
            #endif

            m_pitch = pitch;
        } else {
            //
            // Let directx allocate the memory
            //

            HRESULT hr = m_pdddevice->GetDD()->CreateSurface(&ddsd, &m_data.m_pdds, NULL);
            DDCall(hr);

            //
            // Copy the bits over
            //

            DDCall(m_data.m_pdds->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL));
            m_pitch = ddsd.Pitch();

            ZAssert(pitch <= m_pitch);

            for (int y = 0; y < m_size.Y(); y++) {
                memcpy(
                    ddsd.Pointer() + y * m_pitch,
                    pbits          + y * pitch,
                    pitch
                );
            }

            DDCall(m_data.m_pdds->Unlock(NULL));
        }

        //
        // Do any other common construction tasks
        //

        FinishConstruction();
    }

    bool IsMemoryShared()
    {
        return m_bSharedMemory;
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // 
    //
    //////////////////////////////////////////////////////////////////////////////

    bool IsValid()
    {
        return m_data.m_pdds != NULL;
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Destructor
    //
    //////////////////////////////////////////////////////////////////////////////

    ~DDSurfaceImpl()
    {
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Attach a ZBuffer surface
    //
    //////////////////////////////////////////////////////////////////////////////

    IDirectDrawSurfaceX* GetDDSXZBuffer()
    {
        if (HasZBuffer()) {
            if (m_pddsZBuffer == NULL) {
                DWORD caps = DDSCAPS_ZBUFFER;

                if (m_stype.Test(SurfaceTypeVideo())) {
                    caps |= DDSCAPS_VIDEOMEMORY;
                } else {
                    caps |= DDSCAPS_SYSTEMMEMORY;
                }

                m_pddsZBuffer =
                    m_pdddevice->CreateSurface(
                        m_size,
                        caps,
                        m_pdddevice->GetZBufferPixelFormat(),
                        true
                    );

                if (m_pddsZBuffer) {
                    DDCall(m_data.m_pdds->AddAttachedSurface(m_pddsZBuffer));
                }
            }
        }

        return m_pddsZBuffer;
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Surface updates
    //
    //////////////////////////////////////////////////////////////////////////////

    void SurfaceChanged()
    {
        m_data.m_id++;
        if (m_data.m_id < 0) m_data.m_id = 0;
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Down Sample
    //
    //////////////////////////////////////////////////////////////////////////////

    static DWORD Unpack(WORD pixelWord)
    {
        DWORD pixel = pixelWord;

        return
              ((pixel & 0xf800) << 5)
            | ((pixel & 0x07e0) << 3)
            | ((pixel & 0x001f)     );
    }

    static WORD Pack(DWORD pixel)
    {
        return
            (WORD)(
                  ((pixel >> 5) & 0xf800)
                | ((pixel >> 3) & 0x07e0)
                | ((pixel     ) & 0x001f)
            );
    }

    static WORD Average(WORD pixel0, WORD pixel1, WORD pixel2, WORD pixel3)
    {
        return
            Pack(
                (
                      Unpack(pixel0)
                    + Unpack(pixel1)
                    + Unpack(pixel2)
                    + Unpack(pixel3)
                ) >> 2
            );
    }

    static void DownSample(
        const WinPoint&      sizeTarget,
        IDirectDrawSurfaceX* pddsTarget,
        IDirectDrawSurfaceX* pddsSource
    ) {
        DDSDescription ddsdSource;
        DDSDescription ddsdTarget;

        DDCall(pddsSource->Lock(NULL, &ddsdSource, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL));
        DDCall(pddsTarget->Lock(NULL, &ddsdTarget, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL));

        int pitchSource = ddsdSource.Pitch();
        int pitchTarget = ddsdTarget.Pitch();

        ZAssert(
               ddsdSource.GetPixelFormat().dwRGBBitCount
            == ddsdTarget.GetPixelFormat().dwRGBBitCount
        );

        switch (ddsdSource.GetPixelFormat().dwRGBBitCount) {
            case 16:
                {
                    ZAssert(ddsdSource.GetPixelFormat().dwRBitMask        == 0xf800);
                    ZAssert(ddsdSource.GetPixelFormat().dwGBitMask        == 0x07e0);
                    ZAssert(ddsdSource.GetPixelFormat().dwBBitMask        == 0x001f);
                    ZAssert(ddsdSource.GetPixelFormat().dwRGBAlphaBitMask == 0x0000);

                    WORD* psource   = (WORD*)ddsdSource.Pointer();
                    WORD* ptarget   = (WORD*)ddsdTarget.Pointer();

                    for (int y = sizeTarget.Y() - 1; y >= 0; y--) {
                        for (int x = sizeTarget.X() - 1; x >= 0; x--) {
                            ptarget[x] =
                                Average(
                                    psource[x * 2],
                                    psource[x * 2 + 1],
                                    psource[x * 2     + pitchSource / 2],
                                    psource[x * 2 + 1 + pitchSource / 2]
                                );
                        }

                        psource += 2 * pitchSource / 2;
                        ptarget +=     pitchTarget / 2;
                    }
                }
                break;

            case 24:
                {
                    BYTE* psource   = (BYTE*)ddsdSource.Pointer();
                    BYTE* ptarget   = (BYTE*)ddsdTarget.Pointer();

                    for (int y = sizeTarget.Y() - 1; y >= 0; y--) {
                        for (int x = sizeTarget.X() - 1; x >= 0; x--) {
                            BYTE* psource0 = psource + x * 6;
                            BYTE* psource1 = psource + x * 6 + 3;
                            BYTE* psource2 = psource + x * 6     + pitchSource;
                            BYTE* psource3 = psource + x * 6 + 3 + pitchSource;

                            ptarget[x * 3 + 0] = (psource0[0] + psource1[0] + psource2[0] + psource3[0]) / 4;
                            ptarget[x * 3 + 1] = (psource0[1] + psource1[1] + psource2[1] + psource3[1]) / 4;
                            ptarget[x * 3 + 2] = (psource0[2] + psource1[2] + psource2[2] + psource3[2]) / 4;
                        }

                        psource += pitchSource * 2;
                        ptarget += pitchTarget;
                    }
                }
                break;

            default:
                ZError("DownSample: Invalid PixelFormat");
        };

        DDCall(pddsTarget->Unlock(NULL));
        DDCall(pddsSource->Unlock(NULL));
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Construct a new lod or converted surface
    //
    //////////////////////////////////////////////////////////////////////////////

    void ConstructSurfaceData(SurfaceData& data, PixelFormat* ppf, const WinPoint& size)
    {
        data.m_id  = -1;
        data.m_ppf = ppf;

        DWORD caps = DDSCAPS_SYSTEMMEMORY;

        if (m_bTextureSize/* && ppf->IsSoftwareTexture()*/) {
            caps |= DDSCAPS_TEXTURE;
        } else {
            caps |= DDSCAPS_OFFSCREENPLAIN;
        }

        data.m_pdds =
            m_pdddevice->CreateSurface(
                size,
                caps,
                ppf,
                false
            );

        if (HasColorKey()) {
            DDCOLORKEY key;

            key.dwColorSpaceLowValue  =
            key.dwColorSpaceHighValue =
                ppf->MakePixel(GetColorKey()).Value();

            DDCall(data.m_pdds->SetColorKey(DDCKEY_SRCBLT, &key));
        }

        if (caps & DDSCAPS_TEXTURE) {
#ifdef USEDX7
            data.m_pd3dtexture = data.m_pdds;
#else
            DDCall(data.m_pdds->QueryInterface(IID_IDirect3DTextureX, (void**)&(data.m_pd3dtexture)));
#endif
        } else {
            data.m_pd3dtexture = NULL;
        }
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Convert pixel formats using GDI
    //
    //////////////////////////////////////////////////////////////////////////////

    static void Convert(const WinPoint& size, IDirectDrawSurfaceX* pddsTarget, IDirectDrawSurfaceX* pddsSource)
    {
        HDC hdcSource;
        DDCall(pddsSource->GetDC(&hdcSource));

        HDC hdcTarget;
        DDCall(pddsTarget->GetDC(&hdcTarget));

        ZVerify(::BitBlt(hdcTarget, 0, 0, size.X(), size.Y(), hdcSource, 0, 0, SRCCOPY));

        DDCall(pddsTarget->ReleaseDC(hdcTarget));
        DDCall(pddsSource->ReleaseDC(hdcSource));
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Make sure the bits for a converted surface are available and up to date.
    //
    //////////////////////////////////////////////////////////////////////////////

    void UpdateConvertedSurface(PixelFormat* ppf, const WinPoint& size, SurfaceData& data, const SurfaceData& dataSource)
    {
        if (ppf != data.m_ppf) {
            ConstructSurfaceData(data, ppf, size);
        }

        if (data.m_id != dataSource.m_id) {
            SetSurfaceMode(SurfaceModeDD);

            Convert(size, data.m_pdds, dataSource.m_pdds);
            data.m_id = dataSource.m_id;
        }
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Get a surface with a certain pixel format.
    //
    //////////////////////////////////////////////////////////////////////////////

    IDirectDrawSurfaceX* GetDDSX()
    {
        SetSurfaceMode(SurfaceModeDD);
        return m_data.m_pdds;
    }

    IDirectDrawSurfaceX* GetDDSX(PixelFormat* ppf)
    {
        if (ppf == m_data.m_ppf) {
            SetSurfaceMode(SurfaceModeDD);
            return m_data.m_pdds;
        } else {
            UpdateConvertedSurface(ppf, m_size, m_dataConverted, m_data);
            return m_dataConverted.m_pdds;
        }
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Get a texture with a certain pixel format.
    //
    //////////////////////////////////////////////////////////////////////////////

    IDirect3DTextureX* GetTextureX(PixelFormat* ppf, const WinPoint& size, int& id)
    {
        id = m_data.m_id;

        //
        // Return the fullsize surface if that's what's wanted.
        //

        if (size == m_size) {
            if (ppf == m_data.m_ppf) {
                SetSurfaceMode(SurfaceModeDD);
                #ifdef DREAMCAST
                    if (!m_data.m_pd3dtexture) {
                        DDCall(m_data.m_pdds->QueryInterface(IID_IDirect3DTextureX, (void**)&(m_data.m_pd3dtexture)));
                    }
                #endif
                return m_data.m_pd3dtexture;
            }

            UpdateConvertedSurface(ppf, m_size, m_dataConverted, m_data);
            return m_dataConverted.m_pd3dtexture;
        }

        //
        // Need to return a lower level of detail
        //

        int index = 0;
        WinPoint sizeSource = m_size;

        while (true) {
            sizeSource.SetX(sizeSource.X() / 2);
            sizeSource.SetY(sizeSource.Y() / 2);

            //
            // Do we need to allocate a new lower level of detail?
            //

            if (index == m_datas.GetCount()) {
                m_datas.PushEnd();
                m_datasConverted.PushEnd();
                ConstructSurfaceData(m_datas.Get(index), m_data.m_ppf, sizeSource);
            }

            //
            // Do we need to update this level?
            //

            if (m_datas[index].m_id != m_data.m_id) {
                DownSample(
                    sizeSource,
                    m_datas[index].m_pdds,
                    index == 0 ?
                          m_data.m_pdds
                        : m_datas[index - 1].m_pdds
                );

                m_datas.Get(index).m_id = m_data.m_id;
            }

            //
            // Did we find the right size?
            //

            if (sizeSource == size) {
                //
                // Does the format need to be converted?
                //

                if (ppf == m_data.m_ppf) {
                    return m_datas[index].m_pd3dtexture;
                } else {
                    UpdateConvertedSurface(ppf, size, m_datasConverted.Get(index), m_datas[index]);
                    return m_datasConverted[index].m_pd3dtexture;
                }
            }

            index++;
        }
    }

    TRef<IDirectDrawSurface> GetDDS()
    {
        SetSurfaceMode(SurfaceModeDD);

        TRef<IDirectDrawSurface> pdds;
        DDCall(m_data.m_pdds->QueryInterface(IID_IDirectDrawSurface, (void**)&pdds));
        return pdds;
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Surface Type
    //
    //////////////////////////////////////////////////////////////////////////////

    bool Is3D()
    {
        return m_stype.Test(SurfaceType3D());
    }

    bool HasZBuffer()
    {
        return m_stype.Test(SurfaceTypeZBuffer());
    }

    bool InVideoMemory()
    {
        return m_stype.Test(SurfaceTypeVideo()) != 0;
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Attributes
    //
    //////////////////////////////////////////////////////////////////////////////

    PixelFormat* GetPixelFormat()
    {
        return m_data.m_ppf;
    }

    PrivatePalette* GetPalette()
    {
        return m_ppalette;
    }

    DDDevice* GetDDDevice()
    {
        return m_pdddevice;
    }

    Engine* GetEngine()
    {
        return m_pengine;
    }

    PrivateEngine* GetEngineImpl()
    {
        return m_pengine;
    }

    const WinPoint& GetSize()
    {
        return m_size;
    }

    SurfaceType GetSurfaceType()
    {
        return m_stype;
    }

    bool HasColorKey()
    {
        return m_bColorKey;
    }

    const Color& GetColorKey()
    {
        ZAssert(m_bColorKey);
        return m_colorKey;
    }

    void SetColorKey(const Color& color)
    {
        ZAssert(m_datas.GetCount() == 0);
        ZAssert(m_datasConverted.GetCount() == 0);

        m_bColorKey = true;
        m_colorKey = color;

        //
        // Update the ddraw surface
        //

        SetSurfaceMode(SurfaceModeDD);
        DDCOLORKEY key;
        key.dwColorSpaceLowValue  =
        key.dwColorSpaceHighValue = m_data.m_ppf->MakePixel(m_colorKey).Value();

        DDCall(m_data.m_pdds->SetColorKey(DDCKEY_SRCBLT, &key));

        //
        // Update the converted ddraw surface
        //

        if (m_dataConverted.m_pdds) {
            DDCall(m_dataConverted.m_pdds->SetColorKey(DDCKEY_SRCBLT, &key));
        }

        SurfaceChanged();
    }

    int GetPitch()
    {
        return m_pitch;
    }

    BYTE* GetPointer()
    {
        if (m_pbits != NULL) {
            return m_pbits;
        } else {
            SetSurfaceMode(SurfaceModeLocked);

            BYTE* pbits = m_ddsdLocked.Pointer();

            if (pbits == NULL) {
                //
                // DDraw returned null.  Allocate some memory so the caller won't crash.
                //

                m_pbits = new BYTE[m_pitch * m_size.Y()];
                return m_pbits;
            }

            return pbits;
        }
    }

    void ReleasePointer()
    {
        SetSurfaceMode(SurfaceModeDD);
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Surface modes
    //
    //////////////////////////////////////////////////////////////////////////////

    void BeginScene()
    {
        SetSurfaceMode(SurfaceModeDD);
    }

    void EndScene()
    {
    }

    void SetSurfaceMode(SurfaceMode mode)
    {
        if (m_mode != mode) {
            //
            // switch back to DDMode
            //

            switch (m_mode) {
                case SurfaceModeLocked:
                    if (m_pbits == NULL) {
                        DDSCall(m_data.m_pdds->Unlock(NULL));
                    }
                    SurfaceChanged();
                    break;
            }

            //
            // switch to the new mode
            //

            m_mode = mode;

            switch (mode) {
                case SurfaceModeLocked:
                    DDSCall(m_data.m_pdds->Lock(NULL, &m_ddsdLocked, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL));
                    break;
            }
        }
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Blts to and from a dc
    //
    //////////////////////////////////////////////////////////////////////////////

    void BitBltFromDC(HDC hdc)
    {
        SetSurfaceMode(SurfaceModeDD);
        HDC hdcSurface;
        if (DDSCall(m_data.m_pdds->GetDC(&hdcSurface))) {
            ZVerify(::BitBlt(
                hdcSurface,
                0,
                0,
                m_size.X(),
                m_size.Y(),
                hdc,
                0,
                0,
                SRCCOPY
            ));

            DDSCall(m_data.m_pdds->ReleaseDC(hdcSurface));
        }

        SurfaceChanged();
        SetSurfaceMode(SurfaceModeDD);
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // StretchBlt
    //
    //////////////////////////////////////////////////////////////////////////////

    void UnclippedBlt(
        const WinRect& rectTarget,
        VideoSurface*  psurfaceSource,
        const WinRect& rectSource,
        bool           bColorKey
    ) {
        SetSurfaceMode(SurfaceModeDD);
        DWORD flags = bColorKey ? DDBLT_KEYSRC : 0;
        DDSurface* pddsurface; CastTo(pddsurface, psurfaceSource);

        DDSCall(m_data.m_pdds->Blt(
            (RECT*)&rectTarget, 
            pddsurface->GetDDSX(),
            (RECT*)&rectSource, 
            flags | DDBLT_WAIT, 
            NULL
        ));
    }


    //////////////////////////////////////////////////////////////////////////////
    //
    // Blt
    //
    //////////////////////////////////////////////////////////////////////////////

    void UnclippedBlt(
        const WinRect&       rect, 
        IDirectDrawSurfaceX* pddsSource, 
        const WinPoint&      pointSource, 
        bool                 bColorKey
    ) {
        SetSurfaceMode(SurfaceModeDD);
        DWORD flags = bColorKey ? DDBLT_KEYSRC : 0;
        WinRect rectSource(pointSource, pointSource + rect.Size());

        DDSCall(m_data.m_pdds->Blt(
            (RECT*)&rect, 
            pddsSource, 
            (RECT*)&rectSource, 
            flags | DDBLT_WAIT, 
            NULL
        ));
    }

    void UnclippedBlt(const WinRect& rect, VideoSurface* psurfaceSource, const WinPoint& pointSource)
    {
        DDSurface* pddsurface; CastTo(pddsurface, psurfaceSource);

        UnclippedBlt(rect, pddsurface->GetDDSX(), pointSource, pddsurface->HasColorKey());
    }

    //////////////////////////////////////////////////////////////////////////////
    //
    // Fill
    //
    //////////////////////////////////////////////////////////////////////////////

    void UnclippedFill(const WinRect& rect, Pixel pixel)
    {
        DDBltFX ddbltfx;
        ddbltfx.dwFillColor = pixel.Value();

        DDSCall(m_data.m_pdds->Blt(
            (RECT*)&rect, 
            NULL, 
            (RECT*)&rect, 
            DDBLT_COLORFILL | DDBLT_WAIT, 
            &ddbltfx
        ));
    }
};

//////////////////////////////////////////////////////////////////////////////
//
// Constructors
//
//////////////////////////////////////////////////////////////////////////////

TRef<DDSurface> CreateDDSurface(
          DDDevice*       pdddevice,
          SurfaceType     stype,
          PixelFormat*    ppf,
          PrivatePalette* ppalette,
    const WinPoint&       size
) {
    TRef<DDSurface> pddsurface = new DDSurfaceImpl(pdddevice, stype, ppf, ppalette, size);

    if (pddsurface->IsValid()) {
        return pddsurface;
    } else {
        return NULL;
    }
}

TRef<DDSurface> CreateDDSurface(
          DDDevice*       pdddevice,
          SurfaceType     stype,
          PixelFormat*    ppf,
          PrivatePalette* ppalette,
    const WinPoint&       size,
          int             pitch,
          BYTE*           pbits
) {
    return new DDSurfaceImpl(pdddevice, stype, ppf, ppalette, size, pitch, pbits);
}

TRef<DDSurface> CreateDDSurface(
    DDDevice*            pdddevice,
    IDirectDrawSurfaceX* pdds,
    IDirectDrawSurfaceX* pddsZBuffer,
    PixelFormat*         ppf,
    PrivatePalette*      ppalette,
    SurfaceType          stype
) {
    return
        new DDSurfaceImpl(
            pdddevice,
            pdds,
            pddsZBuffer,
            ppf,
            ppalette,
            stype
        );
}