#ifndef __PIXEL_HPP__
#define __PIXEL_HPP__

/*==========================================================================;
 *
 *  Copyright (C) 1999-2000 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       pixel.hpp
 *  Content:    Utility class for working with pixel formats
 *
 *
 ***************************************************************************/

// includes
#include "d3dobj.hpp"

struct IHVFormatInfo
{
    D3DFORMAT       m_Format;
    DWORD           m_BPP;
    IHVFormatInfo  *m_pNext;
};

// This is a utility class that implements useful helpers for
// allocating and accessing various pixel formats. All methods
// are static and hence should be accessed as follows:
//  e.g. CPixel::LockOffset(...)
//

class CPixel
{
public:
    // Allocate helpers

    // Determine the amount of memory that is needed to
    // allocate various things..
    static UINT ComputeSurfaceSize(UINT            cpWidth,
                                   UINT            cpHeight,
                                   D3DFORMAT       Format);

    static UINT ComputeVolumeSize(UINT             cpWidth,
                                  UINT             cpHeight,
                                  UINT             cpDepth,
                                  D3DFORMAT        Format);


    static UINT ComputeMipMapSize(UINT             cpWidth,
                                  UINT             cpHeight,
                                  UINT             cLevels,
                                  D3DFORMAT        Format);

    static UINT ComputeMipVolumeSize(UINT          cpWidth,
                                     UINT          cpHeight,
                                     UINT          cpDepth,
                                     UINT          cLevels,
                                     D3DFORMAT     Format);

    // Lock helpers

    // Given a surface desc, a level, and pointer to
    // bits (pBits in the LockedRectData) and a sub-rect,
    // this will fill in the pLockedRectData structure
    static void ComputeMipMapOffset(const D3DSURFACE_DESC  *pDescTopLevel,
                                    UINT                    iLevel,
                                    BYTE                   *pBits,
                                    CONST RECT             *pRect,
                                    D3DLOCKED_RECT         *pLockedRectData);

    // MipVolume version of ComputeMipMapOffset
    static void ComputeMipVolumeOffset(const D3DVOLUME_DESC  *pDescTopLevel,
                                       UINT                   iLevel,
                                       BYTE                  *pBits,
                                       CONST D3DBOX          *pBox,
                                       D3DLOCKED_BOX         *pLockedBoxData);

    // Surface version of ComputeMipMapOffset
    static void ComputeSurfaceOffset(const D3DSURFACE_DESC  *pDesc,
                                     BYTE                   *pBits,
                                     CONST RECT             *pRect,
                                     D3DLOCKED_RECT         *pLockedRectData);

    // Is this a supported format?
    static BOOL IsSupported(D3DRESOURCETYPE Type, D3DFORMAT Format);

    // Is this a IHV non-standard format? i.e. do
    // we know the number of bytes per pixel?
    static BOOL IsIHVFormat(D3DFORMAT Format);

    // Is this a Z format that the user can create?
    static BOOL IsEnumeratableZ (D3DFORMAT Format);

    // Is this a Z format that needs mapping b4 sending
    // to the driver?
    static BOOL IsMappedDepthFormat(D3DFORMAT Format);

    // All depth formats other than D16 are currently
    // defined to be non-lockable. This function will
    // return FALSE for:
    //      non-Z formats 
    //      D16_LOCKABLE
    //      IHV formats
    static BOOL IsNonLockableZ(D3DFORMAT Format);

    // Pixel Stride will return negative for DXT formats
    // Call AdjustForDXT to work with things at the block level
    static UINT ComputePixelStride(D3DFORMAT Format);

    // This will adjust cbPixel
    // to pixels per block; and width and height will
    // be adjusted to pixels. Assumes the IsDXT(cbPixel).
    static void AdjustForDXT(UINT *pcpWidth,
                             UINT *pcpHeight,
                             UINT *pcbPixel);

    // Adjust parameters for VolumeDXT
    static void AdjustForVolumeDXT(UINT *pcpWidth,
                                   UINT *pcpHeight,
                                   UINT *pcpDepth,
                                   UINT *pcbPixel);

    // returns TRUE if cbPixel is "negative" i.e. DXT/V group
    static BOOL IsDXT(UINT cbPixel);

    // returns TRUE if format is one of the DXT/V group
    static BOOL IsDXT(D3DFORMAT Format);

    // returns TRUE if format is one of the DXV family
    static BOOL IsVolumeDXT(D3DFORMAT Format);

    // returns TRUE if format has stencil bits
    static BOOL HasStencilBits(D3DFORMAT Format);

    // returns TRUE if format is paletted
    static BOOL IsPaletted(D3DFORMAT Format);

    // Helpers for validation for DXTs.
    static BOOL IsValidRect(D3DFORMAT    Format,
                            UINT         Width,
                            UINT         Height,
                            const RECT  *pRect);

    static BOOL IsValidBox(D3DFORMAT     Format,
                           UINT          Width,
                           UINT          Height,
                           UINT          Depth,
                           const D3DBOX *pBox);

    // Needs 4x4 Rules (DXT/DXVs)
    static BOOL Requires4X4(D3DFORMAT Format);

    // Detection for "real" FourCC formats
    static BOOL IsFourCC(D3DFORMAT Format);

    static D3DFORMAT SuppressAlphaChannel(D3DFORMAT Format);

    static UINT BytesPerPixel(D3DFORMAT Format);

    // Register format for later lookup
    static HRESULT Register(D3DFORMAT Format, DWORD BPP);

    // Cleanup registry
    static void Cleanup();

private:
    // Internal functions

    static UINT ComputeSurfaceStride(UINT cpWidth, UINT cbPixel);

    static UINT ComputeSurfaceSize(UINT            cpWidth,
                                   UINT            cpHeight,
                                   UINT            cbPixel);

    static IHVFormatInfo *m_pFormatList;


}; // CPixel



#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsIHVFormat"

inline BOOL CPixel::IsIHVFormat(D3DFORMAT Format)
{
    // If we know the number of bytes per
    // pixel; it's a non-IHV format
    if (BytesPerPixel(Format) != 0)
        return FALSE;

    // Must be an IHV format
    return TRUE;

} // IsIHVFormat


#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::ComputeSurfaceOffset"

inline void CPixel::ComputeSurfaceOffset(const D3DSURFACE_DESC  *pDesc,
                                         BYTE                   *pBits,
                                         CONST RECT             *pRect,
                                         D3DLOCKED_RECT         *pLockedRectData)
{
    ComputeMipMapOffset(pDesc, 0, pBits, pRect, pLockedRectData);
} // ComputeSurfaceOffset


#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::ComputeSurfaceSize"

inline UINT CPixel::ComputeSurfaceSize(UINT            cpWidth,
                                       UINT            cpHeight,
                                       D3DFORMAT       Format)
{
    UINT cbPixel = ComputePixelStride(Format);

    // Adjust pixel->block if necessary
    BOOL isDXT = IsDXT(cbPixel);
    if (isDXT)
    {
        AdjustForDXT(&cpWidth, &cpHeight, &cbPixel);
    }

    return ComputeSurfaceSize(cpWidth,
                              cpHeight,
                              cbPixel);
} // ComputeSurfaceSize

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::AdjustForDXT"
inline void CPixel::AdjustForDXT(UINT *pcpWidth,
                                 UINT *pcpHeight,
                                 UINT *pcbPixel)
{
    DXGASSERT(pcbPixel);
    DXGASSERT(pcpWidth);
    DXGASSERT(pcpHeight);
    DXGASSERT(IsDXT(*pcbPixel));

    // Adjust width and height for DXT formats to be in blocks
    // instead of pixels. Blocks are 4x4 pixels.
    *pcpWidth  = (*pcpWidth  + 3) / 4;
    *pcpHeight = (*pcpHeight + 3) / 4;

    // Negate the pcbPixel to determine bytes per block
    *pcbPixel *= -1;

    // We only know of two DXT formats right now...
    DXGASSERT(*pcbPixel == 8 || *pcbPixel == 16);

} // CPixel::AdjustForDXT

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::AdjustForVolumeDXT"
inline void CPixel::AdjustForVolumeDXT(UINT *pcpWidth,
                                       UINT *pcpHeight,
                                       UINT *pcpDepth,
                                       UINT *pcbPixel)
{
    DXGASSERT(pcbPixel);
    DXGASSERT(pcpWidth);
    DXGASSERT(pcpHeight);
    DXGASSERT(IsDXT(*pcbPixel));

    // Adjust width, height, depth for DXT formats to be in blocks
    // instead of pixels. Blocks are 4x4x4 pixels.
    *pcpWidth  = (*pcpWidth  + 3) / 4;
    *pcpHeight = (*pcpHeight + 3) / 4;
    *pcpDepth  = (*pcpDepth  + 3) / 4;

    // Negate the pcbPixel to determine bytes per block
    *pcbPixel *= -1;

    // We only know of two DXV formats right now...
    DXGASSERT(*pcbPixel == 32 || *pcbPixel == 64);
} // CPixel::AdjustForVolumeDXT


#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::ComputeVolumeSize"

inline UINT CPixel::ComputeVolumeSize(UINT             cpWidth,
                                      UINT             cpHeight,
                                      UINT             cpDepth,
                                      D3DFORMAT        Format)
{
    UINT cbPixel = ComputePixelStride(Format);

    if (IsDXT(cbPixel))
    {
        if (IsVolumeDXT(Format))
        {
            AdjustForVolumeDXT(&cpWidth, &cpHeight, &cpDepth, &cbPixel);
        }
        else
        {
            AdjustForDXT(&cpWidth, &cpHeight, &cbPixel);
        }
    }

    return cpDepth * ComputeSurfaceSize(cpWidth, cpHeight, cbPixel);
} // ComputeVolumeSize

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsSupported"
inline BOOL CPixel::IsSupported(D3DRESOURCETYPE Type, D3DFORMAT Format)
{
    UINT cbPixel = ComputePixelStride(Format);

    if (cbPixel == 0)
    {
        return FALSE;
    }
    else if (IsVolumeDXT(Format))
    {
        if (Type == D3DRTYPE_VOLUMETEXTURE)
            return TRUE;
        else
            return FALSE;
    }
    else
    {
        return TRUE;
    }
} // IsSupported

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsDXT(cbPixel)"

// returns TRUE if cbPixel is "negative"
inline BOOL CPixel::IsDXT(UINT cbPixel)
{
    if (((INT)cbPixel) < 0)
        return TRUE;
    else
        return FALSE;
} // IsDXT

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsDXT(format)"

// returns TRUE if this is a linear format
// i.e. DXT or DXV
inline BOOL CPixel::IsDXT(D3DFORMAT Format)
{
    // CONSIDER: This is a duplication of Requires4x4 function
    switch (Format)
    {
        // normal DXTs
    case D3DFMT_DXT1:
    case D3DFMT_DXT2:
    case D3DFMT_DXT3:
    case D3DFMT_DXT4:
    case D3DFMT_DXT5:

#ifdef VOLUME_DXT
        // Volume dxts
    case D3DFMT_DXV1:
    case D3DFMT_DXV2:
    case D3DFMT_DXV3:
    case D3DFMT_DXV4:
    case D3DFMT_DXV5:
#endif //VOLUME_DXT

        return TRUE;
    }

    return FALSE;
} // IsDXT

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::Requires4X4"

// returns TRUE for formats that have 4x4 rules
inline BOOL CPixel::Requires4X4(D3DFORMAT Format)
{

    switch (Format)
    {
        // normal DXTs
    case D3DFMT_DXT1:
    case D3DFMT_DXT2:
    case D3DFMT_DXT3:
    case D3DFMT_DXT4:
    case D3DFMT_DXT5:

#ifdef VOLUME_DXT
        // Volume dxts
    case D3DFMT_DXV1:
    case D3DFMT_DXV2:
    case D3DFMT_DXV3:
    case D3DFMT_DXV4:
    case D3DFMT_DXV5:
#endif //VOLUME_DXT

        return TRUE;
    }

    return FALSE;
} // Requires4X4


#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::HasStencilBits"

// returns TRUE if format has stencil bits
inline BOOL CPixel::HasStencilBits(D3DFORMAT Format)
{
    switch (Format)
    {
    case D3DFMT_S1D15:
    case D3DFMT_D15S1:
    case D3DFMT_S8D24:
    case D3DFMT_D24S8:
    case D3DFMT_X4S4D24:
    case D3DFMT_D24X4S4:
        return TRUE;
    }

    return FALSE;
} // HasStencilBits

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsVolumeDXT"

// returns TRUE if format is one of the DXTV family
inline BOOL CPixel::IsVolumeDXT(D3DFORMAT Format)
{
#ifdef VOLUME_DXT
    if (Format >= D3DFMT_DXV1 && Format <= D3DFMT_DXV5)
        return TRUE;
    else
#endif //VOLUME_DXT
        return FALSE;
} // IsVolumeDXT

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsPaletted"

// returns TRUE if Format is paletted
inline BOOL CPixel::IsPaletted(D3DFORMAT Format)
{
    return (Format == D3DFMT_P8) || (Format == D3DFMT_A8P8);
} // IsPaletted

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsFourCC"

// returns TRUE if Format is a FourCC
inline BOOL CPixel::IsFourCC(D3DFORMAT Format)
{
    DWORD dwFormat = (DWORD)Format;
    if (HIBYTE(LOWORD(dwFormat)) != 0)
    {
        // FourCC formats are non-zero for in their
        // third byte.
        return TRUE;
    }
    else
    {
        return FALSE;
    }
} // IsFourCC


#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsEnumeratableZ"

// IsEnumeratableZ
//
// We only want to enumerate D16 and the Z formats.  We have to know
// about the others so we have to keep them in our list, but we added this
// function so we'd never enumerate them to the app.

inline BOOL CPixel::IsEnumeratableZ (D3DFORMAT Format)
{
    if ((Format == D3DFMT_D16)          ||
        (Format == D3DFMT_D16_LOCKABLE) ||
        (Format == D3DFMT_D15S1)        ||
        (Format == D3DFMT_D24X8)        ||
        (Format == D3DFMT_D24X4S4)      ||
        (Format == D3DFMT_D24S8)        ||
        (Format == D3DFMT_D32))
    {
        return TRUE;
    }

    // IHV formats are creatable; so we let them pass
    if (IsIHVFormat(Format))
    {
        return TRUE;
    }

    return FALSE;
} // IsEnumeratableZ

#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsMappedDepthFormat"

// Is this a Z format that needs mapping b4 sending
// to the driver?
inline BOOL CPixel::IsMappedDepthFormat(D3DFORMAT Format)
{
    // D16_LOCKABLE and D32 do not need
    // mapping
    if ((Format == D3DFMT_D16)      ||
        (Format == D3DFMT_D15S1)    ||
        (Format == D3DFMT_D24X4S4)  ||
        (Format == D3DFMT_D24X8)    ||
        (Format == D3DFMT_D24S8))
    {
        return TRUE;
    }
    return FALSE;
} // IsMappedDepthFormat


#undef DPF_MODNAME
#define DPF_MODNAME "CPixel::IsNonLockableZ"

// All depth formats other than D16 are currently
// defined to be non-lockable. This function will
// return FALSE for:
//      non-Z formats 
//      D16_LOCKABLE
//      IHV formats

inline BOOL CPixel::IsNonLockableZ(D3DFORMAT Format)
{
    if ((Format == D3DFMT_D16)      ||
        (Format == D3DFMT_D15S1)    ||
        (Format == D3DFMT_D24X8)    ||
        (Format == D3DFMT_D24S8)    ||
        (Format == D3DFMT_D24X4S4)  ||
        (Format == D3DFMT_D32))
    {
        return TRUE;
    }

    // D16_LOCKABLE is lockable; and other
    // formats are either lockable i.e. IHV or
    // are not a Z format.

    return FALSE;
} // IsNonLockableZ

#endif // __PIXEL_HPP__