/******************************Module*Header*******************************\
* Module Name: perfsuite.cpp
*
* Copyright (c) 1991-1999 Microsoft Corporation
*
* Contains the test prototypes and includes
*
\**************************************************************************/

#include "perftest.h"
#include <winuser.h>

/***************************************************************************\
* TestSuite::TestSuite
*
\***************************************************************************/

TestSuite::TestSuite()
{
}

/***************************************************************************\
* TestSuite::~TestSuite
*
\***************************************************************************/

TestSuite::~TestSuite()
{
}

/***************************************************************************\
* TestSuite::InitializeDestination
*
* Create the destination to be used by the tests.  Could be a particular
* format for the screen, a bitmap, or a DIB.
*
* Returns:
*
*   *bitmapResult - if a GDI+ Bitmap is to be used (use g.GetHDC() to draw
*                   to via GDI)
*   *hbitmapResult - if a GDI bitmap is to be used (use Graphics(hdc) to
*                   draw to via GDI+)
*   both NULL - if the screen is to be used
*
\***************************************************************************/

BOOL 
TestSuite::InitializeDestination(
    DestinationType destinationIndex,
    Bitmap **bitmapResult,
    HBITMAP *hbitmapResult
    )
{
    Graphics *g = NULL;
    HDC hdc = 0;
    INT screenDepth = 0;
    PixelFormat bitmapFormat = PixelFormatMax;
    ULONG *bitfields;
    Bitmap *bitmap;
    HBITMAP hbitmap;

    union 
    {
        BITMAPINFO bitmapInfo;
        BYTE padding[sizeof(BITMAPINFO) + 3*sizeof(RGBQUAD)];
    };

    // Clear all state remembered or returned:

    ModeSet = FALSE;
    
    bitmap = NULL;
    hbitmap = NULL;

    HalftonePalette = NULL;
    
    // Initialize our DIB format in case we use it:

    RtlZeroMemory(&bitmapInfo, sizeof(bitmapInfo));

    bitmapInfo.bmiHeader.biSize   = sizeof(BITMAPINFOHEADER);
    bitmapInfo.bmiHeader.biWidth  = TestWidth;
    bitmapInfo.bmiHeader.biHeight = TestHeight;
    bitmapInfo.bmiHeader.biPlanes = 1;
    bitfields = reinterpret_cast<ULONG*>(&bitmapInfo.bmiColors[0]);

    // First handle any destinations that need to change the color depth:

    switch (destinationIndex)
    {
    case Destination_Screen_Current:
        break;
    
    case Destination_Screen_800_600_8bpp_HalftonePalette:
        HalftonePalette = DllExports::GdipCreateHalftonePalette();
        if (!HalftonePalette)
        {
            return FALSE;
        }
        screenDepth = 8;
        break;
    
    case Destination_Screen_800_600_8bpp_DefaultPalette:
        screenDepth = 8;
        break;

    case Destination_Screen_800_600_16bpp:
        screenDepth = 16;
        break;

    case Destination_Screen_800_600_24bpp:
        screenDepth = 24;
        break;

    case Destination_Screen_800_600_32bpp:
        screenDepth = 32;
        break;

    case Destination_CompatibleBitmap_8bpp:

        // We want to emulate a compatible bitmap at 8bpp.  Because of palette
        // issues, we really have to switch to 8bpp mode to do that.

        screenDepth = 8;
        break;

    case Destination_DIB_15bpp:
        bitmapInfo.bmiHeader.biBitCount    = 16;
        bitmapInfo.bmiHeader.biCompression = BI_BITFIELDS;
        bitfields[0]                       = 0x7c00;
        bitfields[1]                       = 0x03e0;
        bitfields[2]                       = 0x001f;
        break;

    case Destination_DIB_16bpp:
        bitmapInfo.bmiHeader.biBitCount    = 16;
        bitmapInfo.bmiHeader.biCompression = BI_BITFIELDS;
        bitfields[0]                       = 0xf800;
        bitfields[1]                       = 0x07e0;
        bitfields[2]                       = 0x001f;
        break;

    case Destination_DIB_24bpp:
        bitmapInfo.bmiHeader.biBitCount    = 24;
        bitmapInfo.bmiHeader.biCompression = BI_RGB;
        break;

    case Destination_DIB_32bpp:
        bitmapInfo.bmiHeader.biBitCount    = 32;
        bitmapInfo.bmiHeader.biCompression = BI_RGB;
        break;

    case Destination_Bitmap_32bpp_ARGB:
        bitmapFormat = PixelFormat32bppARGB;
        break;
    
    case Destination_Bitmap_32bpp_PARGB:
        bitmapFormat = PixelFormat32bppPARGB;
        break;
        
    default:
        return FALSE;
    }

    // Now that we've figured out what to do, actually create our stuff:

    if (bitmapInfo.bmiHeader.biBitCount != 0)
    {
        // It's a DIB:

        VOID* drawBits;
        HDC hdcScreen = GetDC(NULL);
        hbitmap = CreateDIBSection(hdcScreen,
                                   &bitmapInfo,
                                   DIB_RGB_COLORS,
                                   (VOID**) &drawBits,
                                   NULL,
                                   0);
        ReleaseDC(NULL, hdcScreen);

        if (!hbitmap)
            return(FALSE);
    }
    else if (bitmapFormat != PixelFormatMax)
    {
        // It's a Bitmap:

        bitmap = new Bitmap(TestWidth, TestHeight, bitmapFormat);
        if (!bitmap)
            return(FALSE);
    }
    else
    {
        // It's to the screen (or a weird 8bpp compatible bitmap):
    
        if (screenDepth != 0)
        {
            // We have to do a mode change:

            DEVMODE devMode;
    
            devMode.dmSize       = sizeof(DEVMODE);
            devMode.dmBitsPerPel = screenDepth;
            devMode.dmPelsWidth  = TestWidth;
            devMode.dmPelsHeight = TestHeight;
            devMode.dmFields     = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
    
            // Note that we invoke CDS_FULLSCREEN to tell the system that
            // the mode change is temporary (and so that User won't resize
            // all the windows on the desktop):
    
            if (ChangeDisplaySettings(&devMode, CDS_FULLSCREEN) 
                    != DISP_CHANGE_SUCCESSFUL)
            {
                return(FALSE);
            }

            // Remember that the mode was set:

            ModeSet = TRUE;
            
            // Wait several seconds to allow other OS threads to page in and
            // repaint the task bar, etc.  We don't want that polluting our 
            // perf numbers.
     
            Sleep(5000);
        }
        
        // Handle that 8bpp comaptible bitmap special case:
        
        if (destinationIndex == Destination_CompatibleBitmap_8bpp)
        {
            HDC hdcScreen = GetDC(NULL);
            hbitmap = CreateCompatibleBitmap(hdcScreen, TestWidth, TestHeight);
            ReleaseDC(NULL, hdcScreen);

            if (!hbitmap)
                return(FALSE);
        }
    }

    *hbitmapResult = hbitmap;
    *bitmapResult = bitmap;

    return(TRUE);
}

/***************************************************************************\
* TestSuite::UninitializeDestination
*
\***************************************************************************/

VOID
TestSuite::UninitializeDestination(
    DestinationType destinationIndex,
    Bitmap *bitmap,
    HBITMAP hbitmap
    )
{
    if (ModeSet)
    {
        ChangeDisplaySettings(NULL, 0);
    }
    
    if (HalftonePalette)
    {
        DeleteObject(HalftonePalette);
    }

    DeleteObject(hbitmap);
    delete bitmap;
}

/***************************************************************************\
* TestSuite::InitializeApi
*
* If 'Api_GdiPlus', returns a 'Graphics*' that can be used to render to
* the specified surface.
*
* If 'Api_Gdi', returns an 'HDC' that can be use to render to the specified
* surface.
*
* The surface is tried in the following order:
*
*   1. Bitmap* (if non-NULL)
*   2. HBITMAP (if non-NULL)
*   3. HWND
*
\***************************************************************************/

BOOL
TestSuite::InitializeApi(
    ApiType apiIndex,
    Bitmap *bitmap,
    HBITMAP hbitmap,
    HWND hwnd,
    Graphics **gResult,
    HDC *hdcResult)
{
    Graphics *g = NULL;
    HDC hdc = NULL;

    OldPalette = NULL;
    
    if (bitmap != NULL)
    {
        g = new Graphics(bitmap);
        if (!g)
            return(FALSE);

        if (apiIndex == Api_Gdi)
        {
            hdc = g->GetHDC();
            if (!hdc)
            {
                delete g;
                return(FALSE);
            }
        }
    }
    else if (hbitmap != NULL)
    {
        HDC hdcScreen = GetDC(hwnd);
        hdc = CreateCompatibleDC(hdcScreen);
        SelectObject(hdc, hbitmap);
        ReleaseDC(hwnd, hdcScreen);

        if (apiIndex == Api_GdiPlus)
        {
            g = new Graphics(hdc);
            if (!g)
            {
                DeleteObject(hdc);
                return(FALSE);
            }
        }
    }
    else
    {
        
        hdc = GetDC(hwnd);
        if (!hdc)
            return(FALSE);

        if (HalftonePalette)
        {
            OldPalette = SelectPalette(hdc, HalftonePalette, FALSE);
            RealizePalette(hdc);   
        }
        
        if (apiIndex == Api_GdiPlus)
        {
            g = new Graphics(hdc);
            if (!g)
                return(FALSE);
        }
    }

    *gResult = g;
    *hdcResult = hdc;

    return(TRUE);
}

/***************************************************************************\
* TestSuite::UninitializeApi
*
\***************************************************************************/

VOID
TestSuite::UninitializeApi(
    ApiType apiIndex,
    Bitmap *bitmap,
    HBITMAP hbitmap,
    HWND hwnd,
    Graphics *g,
    HDC hdc)
{
    if (bitmap != NULL)
    {
        if (apiIndex == Api_Gdi)
            g->ReleaseHDC(hdc);

        delete g;
    }
    else if (hbitmap != NULL)
    {
        if (apiIndex == Api_GdiPlus)
            delete g;

        DeleteObject(hdc);
    }
    else
    {
        if (apiIndex == Api_GdiPlus)
            delete g;
        
        if (OldPalette)
        {
            SelectPalette(hdc, OldPalette, FALSE);
            OldPalette = NULL;
        }
        
        ReleaseDC(hwnd, hdc);
    }
}

/***************************************************************************\
* TestSuite::InitializeState
*
\***************************************************************************/

BOOL
TestSuite::InitializeState(
    ApiType apiIndex,
    StateType stateIndex,
    Graphics *g,
    HDC hdc)
{
    if (apiIndex == Api_GdiPlus)
    {
        SavedState = g->Save();
        if (!SavedState)
            return(FALSE);

        switch (stateIndex)
        {
        case State_Antialias:
            g->SetSmoothingMode(SmoothingModeAntiAlias);
            g->SetTextRenderingHint(TextRenderingHintAntiAlias); 
            break;
        }
    }
    else
    {
        // Do stuff to 'hdc'
    }

    return(TRUE);
}

/***************************************************************************\
* TestSuite::UninitializeState
*
\***************************************************************************/

VOID
TestSuite::UninitializeState(
    ApiType apiIndex,
    StateType stateIndex,
    Graphics *g,
    HDC hdc)
{
    if (apiIndex == Api_GdiPlus)
    {
        g->Restore(SavedState);
    }
    else
    {
        // Do stuff to 'hdc'
    }
}

/***************************************************************************\
* TestSuite::Run
*
\***************************************************************************/

void TestSuite::Run(HWND hwnd)
{
    INT i;
    Graphics *g;
    HDC hdc;
    INT destinationIndex;
    INT apiIndex;
    INT stateIndex;
    INT testIndex;
    TCHAR string[2048];
    Bitmap *bitmap;
    HBITMAP hbitmap;
    
    CurrentTestIndex=0;

    // Maximize the window:

    ShowWindow(hwnd, SW_MAXIMIZE);

    // Zero out the results matrix

    for (i = 0; i < ResultCount(); i++)
    {
        ResultsList[i].Score = 0;
    }

    // Go through the matrix of tests to find stuff to run

    for (destinationIndex = 0;
         destinationIndex < Destination_Count; 
         destinationIndex++)
    {
        if (!DestinationList[destinationIndex].Enabled)
            continue;

        if (!InitializeDestination((DestinationType) destinationIndex, &bitmap, &hbitmap))
            continue;

        for (apiIndex = 0;
             apiIndex < Api_Count;
             apiIndex++)
        {
            if (!ApiList[apiIndex].Enabled)
                continue;

            if (!InitializeApi((ApiType) apiIndex, bitmap, hbitmap, hwnd, &g, &hdc))
                continue;

            for (stateIndex = 0;
                 stateIndex < State_Count;
                 stateIndex++)
            {
                if (!StateList[stateIndex].Enabled)
                    continue;

                if (!InitializeState((ApiType) apiIndex, (StateType) stateIndex, g, hdc))
                    continue;
    
                for (testIndex = 0;
                     testIndex < Test_Count;
                     testIndex++)
                {
                    if (!TestList[testIndex].Enabled)
                        continue;
    
                    _stprintf(string, 
                              _T("[%s] [%s] [%s] [%s]"),
                              ApiList[apiIndex].Description,
                              DestinationList[destinationIndex].Description,
                              StateList[stateIndex].Description,
                              TestList[testIndex].TestEntry->Description);

                    SetWindowText(hwnd, string); 
                    
                    if (Icecap && FoundIcecap)
                    {
                        // Save the test information so that we can
                        // add it to the profile
                        
                        CurrentTestIndex++;

                        #if UNICODE
                            
                            WideCharToMultiByte(
                                CP_ACP,
                                0,
                                string,
                                -1,
                                CurrentTestDescription,
                                2048,
                                NULL,
                                NULL);

                        #else
                        
                            strncpy(CurrentTestDescription, string, 2048);

                        #endif
                    }

                    // Woo hoo, everything is now set up and we're ready
                    // to run a test!

                    if (apiIndex == Api_GdiPlus)
                    {
                        GraphicsState oldState = g->Save();

                        ResultsList[ResultIndex(destinationIndex, 
                                                apiIndex, 
                                                stateIndex, 
                                                testIndex)].Score
                            = TestList[testIndex].TestEntry->Function(g, NULL);

                        g->Restore(oldState);
                    }
                    else
                    {
                        SaveDC(hdc);

                        ResultsList[ResultIndex(destinationIndex, 
                                                apiIndex, 
                                                stateIndex, 
                                                testIndex)].Score
                            = TestList[testIndex].TestEntry->Function(NULL, hdc);

                        RestoreDC(hdc, -1);
                    }

                    // Copy the result to the screen if it was from a bitmap:

                    if (bitmap)
                    {
                        Graphics gScreen(hwnd);
                        gScreen.DrawImage(bitmap, 0, 0);
                    }
                    else if (hbitmap)
                    {
                        // This will use the source 'hdc', which may have a
                        // transform set on it.  Oh well!

                        HDC hdcScreen = GetDC(hwnd);
                        BitBlt(hdcScreen, 0, 0, TestWidth, TestHeight, hdc, 0, 0, SRCCOPY);
                        ReleaseDC(hwnd, hdcScreen);
                    }
                }

                UninitializeState((ApiType) apiIndex, (StateType) stateIndex, g, hdc);
            }

            UninitializeApi((ApiType) apiIndex, bitmap, hbitmap, hwnd, g, hdc);
        }

        UninitializeDestination((DestinationType) destinationIndex, bitmap, hbitmap);
    }

    // We're done!

    CreatePerformanceReport(ResultsList, ExcelOut);
}

/******************************Public*Routine******************************\
* bFillBitmapInfo
*
* Fills in the fields of a BITMAPINFO so that we can create a bitmap
* that matches the format of the display.
*
* This is done by creating a compatible bitmap and calling GetDIBits
* to return the color masks.  This is done with two calls.  The first
* call passes in biBitCount = 0 to GetDIBits which will fill in the
* base BITMAPINFOHEADER data.  The second call to GetDIBits (passing
* in the BITMAPINFO filled in by the first call) will return the color
* table or bitmasks, as appropriate.
*
* Returns:
*   TRUE if successful, FALSE otherwise.
*
* History:
*
*  20-Jan-2000 [gilmanw]
* Removed code to set color table for 8bpp and less DIBs since calling
* code will not create such DIBs.
*
*  07-Jun-1995 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static BOOL
bFillBitmapInfo(HDC hdc, BITMAPINFO *pbmi)
{
    HBITMAP hbm;
    BOOL    bRet = FALSE;

    //
    // Create a dummy bitmap from which we can query color format info
    // about the device surface.
    //

    if ( (hbm = CreateCompatibleBitmap(hdc, 1, 1)) != NULL )
    {
        pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

        //
        // Call first time to fill in BITMAPINFO header.
        //

        GetDIBits(hdc, hbm, 0, 0, NULL, pbmi, DIB_RGB_COLORS);

        if ( pbmi->bmiHeader.biCompression == BI_BITFIELDS )
        {
            //
            // Call a second time to get the color masks.
            // It's a GetDIBits Win32 "feature".
            //

            GetDIBits(hdc, hbm, 0, pbmi->bmiHeader.biHeight, NULL, pbmi,
                      DIB_RGB_COLORS);
        }

        bRet = TRUE;

        DeleteObject(hbm);
    }

    return bRet;
}

/******************************Public*Routine******************************\
* CreateCompatibleDIB2
*
* Create a DIB section with a optimal format w.r.t. the specified device.
*
* Parameters
*
*     hdc
*
*         Specifies display DC used to determine format.  Must be a direct DC
*         (not an info or memory DC).
*
*     width
*
*         Specifies the width of the bitmap.
*
*     height
*
*         Specifies the height of the bitmap.
*
* Return Value
*
*     The return value is the handle to the bitmap created.  If the function
*     fails, the return value is NULL.
*
* Comments
*
*     For devices that are <= 8bpp, a normal compatible bitmap is
*     created (i.e., CreateCompatibleBitmap is called).  I have a
*     different version of this function that will create <= 8bpp
*     DIBs.  However, DIBs have the property that their color table
*     has precedence over the palette selected into the DC whereas
*     a bitmap from CreateCompatibleBitmap uses the palette selected
*     into the DC.  Therefore, in the interests of keeping this
*     version as close to CreateCompatibleBitmap as possible, I'll
*     revert to CreateCompatibleBitmap for 8bpp or less.
*
* History:
*  19-Jan-2000 [gilmanw]
* Adapted original "fastdib" version for maximum compatibility with
* CreateCompatibleBitmap.
*
*  23-Jan-1996 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

HBITMAP 
CreateCompatibleDIB2(HDC hdc, int width, int height)
{
    HBITMAP hbmRet = (HBITMAP) NULL;
    BYTE aj[sizeof(BITMAPINFO) + (sizeof(RGBQUAD) * 255)];
    BITMAPINFO *pbmi = (BITMAPINFO *) aj;

    //
    // Redirect 8 bpp or lower to CreateCompatibleBitmap.
    //

    if ( (GetDeviceCaps(hdc, BITSPIXEL) * GetDeviceCaps(hdc, PLANES)) <= 8 )
    {
        return CreateCompatibleBitmap(hdc, width, height);
    }

    //
    // Validate hdc.
    //

    if ( GetObjectType(hdc) != OBJ_DC )
    {
        return hbmRet;
    }

    memset(aj, 0, sizeof(aj));
    if ( bFillBitmapInfo(hdc, pbmi) )
    {
        VOID *pvBits;

        //
        // Change bitmap size to match specified dimensions.
        //

        pbmi->bmiHeader.biWidth = width;
        pbmi->bmiHeader.biHeight = height;
        if (pbmi->bmiHeader.biCompression == BI_RGB)
        {
            pbmi->bmiHeader.biSizeImage = 0;
        }
        else
        {
            if ( pbmi->bmiHeader.biBitCount == 16 )
                pbmi->bmiHeader.biSizeImage = width * height * 2;
            else if ( pbmi->bmiHeader.biBitCount == 32 )
                pbmi->bmiHeader.biSizeImage = width * height * 4;
            else
                pbmi->bmiHeader.biSizeImage = 0;
        }
        pbmi->bmiHeader.biClrUsed = 0;
        pbmi->bmiHeader.biClrImportant = 0;

        //
        // Create the DIB section.  Let Win32 allocate the memory and return
        // a pointer to the bitmap surface.
        //

        hbmRet = CreateDIBSection(hdc, pbmi, DIB_RGB_COLORS, &pvBits, NULL, 0);
    }

    return hbmRet;
}

////////////////////////////////////////////////////////////////////////
//
// Timer Utility Functions
//
////////////////////////////////////////////////////////////////////////

LONGLONG StartCounter;      // Timer global, to be set by StartTimer()

LONGLONG MinimumCount;      // Minimum number of performance counter ticks
                            //   that must elapse before a test is considered
                            //   'done'
                            
LONGLONG CountsPerSecond;   // Frequency of the performance counter

UINT Iterations;            // Timer global, to be set by StartTimer() and
                            //   incremented for every call to EndTimer()

UINT MinIterations;         // Minimum number of iterations of the test to
                            //   be done

/***************************************************************************\
* StartTimer
*
* Called by timing routine to start the timer.
*
\***************************************************************************/

void StartTimer()
{
    if (Icecap && FoundIcecap)
    {
        ICStartProfile(PROFILE_GLOBALLEVEL, PROFILE_CURRENTID);
        ICCommentMarkProfile(CurrentTestIndex, CurrentTestDescription);
    }

    // Disable the cursor so that it doesn't interfere with the timing:

    ShowCursor(FALSE);

    if (TestRender)
    {
        // Rig it so that we do only one iteration of the test.

        MinIterations = 0;
        MinimumCount = 0;
    }
    else
    {
        // Somewhat randomly choose 1 second as the minimum counter time:
    
        QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&CountsPerSecond));
        MinimumCount = CountsPerSecond;
    
        // Okay, start timing!
    
        Iterations = 0;
        QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&StartCounter));
    }
}

/***************************************************************************\
* EndTimer
*
* Called by timing routine to see if it's okay to stop timing.  Timing
* can stop if 2 conditions are satisfied:
*
*   1.  We've gone the minimum time duration (to ensure that we good
*       good accuracy from the timer functions we're using)
*   2.  We've done the minimum number of iterations (to ensure, if the
*       routine being timed is very very slow, that we do more than
*       one iteration)
*
\***************************************************************************/

BOOL EndTimer()
{
    LONGLONG counter;

    // Always do at least MIN_ITERATIONS iterations (and only check
    // the timer that frequently as well):

    Iterations++;
    if (Iterations & MinIterations)
        return(FALSE);

    // Query the performance counter, and bail if for some bizarre reason
    // this computer doesn't support a high resolution timer (which I think
    // all do now-a-days):

    if (!QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&counter)))
        return(TRUE);

    // Ensure that we get good timer accuracy by going for the minimum
    // amount of time:

    if ((counter - StartCounter) <= MinimumCount)
        return(FALSE);

    ShowCursor(TRUE);

    if (Icecap && FoundIcecap)
    {
        ICStopProfile(PROFILE_GLOBALLEVEL, PROFILE_CURRENTID);
    }

    // Okay, you can stop timing!

    return(TRUE);
}

/***************************************************************************\
* GetTimer
*
* Should only be called after EndTimer() returns TRUE.  Returns the
* time in seconds, and the number of iterations benchmarked.
*
\***************************************************************************/

void GetTimer(float* seconds, UINT* iterations)
{
    LONGLONG counter;

    // Note that we re-check the timer here to account for any
    // flushes that the caller may have needed to have done after
    // the EndTimer() call:

    QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&counter));

    if ((TestRender) || (CountsPerSecond == 0))
    {
        // Either the timer doesn't work, or we're doing a 'test render':

        *seconds = 1000000.0f;
        *iterations = 1;
    }
    else
    {
        // Woo hoo, we're done!

        *seconds = static_cast<float>(counter - StartCounter) / CountsPerSecond;
        *iterations = Iterations;
    }
}