/******************************************************************************\
*
* $Workfile:   heap.c  $
*
* This module contains the routines for a 2-d heap.  It is used primarily
* for allocating space for device-format-bitmaps in off-screen memory.
*
* Off-screen bitmaps are a big deal on NT because:
*
*    1) It reduces the working set.  Any bitmap stored in off-screen
*       memory is a bitmap that isn't taking up space in main memory.
*
*    2) There is a speed win by using the accelerator hardware for
*       drawing, in place of NT's GDI code.  NT's GDI is written entirely
*       in 'C++' and perhaps isn't as fast as it could be.
*
*    3) It leads naturally to nifty tricks that can take advantage of
*       the hardware, such as MaskBlt support and cheap double buffering
*       for OpenGL.
*
* The heap algorithm employed herein attempts to solve an unsolvable
* problem: the problem of keeping arbitrary sized bitmaps as packed as
* possible in a 2-d space, when the bitmaps can come and go at random.
*
* This problem is due entirely to the nature of the hardware for which this
* driver is written: the hardware treats everything as 2-d quantities.  If
* the hardware bitmap pitch could be changed so that the bitmaps could be
* packed linearly in memory, the problem would be infinitely easier (it is
* much easier to track the memory, and the accelerator can be used to re-pack
* the heap to avoid segmentation).
*
* If your hardware can treat bitmaps as one dimensional quantities (as can
* the XGA and ATI), by all means please implement a new off-screen heap.
*
* When the heap gets full, old allocations will automatically be punted
* from off-screen and copied to DIBs, which we'll let GDI draw on.
*
* Note that this heap manages reverse-L shape off-screen memory
* configurations (where the scan pitch is longer than the visible screen,
* such as happens at 800x600 when the scan length must be a multiple of
* 1024).
*
* NOTE: All heap operations must be done under some sort of synchronization,
*       whether it's controlled by GDI or explicitly by the driver.  All
*       the routines in this module assume that they have exclusive access
*       to the heap data structures; multiple threads partying in here at
*       the same time would be a Bad Thing.  (By default, GDI does NOT
*       synchronize drawing on device-created bitmaps.)
*
* Copyright (c) 1993-1995 Microsoft Corporation
* Copyright (c) 1996 Cirrus Logic, Inc.
*
* $Log:   S:/projects/drivers/ntsrc/display/heap.c_v  $
 * 
 *    Rev 1.3   Apr 03 1997 15:38:54   unknown
 *  
 *
 *    Rev 1.2   28 Mar 1997 16:08:50   PLCHU
 *
 *
 *    Rev 1.1   Oct 10 1996 15:37:42   unknown
 *
*
*    Rev 1.8   12 Aug 1996 17:11:22   frido
* hp#1 - Commented new heap stuff.
* hp#2 - Keep enough memory for WinBench bitmap.
*
*    Rev 1.7   12 Aug 1996 16:53:30   frido
* Added NT 3.5x/4.0 auto detection.
*
*    Rev 1.6   23 Jul 1996 17:48:38   frido
* Removed creation of small device bitmaps.
* Never reject the WinBench bitmap!
*
*    Rev 1.5   16 Jul 1996 18:55:52   frido
* Changed splitting of new block to cause less fragmentation.
*
* pat04: 12-20-96 : Supported NT3.51 software cursor with panning scrolling
* pat08: 04-01-97 : SW cursor problem due to code merge. Frido had turned
*                   of allocation of small device bitmaps for optimization,
*                   --- winnt to return a null ptr to a color bitmap mask.
*
\******************************************************************************/

#include "precomp.h"

#define OH_ALLOC_SIZE   4000        // Do all memory allocations in 4k chunks
#define OH_QUANTUM      8           // The minimum dimension of an allocation
#define CXCY_SENTINEL   0x7fffffff  // The sentinel at the end of the available
                                    //  list has this very large 'cxcy' value

// This macro results in the available list being maintained with a
// cx-major, cy-minor sort:

#define CXCY(cx, cy) (((cx) << 16) | (cy))

#if DBG
    BOOL gbEnableDFB = TRUE;
#endif

/******************************Public*Routine******************************\
* OH* pohNewNode
*
* Allocates a basic memory unit in which we'll pack our data structures.
*
* Since we'll have a lot of OH nodes, most of which we will be
* occasionally traversing, we do our own memory allocation scheme to
* keep them densely packed in memory.
*
* It would be the worst possible thing for the working set to simply
* call EngAllocMem(sizeof(OH)) every time we needed a new node.  There
* would be no locality; OH nodes would get scattered throughout memory,
* and as we traversed the available list for one of our allocations,
* it would be far more likely that we would hit a hard page fault.
\**************************************************************************/

OH* pohNewNode(
PDEV*   ppdev)
{
    LONG     i;
    LONG     cOhs;
    OHALLOC* poha;
    OH*      poh;

    if (ppdev->heap.pohFreeList == NULL)
    {
        // We zero-init to initialize all the OH flags, and to help in
        // debugging (we can afford to do this since we'll be doing this
        // very infrequently):

        poha = ALLOC(OH_ALLOC_SIZE);
        if (poha == NULL)
            return(NULL);

        // Insert this OHALLOC at the begining of the OHALLOC chain:

        poha->pohaNext  = ppdev->heap.pohaChain;
        ppdev->heap.pohaChain = poha;

        // This has a '+ 1' because OHALLOC includes an extra OH in its
        // structure declaration:

        cOhs = (OH_ALLOC_SIZE - sizeof(OHALLOC)) / sizeof(OH) + 1;

        // The big OHALLOC allocation is simply a container for a bunch of
        // OH data structures in an array.  The new OH data structures are
        // linked together and added to the OH free list:

        poh = &poha->aoh[0];
        for (i = cOhs - 1; i != 0; i--)
        {
            poh->pohNext = poh + 1;
            poh          = poh + 1;
        }

        poh->pohNext      = NULL;
        ppdev->heap.pohFreeList = &poha->aoh[0];
    }

    poh = ppdev->heap.pohFreeList;
    ppdev->heap.pohFreeList = poh->pohNext;

    return(poh);
}

/******************************Public*Routine******************************\
* VOID vOhFreeNode
*
* Frees our basic data structure allocation unit by adding it to a free
* list.
*
\**************************************************************************/

VOID vOhFreeNode(
PDEV*   ppdev,
OH*     poh)
{
    if (poh == NULL)
        return;

    poh->pohNext            = ppdev->heap.pohFreeList;
    ppdev->heap.pohFreeList = poh;
    poh->ofl                = 0;
}

/******************************Public*Routine******************************\
* OH* pohFree
*
* Frees an off-screen heap allocation.  The free space will be combined
* with any adjacent free spaces to avoid segmentation of the 2-d heap.
*
* Note: A key idea here is that the data structure for the upper-left-
*       most node must be kept at the same physical CPU memory so that
*       adjacency links are kept correctly (when two free spaces are
*       merged, the lower or right node can be freed).
*
\**************************************************************************/

OH* pohFree(
PDEV*   ppdev,
OH*     poh)
{
    ULONG cxcy;
    OH*   pohBeside;
    OH*   pohNext;
    OH*   pohPrev;
#if 1 //hp#1
    BOOL  bUpdate;
#endif

    if (poh == NULL)
        return(NULL);

#if 1 //hp#1
    bUpdate = (poh->ofl & OFL_PERMANENT) != 0;
#endif

    DISPDBG((4, "Freeing %d x %d at (%d, %d)", poh->cx, poh->cy, poh->x,
             poh->y));

    #if DEBUG_HEAP
    {
        RECTL           rclBitmap;
        RBRUSH_COLOR    rbc;
        LONG            xOffset;
        LONG            yOffset;
        LONG            xyOffset;

        rclBitmap.left   = poh->x;
        rclBitmap.top    = poh->y;
        rclBitmap.right  = poh->x + poh->cx;
        rclBitmap.bottom = poh->y + poh->cy;

        xOffset  = ppdev->xOffset;
        yOffset  = ppdev->yOffset;
        xyOffset = ppdev->xyOffset;

        ppdev->xOffset  = 0;
        ppdev->yOffset  = 0;
        ppdev->xyOffset = 0;

        ppdev->pfnFillSolid(ppdev, 1, &rclBitmap, 0x0, rbc, NULL);

        ppdev->xOffset  = xOffset;
        ppdev->yOffset  = yOffset;
        ppdev->xyOffset = xyOffset;
    }
    #endif

    // Update the uniqueness to show that space has been freed, so that
    // we may decide to see if some DIBs can be moved back into off-screen
    // memory:

    ppdev->iHeapUniq++;

MergeLoop:

#if 0 //hp#1
    ASSERTDD(!(poh->ofl & OFL_PERMANENT), "Can't free permanents for now");
#endif

    // Try merging with the right sibling:

    pohBeside = poh->pohRight;
    if ((pohBeside->ofl & OFL_AVAILABLE)        &&
        (pohBeside->cy      == poh->cy)         &&
        (pohBeside->pohUp   == poh->pohUp)      &&
        (pohBeside->pohDown == poh->pohDown)    &&
        (pohBeside->pohRight->pohLeft != pohBeside))
    {
        // Add the right rectangle to ours:

        poh->cx      += pohBeside->cx;
        poh->pohRight = pohBeside->pohRight;

        // Remove 'pohBeside' from the ??? list and free it:

        pohBeside->pohNext->pohPrev = pohBeside->pohPrev;
        pohBeside->pohPrev->pohNext = pohBeside->pohNext;

        vOhFreeNode(ppdev, pohBeside);
        goto MergeLoop;
    }

    // Try merging with the lower sibling:

    pohBeside = poh->pohDown;
    if ((pohBeside->ofl & OFL_AVAILABLE)        &&
        (pohBeside->cx       == poh->cx)        &&
        (pohBeside->pohLeft  == poh->pohLeft)   &&
        (pohBeside->pohRight == poh->pohRight)  &&
        (pohBeside->pohDown->pohUp != pohBeside))
    {
        poh->cy     += pohBeside->cy;
        poh->pohDown = pohBeside->pohDown;

        pohBeside->pohNext->pohPrev = pohBeside->pohPrev;
        pohBeside->pohPrev->pohNext = pohBeside->pohNext;

        vOhFreeNode(ppdev, pohBeside);
        goto MergeLoop;
    }

    // Try merging with the left sibling:

    pohBeside = poh->pohLeft;
    if ((pohBeside->ofl & OFL_AVAILABLE)        &&
        (pohBeside->cy       == poh->cy)        &&
        (pohBeside->pohUp    == poh->pohUp)     &&
        (pohBeside->pohDown  == poh->pohDown)   &&
        (pohBeside->pohRight == poh)            &&
        (poh->pohRight->pohLeft != poh))
    {
        // We add our rectangle to the one to the left:

        pohBeside->cx      += poh->cx;
        pohBeside->pohRight = poh->pohRight;

        // Remove 'poh' from the ??? list and free it:

        poh->pohNext->pohPrev = poh->pohPrev;
        poh->pohPrev->pohNext = poh->pohNext;

        vOhFreeNode(ppdev, poh);

        poh = pohBeside;
        goto MergeLoop;
    }

    // Try merging with the upper sibling:

    pohBeside = poh->pohUp;
    if ((pohBeside->ofl & OFL_AVAILABLE)        &&
        (pohBeside->cx       == poh->cx)        &&
        (pohBeside->pohLeft  == poh->pohLeft)   &&
        (pohBeside->pohRight == poh->pohRight)  &&
        (pohBeside->pohDown  == poh)            &&
        (poh->pohDown->pohUp != poh))
    {
        pohBeside->cy      += poh->cy;
        pohBeside->pohDown  = poh->pohDown;

        poh->pohNext->pohPrev = poh->pohPrev;
        poh->pohPrev->pohNext = poh->pohNext;

        vOhFreeNode(ppdev, poh);

        poh = pohBeside;
        goto MergeLoop;
    }

    // Remove the node from the ???list if it was in use (we wouldn't
    // want to do this for a OFL_PERMANENT node that had been freed):

    poh->pohNext->pohPrev = poh->pohPrev;
    poh->pohPrev->pohNext = poh->pohNext;

    cxcy = CXCY(poh->cx, poh->cy);

    // Insert the node into the available list:

    pohNext = ppdev->heap.ohAvailable.pohNext;
    while (pohNext->cxcy < cxcy)
    {
        pohNext = pohNext->pohNext;
    }
    pohPrev = pohNext->pohPrev;

    pohPrev->pohNext = poh;
    pohNext->pohPrev = poh;
    poh->pohPrev     = pohPrev;
    poh->pohNext     = pohNext;
    poh->ofl         = OFL_AVAILABLE;
    poh->cxcy        = cxcy;

#if 1 //hp#1
    if (bUpdate)
    {
        vCalculateMaximum(ppdev);
    }
#endif

    // Return the node pointer for the new and improved available rectangle:
    return(poh);
}

/******************************Public*Routine******************************\
* OH* pohAllocate
*
* Allocates space for an off-screen rectangle.  It will attempt to find
* the smallest available free rectangle, and will allocate the block out
* of its upper-left corner.  The remaining two rectangles will be placed
* on the available free space list.
*
* If the rectangle would have been large enough to fit into off-screen
* memory, but there is not enough available free space, we will boot
* bitmaps out of off-screen and into DIBs until there is enough room.
*
\**************************************************************************/

OH* pohAllocate(
PDEV*   ppdev,
LONG    cxThis,             // Width of rectangle to be allocated
LONG    cyThis,             // Height of rectangle to be allocated
FLOH    floh)               // Allocation flags
{
    ULONG cxcyThis;         // Width and height search key
    OH*   pohThis;          // Points to found available rectangle we'll use
    ULONG cxcy;             // Temporary versions
    OH*   pohNext;
    OH*   pohPrev;

    LONG  cxRem;
    LONG  cyRem;

    OH*   pohBelow;
    LONG  cxBelow;
    LONG  cyBelow;

    OH*   pohBeside;
    LONG  cxBeside;
    LONG  cyBeside;

    DISPDBG((4, "Allocating %d x %d...", cxThis, cyThis));

    ASSERTDD((cxThis > 0) && (cyThis > 0), "Illegal allocation size");

    // Increase the width to get the proper alignment (thus ensuring that all
    // allocations will be properly aligned):

    cxThis = (cxThis + (HEAP_X_ALIGNMENT - 1)) & ~(HEAP_X_ALIGNMENT - 1);

    // We can't succeed if the requested rectangle is larger than the
    // largest possible available rectangle:

    if ((cxThis > ppdev->heap.cxMax) || (cyThis > ppdev->heap.cyMax))
    {
#if 1 //hp#2
        if ((cxThis != 400) && (cyThis != 90))
#endif
        {
            DISPDBG((4, "Failing pohAllocate... rectangle is larger than max allowed"));
            DISPDBG((4, "max = (%d,%d)", ppdev->heap.cxMax, ppdev->heap.cyMax));
            DISPDBG((4, "req = (%d,%d)", cxThis, cyThis));
            return(NULL);
        }
    }

    // Find the first available rectangle the same size or larger than
    // the requested one:

    cxcyThis = CXCY(cxThis, cyThis);
    pohThis  = ppdev->heap.ohAvailable.pohNext;
    while (pohThis->cxcy < cxcyThis)
    {
        pohThis = pohThis->pohNext;
    }

    while (pohThis->cy < cyThis)
    {
        pohThis = pohThis->pohNext;
    }

    if (pohThis->cxcy == CXCY_SENTINEL)
    {
        // There was no space large enough...

        if (floh & FLOH_ONLY_IF_ROOM)
        {
            DISPDBG((4, "Failing pohAllocate... no space large enough"));
            DISPDBG((4, "req = (%d,%d)", cxThis, cyThis));
            return(NULL);
        }

        // We couldn't find an available rectangle that was big enough
        // to fit our request.  So throw things out of the heap until we
        // have room:

        do {
            pohThis = ppdev->heap.ohDfb.pohPrev;  // Least-recently blitted

            ASSERTDD(pohThis != &ppdev->heap.ohDfb, "Ran out of in-use entries");

#if 1 //hp#1
            while (pohThis->ofl == OFL_PERMANENT)
            {
                pohThis = pohThis->pohPrev;
                if (pohThis == &ppdev->heap.ohDfb)
                {
                    DISPDBG((4, "Failing pohAllocate... not enough memory"));
                    DISPDBG((4, "req = (%d,%d)", cxThis, cyThis));
                    return(NULL);
                }
            }
#endif
            // We can safely exit here if we have to:

            pohThis = pohMoveOffscreenDfbToDib(ppdev, pohThis);
            if (pohThis == NULL)
            {
                DISPDBG((4, "Failing pohAllocate... failed to eject a dfb"));
                DISPDBG((4, "req = (%d,%d)", cxThis, cyThis));
                return(NULL);
            }

        } while ((pohThis->cx < cxThis) || (pohThis->cy < cyThis));
    }

    // We've now found an available rectangle that is the same size or
    // bigger than our requested rectangle.  We're going to use the
    // upper-left corner of our found rectangle, and divide the unused
    // remainder into two rectangles which will go on the available
    // list.

    // Compute the width of the unused rectangle to the right, and the
    // height of the unused rectangle below:

    cyRem = pohThis->cy - cyThis;
    cxRem = pohThis->cx - cxThis;

    // Given finite area, we wish to find the two rectangles that are
    // most square -- i.e., the arrangement that gives two rectangles
    // with the least perimiter:

    cyBelow  = cyRem;
    cxBeside = cxRem;

#if 1 //hp#1
    if ((cxRem <= cyRem) || (pohThis->cx >= ppdev->cxScreen))
#else
    if (cxRem <= cyRem)
#endif
    {
        cxBelow  = cxThis + cxRem;
        cyBeside = cyThis;
    }
    else
    {
        cxBelow  = cxThis;
        cyBeside = cyThis + cyRem;
    }

    // We only make new available rectangles of the unused right and bottom
    // portions if they're greater in dimension than OH_QUANTUM (it hardly
    // makes sense to do the book-work to keep around a 2-pixel wide
    // available space, for example):

    pohBeside = NULL;
    if (cxBeside >= OH_QUANTUM)
    {
        pohBeside = pohNewNode(ppdev);
        if (pohBeside == NULL)
            return(NULL);
    }

    pohBelow = NULL;
    if (cyBelow >= OH_QUANTUM)
    {
        pohBelow = pohNewNode(ppdev);
        if (pohBelow == NULL)
        {
            vOhFreeNode(ppdev, pohBeside);
            return(NULL);
        }

        // Insert this rectangle into the available list (which is
        // sorted on ascending cxcy):

        cxcy    = CXCY(cxBelow, cyBelow);
        pohNext = ppdev->heap.ohAvailable.pohNext;
        while (pohNext->cxcy < cxcy)
        {
            pohNext = pohNext->pohNext;
        }
        pohPrev = pohNext->pohPrev;

        pohPrev->pohNext  = pohBelow;
        pohNext->pohPrev  = pohBelow;
        pohBelow->pohPrev = pohPrev;
        pohBelow->pohNext = pohNext;

        // Now update the adjacency information:

        pohBelow->pohLeft  = pohThis->pohLeft;
        pohBelow->pohUp    = pohThis;
        pohBelow->pohRight = pohThis->pohRight;
        pohBelow->pohDown  = pohThis->pohDown;

        // Update the rest of the new node information:

        pohBelow->cxcy = cxcy;
        pohBelow->ofl  = OFL_AVAILABLE;

        pohBelow->x    = pohThis->x;
        pohBelow->y    = pohThis->y + cyThis;
        pohBelow->xy   = PELS_TO_BYTES(pohBelow->x)
                       + (pohBelow->y * ppdev->lDelta);

        pohBelow->cx   = cxBelow;
        pohBelow->cy   = cyBelow;

        // Modify the current node to reflect the changes we've made:

        pohThis->cy = cyThis;
    }

    if (cxBeside >= OH_QUANTUM)
    {
        // Insert this rectangle into the available list (which is
        // sorted on ascending cxcy):

        cxcy    = CXCY(cxBeside, cyBeside);
        pohNext = ppdev->heap.ohAvailable.pohNext;
        while (pohNext->cxcy < cxcy)
        {
            pohNext = pohNext->pohNext;
        }
        pohPrev = pohNext->pohPrev;

        pohPrev->pohNext   = pohBeside;
        pohNext->pohPrev   = pohBeside;
        pohBeside->pohPrev = pohPrev;
        pohBeside->pohNext = pohNext;

        // Now update the adjacency information:

        pohBeside->pohUp    = pohThis->pohUp;
        pohBeside->pohLeft  = pohThis;
        pohBeside->pohDown  = pohThis->pohDown;
        pohBeside->pohRight = pohThis->pohRight;

        // Update the rest of the new node information:

        pohBeside->cxcy = cxcy;
        pohBeside->ofl  = OFL_AVAILABLE;

        pohBeside->x    = pohThis->x + cxThis;
        pohBeside->y    = pohThis->y;
        pohBeside->xy   = PELS_TO_BYTES(pohBeside->x)
                        + (pohBeside->y * ppdev->lDelta);

        pohBeside->cx   = cxBeside;
        pohBeside->cy   = cyBeside;

        // Modify the current node to reflect the changes we've made:

        pohThis->cx = cxThis;
    }

    if (pohBelow != NULL)
    {
        pohThis->pohDown = pohBelow;
        if ((pohBeside != NULL) && (cyBeside == pohThis->cy))
            pohBeside->pohDown = pohBelow;
    }
    if (pohBeside != NULL)
    {
        pohThis->pohRight = pohBeside;
        if ((pohBelow != NULL) && (cxBelow == pohThis->cx))
            pohBelow->pohRight  = pohBeside;
    }

    pohThis->ofl    = OFL_INUSE;
    pohThis->cxcy   = CXCY(pohThis->cx, pohThis->cy);
    pohThis->pdsurf = NULL; // Caller is responsible for setting this field

    // Remove this from the available list:

    pohThis->pohPrev->pohNext = pohThis->pohNext;
    pohThis->pohNext->pohPrev = pohThis->pohPrev;

    // Now insert this at the head of the DFB list:

    pohThis->pohNext                   = ppdev->heap.ohDfb.pohNext;
    pohThis->pohPrev                   = &ppdev->heap.ohDfb;
    ppdev->heap.ohDfb.pohNext->pohPrev = pohThis;
    ppdev->heap.ohDfb.pohNext          = pohThis;

    DISPDBG((4, "   Allocated at (%d, %d)", pohThis->x, pohThis->y));

    // Calculate the effective start address for this bitmap in off-
    // screen memory:

    pohThis->pvScan0 = ppdev->pjScreen + (pohThis->y * ppdev->lDelta)
                                       + PELS_TO_BYTES(pohThis->x);
    return(pohThis);
}

/******************************Public*Routine******************************\
* VOID vCalculateMaxmimum
*
* Traverses the list of in-use and available rectangles to find the one
* with the maximal area.
*
\**************************************************************************/
VOID vCalculateMaximum(
PDEV*   ppdev)
{
    OH*     poh;
    OH*     pohSentinel;
    LONG    lArea;
    LONG    lMaxArea;
    LONG    cxMax;
    LONG    cyMax;
    LONG    i;

    lMaxArea = 0;
    cxMax    = 0;
    cyMax    = 0;

    // First time through, loop through the list of available rectangles:

    pohSentinel = &ppdev->heap.ohAvailable;

    for (i = 2; i != 0; i--)
    {
        for (poh = pohSentinel->pohNext; poh != pohSentinel; poh = poh->pohNext)
        {
#if 1 //hp#1
            if (poh->ofl & OFL_PERMANENT)
            {
                continue;
            }
#else
            ASSERTDD(!(poh->ofl & OFL_PERMANENT),
                     "Permanent in available/DFB chain?");
#endif

            // We don't have worry about this multiply overflowing
            // because we are dealing in physical screen coordinates,
            // which will probably never be more than 15 bits:

            lArea = poh->cx * poh->cy;
            if (lArea > lMaxArea)
            {
                cxMax    = poh->cx;
                cyMax    = poh->cy;
                lMaxArea = lArea;
            }
        }

        // Second time through, loop through the list of in-use rectangles:

        pohSentinel = &ppdev->heap.ohDfb;
    }

    // All that we are interested in is the dimensions of the rectangle
    // that has the largest possible available area (and remember that
    // there might not be any possible available area):

    ppdev->heap.cxMax = cxMax;
    ppdev->heap.cyMax = cyMax;

    DISPDBG((1, "Maximum heap: %d x %d", cxMax, cyMax));
}

/******************************Public*Routine******************************\
* OH* pohAllocatePermanent
*
* Allocates an off-screen rectangle that can never be booted of the heap.
* It's the caller's responsibility to manage the rectangle, which includes
* what to do with the memory in DrvAssertMode when the display is changed
* to full-screen mode.
*
\**************************************************************************/

OH* pohAllocatePermanent(
PDEV*   ppdev,
LONG    cx,
LONG    cy)
{
    OH*     poh;

    poh = pohAllocate(ppdev, cx, cy, 0);
    if (poh != NULL)
    {
        // Mark the rectangle as permanent:

        poh->ofl = OFL_PERMANENT;

#if 0 //hp#1
        // Remove the node from the most-recently blitted list:

        poh->pohPrev->pohNext = poh->pohNext;
        poh->pohNext->pohPrev = poh->pohPrev;
        poh->pohPrev = NULL;
        poh->pohNext = NULL;
#endif

        // Now calculate the new maximum size rectangle available in the
        // heap:
        vCalculateMaximum(ppdev);
    }

    return(poh);
}

/******************************Public*Routine******************************\
* BOOL bMoveDibToOffscreenDfbIfRoom
*
* Converts the DIB DFB to an off-screen DFB, if there's room for it in
* off-screen memory.
*
* Returns: FALSE if there wasn't room, TRUE if successfully moved.
*
\**************************************************************************/

BOOL bMoveDibToOffscreenDfbIfRoom(
PDEV*   ppdev,
DSURF*  pdsurf)
{
    OH*         poh;
    SURFOBJ*    pso;
    RECTL       rclDst;
    POINTL      ptlSrc;
    HSURF       hsurf;

    ASSERTDD(pdsurf->dt == DT_DIB,
             "Can't move a bitmap off-screen when it's already off-screen");

    DISPDBG((4, "Trying to reload %d x %d surface", pdsurf->sizl.cx,
             pdsurf->sizl.cy));

    // If we're in full-screen mode, we can't move anything to off-screen
    // memory:

    if (!ppdev->bEnabled)
        return(FALSE);

    poh = pohAllocate(ppdev, pdsurf->sizl.cx, pdsurf->sizl.cy,
                      FLOH_ONLY_IF_ROOM);
    if (poh == NULL)
    {
        // There wasn't any free room.

        return(FALSE);
    }

    // 'pdsurf->sizl' is the actual bitmap dimension, not 'poh->cx' or
    // 'poh->cy'.

    rclDst.left   = poh->x;
    rclDst.top    = poh->y;
    rclDst.right  = rclDst.left + pdsurf->sizl.cx;
    rclDst.bottom = rclDst.top  + pdsurf->sizl.cy;

    ptlSrc.x      = 0;
    ptlSrc.y      = 0;

    ppdev->pfnPutBits(ppdev, pdsurf->pso, &rclDst, &ptlSrc);

    // Update the data structures to reflect the new off-screen node:

    pso           = pdsurf->pso;
    pdsurf->dt    = DT_SCREEN;
    pdsurf->poh   = poh;
    poh->pdsurf   = pdsurf;

    // Now free the DIB.  Get the hsurf from the SURFOBJ before we unlock
    // it (it's not legal to dereference psoDib when it's unlocked):

    hsurf = pso->hsurf;
    EngUnlockSurface(pso);
    EngDeleteSurface(hsurf);

    return(TRUE);
}

/******************************Public*Routine******************************\
* OH* pohMoveOffscreenDfbToDib
*
* Converts the DFB from being off-screen to being a DIB.
*
* Note: The caller does NOT have to call 'pohFree' on 'poh' after making
*       this call.
*
* Returns: NULL if the function failed (due to a memory allocation).
*          Otherwise, it returns a pointer to the coalesced off-screen heap
*          node that has been made available for subsequent allocations
*          (useful when trying to free enough memory to make a new
*          allocation).
\**************************************************************************/

OH* pohMoveOffscreenDfbToDib(
PDEV*   ppdev,
OH*     poh)
{
    DSURF*   pdsurf;
    HBITMAP  hbmDib;
    SURFOBJ* pso;
    RECTL    rclDst;
    POINTL   ptlSrc;

    DISPDBG((4, "Throwing out %li x %li at (%li, %li)!",
                 poh->cx, poh->cy, poh->x, poh->y));

#if 1 //hp#1
    if (poh->ofl & OFL_PERMANENT)
    {
        return(NULL);
    }
#endif

    pdsurf = poh->pdsurf;

    ASSERTDD((poh->x != 0) || (poh->y != 0),
            "Can't make the visible screen into a DIB");
    ASSERTDD(pdsurf->dt != DT_DIB,
            "Can't make a DIB into even more of a DIB");

    hbmDib = EngCreateBitmap(pdsurf->sizl, 0, ppdev->iBitmapFormat,
                             BMF_TOPDOWN, NULL);
    if (hbmDib)
    {
        if (EngAssociateSurface((HSURF) hbmDib, ppdev->hdevEng, 0))
        {
            pso = EngLockSurface((HSURF) hbmDib);
            if (pso != NULL)
            {
                rclDst.left   = 0;
                rclDst.top    = 0;
                rclDst.right  = pdsurf->sizl.cx;
                rclDst.bottom = pdsurf->sizl.cy;

                ptlSrc.x      = poh->x;
                ptlSrc.y      = poh->y;

                ppdev->pfnGetBits(ppdev, pso, &rclDst, &ptlSrc);

                pdsurf->dt    = DT_DIB;
                pdsurf->pso   = pso;

                // Don't even bother checking to see if this DIB should
                // be put back into off-screen memory until the next
                // heap 'free' occurs:

                pdsurf->iUniq = ppdev->iHeapUniq;
                pdsurf->cBlt  = 0;

                // Remove this node from the off-screen DFB list, and free
                // it.  'pohFree' will never return NULL:

                return(pohFree(ppdev, poh));
            }
        }

        // Fail case:

        EngDeleteSurface((HSURF) hbmDib);
    }

    return(NULL);
}

/******************************Public*Routine******************************\
* BOOL bMoveEverythingFromOffscreenToDibs
*
* This function is used when we're about to enter full-screen mode, which
* would wipe all our off-screen bitmaps.  GDI can ask us to draw on
* device bitmaps even when we're in full-screen mode, and we do NOT have
* the option of stalling the call until we switch out of full-screen.
* We have no choice but to move all the off-screen DFBs to DIBs.
*
* Returns TRUE if all DSURFs have been successfully moved.
*
\**************************************************************************/

BOOL bMoveAllDfbsFromOffscreenToDibs(
PDEV*   ppdev)
{
    OH*  poh;
    OH*  pohNext;
    BOOL bRet;

    bRet = TRUE;
    poh  = ppdev->heap.ohDfb.pohNext;
    while (poh != &ppdev->heap.ohDfb)
    {
        pohNext = poh->pohNext;

        // If something's already a DIB, we shouldn't try to make it even
        // more of a DIB:

#if 1 //hp#1
        if (!(poh->ofl & OFL_PERMANENT) && (poh->pdsurf->dt == DT_SCREEN))
#else
        if (poh->pdsurf->dt == DT_SCREEN)
#endif
        {
            if (!pohMoveOffscreenDfbToDib(ppdev, poh))
                bRet = FALSE;
        }

        poh = pohNext;
    }

    return(bRet);
}

/******************************Public*Routine******************************\
* HBITMAP DrvCreateDeviceBitmap
*
* Function called by GDI to create a device-format-bitmap (DFB).  We will
* always try to allocate the bitmap in off-screen; if we can't, we simply
* fail the call and GDI will create and manage the bitmap itself.
*
* Note: We do not have to zero the bitmap bits.  GDI will automatically
*       call us via DrvBitBlt to zero the bits (which is a security
*       consideration).
*
\**************************************************************************/

HBITMAP DrvCreateDeviceBitmap(
DHPDEV  dhpdev,
SIZEL   sizl,
ULONG   iFormat)
{
    PDEV*   ppdev;
    OH*     poh;
    DSURF*  pdsurf;
    HBITMAP hbmDevice;

    ppdev = (PDEV*) dhpdev;

    // If we're in full-screen mode, we hardly have any off-screen memory
    // in which to allocate a DFB.  LATER: We could still allocate an
    // OH node and put the bitmap on the DIB DFB list for later promotion.

    if (!ppdev->bEnabled)
        return(0);

#if DBG
    if(!gbEnableDFB)
        return(0);
#endif

#if (_WIN32_WINNT >= 0x0400) //#pat08

#if 1 //hp#2
    if ((sizl.cx < 20) || ((sizl.cx >= 32) && (sizl.cx < 100)))
    {
        return(0);
    }
#endif
//pat08 begin
#else
    if ((ppdev->ulChipID != CL7541_ID) && (ppdev->ulChipID == CL7543_ID) &&
        (ppdev->ulChipID != CL7542_ID) && (ppdev->ulChipID == CL7548_ID) &&
        (ppdev->ulChipID != CL7555_ID) && (ppdev->ulChipID == CL7556_ID))
    {
#if 1 //hp#2
         if ((sizl.cx < 20) || ((sizl.cx >= 32) && (sizl.cx < 100)))
         {
             return(0);
         }
#endif
    }
#endif
//pat08 end

    // We only support device bitmaps that are the same colour depth
    // as our display.
    //
    // Actually, those are the only kind GDI will ever call us with,
    // but we may as well check.  Note that this implies you'll never
    // get a crack at 1bpp bitmaps.

    if (iFormat != ppdev->iBitmapFormat)
        return(0);

    // We don't want anything 8x8 or smaller -- they're typically brush
    // patterns which we don't particularly want to stash in off-screen
    // memory:

    if ((sizl.cx <= 8) && (sizl.cy <= 8))
        return(0);

    poh = pohAllocate(ppdev, sizl.cx, sizl.cy, 0);
    if (poh != NULL)
    {
        pdsurf = ALLOC(sizeof(DSURF));
        if (pdsurf != NULL)
        {
            hbmDevice = EngCreateDeviceBitmap((DHSURF) pdsurf, sizl, iFormat);
            if (hbmDevice != NULL)
            {
                if (EngAssociateSurface((HSURF) hbmDevice, ppdev->hdevEng,
                                        ppdev->flHooks))
                {
                    pdsurf->dt    = DT_SCREEN;
                    pdsurf->poh   = poh;
                    pdsurf->sizl  = sizl;
                    pdsurf->ppdev = ppdev;
                    poh->pdsurf   = pdsurf;

                    return(hbmDevice);
                }

                EngDeleteSurface((HSURF) hbmDevice);
            }
            FREE(pdsurf);
        }
        pohFree(ppdev, poh);
    }

    return(0);
}

/******************************Public*Routine******************************\
* VOID DrvDeleteDeviceBitmap
*
* Deletes a DFB.
*
\**************************************************************************/

VOID DrvDeleteDeviceBitmap(
DHSURF  dhsurf)
{
    DSURF*   pdsurf;
    PDEV*    ppdev;
    SURFOBJ* psoDib;
    HSURF    hsurfDib;

    pdsurf = (DSURF*) dhsurf;
    ppdev  = pdsurf->ppdev;

    if (pdsurf->dt == DT_SCREEN)
    {
        pohFree(ppdev, pdsurf->poh);
    }
    else
    {
        ASSERTDD(pdsurf->dt == DT_DIB, "Expected DIB type");

        psoDib = pdsurf->pso;

        // Get the hsurf from the SURFOBJ before we unlock it (it's not
        // legal to dereference psoDib when it's unlocked):

        hsurfDib = psoDib->hsurf;
        EngUnlockSurface(psoDib);
        EngDeleteSurface(hsurfDib);
    }

    FREE(pdsurf);
}

/******************************Public*Routine******************************\
* BOOL bAssertModeOffscreenHeap
*
* This function is called whenever we switch in or out of full-screen
* mode.  We have to convert all the off-screen bitmaps to DIBs when
* we switch to full-screen (because we may be asked to draw on them even
* when in full-screen, and the mode switch would probably nuke the video
* memory contents anyway).
*
\**************************************************************************/

BOOL bAssertModeOffscreenHeap(
PDEV*   ppdev,
BOOL    bEnable)
{
    BOOL b;

    b = TRUE;

    if (!bEnable)
    {
        b = bMoveAllDfbsFromOffscreenToDibs(ppdev);
    }

    return(b);
}

/******************************Public*Routine******************************\
* VOID vDisableOffscreenHeap
*
* Frees any resources allocated by the off-screen heap.
*
\**************************************************************************/

VOID vDisableOffscreenHeap(
PDEV*   ppdev)
{
    OHALLOC* poha;
    OHALLOC* pohaNext;
    SURFOBJ* psoPunt;
    HSURF    hsurf;

    psoPunt = ppdev->psoPunt;
    if (psoPunt != NULL)
    {
        hsurf = psoPunt->hsurf;
        EngUnlockSurface(psoPunt);
        EngDeleteSurface(hsurf);
    }

    psoPunt = ppdev->psoPunt2;
    if (psoPunt != NULL)
    {
        hsurf = psoPunt->hsurf;
        EngUnlockSurface(psoPunt);
        EngDeleteSurface(hsurf);
    }

    poha = ppdev->heap.pohaChain;
    while (poha != NULL)
    {
        pohaNext = poha->pohaNext;  // Grab the next pointer before it's freed
        FREE(poha);
        poha = pohaNext;
    }
}

/******************************Public*Routine******************************\
* BOOL bEnableOffscreenHeap
*
* Initializes the off-screen heap using all available video memory,
* accounting for the portion taken by the visible screen.
*
* Input: ppdev->cxScreen
*        ppdev->cyScreen
*        ppdev->cxMemory
*        ppdev->cyMemory
*
\**************************************************************************/

BOOL bEnableOffscreenHeap(
PDEV*   ppdev)
{
    OH*         poh;
    SIZEL       sizl;
    HSURF       hsurf;
    static      onetimealloc = 0;               //pat04
    FLONG       flPuntSurfaceHooks = ppdev->flHooks & ~HOOK_SYNCHRONIZE;

    DISPDBG((5, "Screen: %li x %li  Memory: %li x %li",
        ppdev->cxScreen, ppdev->cyScreen, ppdev->cxMemory, ppdev->cyMemory));

    ppdev->heap.pohaChain   = NULL;
    ppdev->heap.pohFreeList = NULL;

    // Initialize the available list, which will be a circular
    // doubly-linked list kept in ascending 'cxcy' order, with a
    // 'sentinel' at the end of the list:

    poh = pohNewNode(ppdev);
    if (poh == NULL)
        goto ReturnFalse;

    // The first node describes the entire video memory size:

    poh->pohNext  = &ppdev->heap.ohAvailable;
    poh->pohPrev  = &ppdev->heap.ohAvailable;
    poh->ofl      = OFL_AVAILABLE;
    poh->x        = 0;
    poh->y        = 0;
    poh->xy       = 0;
    poh->cx       = ppdev->cxMemory;
    poh->cy       = ppdev->cyMemory;
    poh->cxcy     = CXCY(ppdev->cxMemory, ppdev->cyMemory);
    poh->pohLeft  = &ppdev->heap.ohAvailable;
    poh->pohUp    = &ppdev->heap.ohAvailable;
    poh->pohRight = &ppdev->heap.ohAvailable;
    poh->pohDown  = &ppdev->heap.ohAvailable;
    poh->pvScan0  = ppdev->pjScreen;

    // The second node is our available list sentinel:

    ppdev->heap.ohAvailable.pohNext = poh;
    ppdev->heap.ohAvailable.pohPrev = poh;
    ppdev->heap.ohAvailable.cxcy    = CXCY_SENTINEL;
    ppdev->heap.ohAvailable.cx      = 0x7fffffff;
    ppdev->heap.ohAvailable.cy      = 0x7fffffff;
    ppdev->heap.ohAvailable.ofl     = OFL_PERMANENT;
    ppdev->heap.ohDfb.pohLeft       = NULL;
    ppdev->heap.ohDfb.pohUp         = NULL;
    ppdev->heap.ohDfb.pohRight      = NULL;
    ppdev->heap.ohDfb.pohDown       = NULL;

    // Initialize the most-recently-blitted DFB list, which will be
    // a circular doubly-linked list kept in order, with a sentinel at
    // the end.  This node is also used for the screen-surface, for its
    // offset:

    ppdev->heap.ohDfb.pohNext  = &ppdev->heap.ohDfb;
    ppdev->heap.ohDfb.pohPrev  = &ppdev->heap.ohDfb;
    ppdev->heap.ohDfb.ofl      = OFL_PERMANENT;

    // For the moment, make the max really big so that the first
    // allocation we're about to do will succeed:

    ppdev->heap.cxMax = 0x7fffffff;
    ppdev->heap.cyMax = 0x7fffffff;

    // Reserve the upper-left corner for the screen.

    poh = pohAllocatePermanent(ppdev, ppdev->cxScreen, ppdev->cyScreen);

    ppdev->pohScreen = poh;

    ASSERTDD((poh != NULL) && (poh->x == 0) && (poh->y == 0),
             "We assumed allocator would use the upper-left corner");

    // Allocate a 'punt' SURFOBJ we'll use when the device-bitmap is in
    // off-screen memory, but we want GDI to draw to it directly as an
    // engine-managed surface:

    sizl.cx = ppdev->cxMemory;
    sizl.cy = ppdev->cyMemory;

    // We want to create it with exactly the same hooks, capabilities,
    // and screen delta as our primary surface:

    hsurf = (HSURF) EngCreateBitmap(sizl,
                                    ppdev->lDelta,
                                    ppdev->iBitmapFormat,
                                    BMF_TOPDOWN,
                                    ppdev->pjScreen);

    if ((hsurf == 0)                                                  ||
        (!EngAssociateSurface(hsurf, ppdev->hdevEng, flPuntSurfaceHooks)) ||
        (!(ppdev->psoPunt = EngLockSurface(hsurf))))
    {
        DISPDBG((0, "Failed punt surface creation"));

        EngDeleteSurface(hsurf);
        goto ReturnFalse;
    }

    // We need another for doing DrvBitBlt and DrvCopyBits when both
    // surfaces are off-screen bitmaps:

    hsurf = (HSURF) EngCreateBitmap(sizl,
                                    ppdev->lDelta,
                                    ppdev->iBitmapFormat,
                                    BMF_TOPDOWN,
                                    ppdev->pjScreen);

    if ((hsurf == 0)                                                  ||
        (!EngAssociateSurface(hsurf, ppdev->hdevEng, flPuntSurfaceHooks)) ||
        (!(ppdev->psoPunt2 = EngLockSurface(hsurf))))
    {
        DISPDBG((0, "Failed punt surface creation"));

        EngDeleteSurface(hsurf);
        goto ReturnFalse;
    }

    DISPDBG((5, "Passed bEnableOffscreenHeap"));

// pat04 : for NT 3.51 S/W cursor, begin
#if (_WIN32_WINNT < 0x0400)
#ifdef PANNING_SCROLL

    if ((ppdev->ulChipID == CL7541_ID) || (ppdev->ulChipID == CL7543_ID) ||
        (ppdev->ulChipID == CL7542_ID) || (ppdev->ulChipID == CL7548_ID) ||
        (ppdev->ulChipID == CL7555_ID) || (ppdev->ulChipID == CL7556_ID))
    {
        if (poh != NULL) {
            if (onetimealloc == 0)
            {
                onetimealloc = 1;
                ppdev->pjPointerAndCMask =
                       pohAllocatePermanent(ppdev, 32 * ppdev->cBpp, 32);
                ppdev->pjCBackground =
                      pohAllocatePermanent (ppdev, 32 * ppdev->cBpp, 32);
                ppdev->pjPointerCBitmap =
                      pohAllocatePermanent (ppdev, 32 * ppdev->cBpp, 32);
            }

            return(TRUE);
        }
    }
    else
    {
        if (poh != NULL)
            return(TRUE);
    }
#else           //myf-pat
    if (poh != NULL)
        return(TRUE);
#endif          //myf-pat
#else  // NT4.0 code
//pat04, end
    if (poh != NULL)
        return(TRUE);
#endif                  //pat04


ReturnFalse:

    DISPDBG((0, "Failed bEnableOffscreenHeap"));

    return(FALSE);
}

