/******************************Module*Header**********************************\
*
*                           *******************
*                           * GDI SAMPLE CODE *
*                           *******************
*
* Module Name: heap.c
*
* Content:
*
* 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) 1994-1999 3Dlabs Inc. Ltd. All rights reserved.
* Copyright (c) 1995-2001 Microsoft Corporation.  All rights reserved.
\*****************************************************************************/

#include "precomp.h"
#include "glint.h"

#if WNT_DDRAW
#include "linalloc.h"
#endif

#define OH_ALLOC_SIZE   4000        // Do all memory allocations in 4k chunks
#define OH_QUANTUM      4           // 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))

const ULONG HEAP_X_ALIGNMENT_P3[5] = {
    4,    // GLINTDEPTH8
    4,    // GLINTDEPTH16
    32,    // GLINTDEPTH32      = 32 pixels
    0,    // ---
    4,    // GLINTDEPTH24
};


/******************************Public*Routine******************************\
* void UploadDFBToDIB
\**************************************************************************/
void UploadDFBToDIB(PDEV *ppdev, SURFOBJ *pso, DSURF *pdsurf)
{
    OH       *poh = pdsurf->poh;
    RECTL    rclDst;
    POINTL   ptlSrc;
    LONG     xOff;
    LONG     pixOffset;
    LONG     pixDelta;
    ULONG    xyOffsetDst;
    BOOL     bOff;
    GLINT_DECL;

    rclDst.left   = 0;
    rclDst.top    = 0;
    rclDst.right  = pdsurf->sizl.cx;
    rclDst.bottom = pdsurf->sizl.cy;

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

    GET_PPDEV_DST_OFFSETS(ppdev, pixOffset, pixDelta, xyOffsetDst, xOff, bOff);
    SET_PPDEV_DST_OFFSETS(ppdev, poh->pixOffset, poh->lPixDelta, 
                          MAKEDWORD_XY(poh->x, poh->y), 
                          poh->bDXManaged ? 0 : poh->x, pdsurf->bOffScreen);
    VALIDATE_DD_CONTEXT;
    ppdev->pgfnUpload(ppdev, 1, &rclDst, pso, &ptlSrc, &rclDst);

    SET_PPDEV_DST_OFFSETS(ppdev, pixOffset, pixDelta, xyOffsetDst, xOff, bOff);
}

/******************************Public*Routine******************************\
* void DownloadDIBToDFB
\**************************************************************************/
void DownloadDIBToDFB(PDEV *ppdev, SURFOBJ *pso, DSURF *pdsurf)
{
    OH       *poh = pdsurf->poh;
    RECTL    rclDst;
    POINTL   ptlSrc;
    LONG     xOffset;
    LONG     pixOffset;
    LONG     pixDelta;
    ULONG    xyOffsetDst;
    BOOL     bOff;
    GLINT_DECL;

    // call low level download routine to download the DIB data to the
    // new off-screen DFB. Save and restore the ppdev offsets in case
    // we were called from the middle of some blt routine which has
    // already set them up.
    //
    rclDst.left   = 0;
    rclDst.top    = 0;
    rclDst.right  = pdsurf->sizl.cx;
    rclDst.bottom = pdsurf->sizl.cy;

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

    GET_PPDEV_DST_OFFSETS(ppdev, pixOffset, pixDelta, 
                          xyOffsetDst, xOffset, bOff);
    SET_PPDEV_DST_OFFSETS(ppdev, poh->pixOffset, poh->lPixDelta, 
                          MAKEDWORD_XY(poh->x, poh->y), 
                          poh->bDXManaged ? 0 : poh->x, pdsurf->bOffScreen);

    DISPDBG((DBGLVL,"Converting a DIB back to a DFB. calling image download"));
    VALIDATE_DD_CONTEXT;
    ppdev->pgfnXferImage(ppdev, &rclDst, 1, __GLINT_LOGICOP_COPY, 
                         __GLINT_LOGICOP_COPY, pso, &ptlSrc, &rclDst, NULL);

    SET_PPDEV_DST_OFFSETS(ppdev, pixOffset, pixDelta, 
                          xyOffsetDst, xOffset, bOff);
}

/******************************Private*Routine******************************\
* OH* GlintVidMemAlloc
*
* Use the DX heap manager to allocate linear memory from off-screen
*
\**************************************************************************/

OH *GlintVidMemAlloc(PDEV *ppdev, OH *pohThis, LONG cxThis, LONG cyThis)
{
    FLATPTR             fp = 0;

#if WNT_DDRAW

    P3_MEMREQUEST       mmrq;
    LinearAllocatorInfo *pvmHeap = NULL;
    LONG                iHeap;
    LONG                lDelta;
    ULONG               Mask32bit;
    GLINT_DECL;

    ASSERTDD((ppdev->flStatus & STAT_LINEAR_HEAP), 
              "GlintVidMemAlloc: ERROR - "
              "linear allocator called when linear heap not enabled!");

    DISPDBG((DBGLVL, "GlintVidMemAlloc: want cxy(%xh,%xh), cHeaps(%d)", 
                  cxThis, cyThis, ppdev->heap.cLinearHeaps));

    if(ppdev->heap.cLinearHeaps)
    {
        // align to dword boundaries
        Mask32bit = (1 << (2 - ppdev->cPelSize)) - 1;
        lDelta = cxThis + Mask32bit;
        lDelta &= ~Mask32bit;
        lDelta <<= ppdev->cPelSize;

        memset(&mmrq, 0, sizeof mmrq);
        mmrq.dwSize = sizeof mmrq;
        mmrq.dwBytes = lDelta * cyThis;
        mmrq.dwAlign = 16;    // 16 Byte alignment will work for everything
        mmrq.dwFlags = MEM3DL_FIRST_FIT | MEM3DL_FRONT;

retry:
        for (iHeap = 0, fp = 0; 
             iHeap < (LONG)ppdev->heap.cLinearHeaps && fp == 0; 
             ++iHeap)
        {
            pvmHeap = &ppdev->heap.pvmLinearHeap[iHeap];

            // we don't allocate from the AGP heap at present
            if(pvmHeap)
            {
                if(_DX_LIN_AllocateLinearMemory(pvmHeap, &mmrq) == GLDD_SUCCESS)
                {
                    fp = mmrq.pMem;
                }
                else
                {
                    DISPDBG((DBGLVL, "GlintVidMemAlloc: allocation failed"));
                }
            }
        }
        
        if(fp == 0)
        {
            OH *poh;
            LONG cxcyThis = cxThis * cyThis;
            LONG cxcy;

            do
            {
               poh = ppdev->heap.ohDiscardable.pohPrev;
                if (poh == &ppdev->heap.ohDiscardable)
                {
                    DISPDBG((DBGLVL, "GlintVidMemAlloc: FAILED :"
                                  "No discardable bitmaps remaining in "
                                  "offscreen and still not enough room"));
                    return(NULL);
                }
                
                ASSERTDD(poh != &ppdev->heap.ohDiscardable, 
                         "Ran out of discardable entries");
                ASSERTDD(poh->ohState == OH_DISCARDABLE, 
                         "Non-discardable node in discardable list");

                poh = pohMoveOffscreenDfbToDib(ppdev, poh);
                if (poh == NULL)
                {
                    DISPDBG((DBGLVL, "GlintVidMemAlloc: "
                                  "failed to kick DFB into system memory"));
                    return(NULL);
                }
                
                cxcy = poh->cx * poh->cy;
                cxcyThis -= cxcy;
            } 
            while (cxcyThis > 0);

            goto retry;
        }
    }

    if(fp)
    {
        ULONG pixOffset, x, y, xAligned;
                    
        DISPDBG((DBGLVL, "GlintVidMemAlloc: got some memory"
                      " - fp(%08xh) lDelta(%xh)", (ULONG)fp, lDelta));


        pixOffset = (DWORD)(fp >> ppdev->cPelSize);
        y = pixOffset / ppdev->cxMemory;
        x = pixOffset % ppdev->cxMemory;
        
        DISPDBG((DBGLVL, "GlintVidMemAlloc: rectangular values are: "
                      "pixOffset %08xh = xy(%xh,%xh)", pixOffset, x, y));

        xAligned = x & ~((1 << (2 - ppdev->cPelSize)) - 1);
        pixOffset = y * ppdev->cxMemory + xAligned;
        y = 0;
        x -= xAligned;

        pohThis->x = x;
        pohThis->y = y;
        pohThis->cx = cxThis;
        pohThis->cy = cyThis;
        pohThis->lPixDelta = lDelta >> ppdev->cPelSize;
        pohThis->pixOffset = pixOffset;
        pohThis->cxReserved = 0;
        pohThis->cyReserved = 0;
        pohThis->cxcy = CXCY(cxThis, cyThis);
        pohThis->pdsurf = NULL;
        pohThis->pvScan0 = ppdev->pjScreen + fp;
        pohThis->bDXManaged = TRUE;
        pohThis->pvmHeap = pvmHeap;
        pohThis->fpMem = fp;

        DISPDBG((DBGLVL, "GlintVidMemAlloc: linear values are: "
                      "pixOffset(%08xh), xy(%xh,%xh), cxy(%xh,%xh) Delta(%xh)",
                      pohThis->pixOffset = pixOffset, pohThis->x, pohThis->y, 
                      pohThis->cx, pohThis->cy, pohThis->lPixDelta));
    }
    else
    {
        // didn't get any memory - point at the free list 
        // sentinel to register our disappointment
        DISPDBG((DBGLVL, "GlintVidMemAlloc: "
                         "failed to get any offscreen memory"));
         
        for(pohThis = &ppdev->heap.ohFree; 
            pohThis->cxcy != CXCY_SENTINEL; 
            pohThis = pohThis->pohNext)
        {
            NULL;
        }
    }

#endif // WNT_DDRAW

    return(fp ? pohThis : NULL);
}

/******************************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)
    {
        DISPDBG((DBGLVL, "pohNewNode(): allocating new poha block"));

        // 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 = ENGALLOCMEM(FL_ZERO_MEMORY, OH_ALLOC_SIZE, ALLOC_TAG_GDI(D));
        if (poha == NULL)
        {
            DISPDBG((DBGLVL, "pohNewNode: failed to alloc node array, "
                             "returning 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;

    DISPDBG((DBGLVL, "pohNewNode(): returning poh %ph", poh));

    return(poh);
}

/******************************Private*Routine******************************\
* OH* GetFreeNode
*
* returns a node from the free list. If nothing free, returns the sentinel
*
\**************************************************************************/

OH *GetFreeNode(PDEV *ppdev, LONG cxThis, LONG cyThis)
{
    ULONG   cxcyThis = CXCY(cxThis, cyThis);
    OH      *pohThis;

    if((ppdev->flStatus & STAT_LINEAR_HEAP))
    {
        // We don't used the free list - DX does all the heap management
        // just create a node structure and try to alloc from the DX heap. 
        pohThis = pohNewNode(ppdev);
        
        if(pohThis)
        {
            // pohNewNode unlinks the node from the free list - link it 
            // back in as the caller to GetFreeNode will expect it there
            pohThis->pohNext = ppdev->heap.ohFree.pohNext;
            pohThis->pohPrev = ppdev->heap.ohFree.pohNext->pohPrev;
        
            pohThis->pohNext->pohPrev = pohThis;
            pohThis->pohPrev->pohNext = pohThis;

            pohThis = GlintVidMemAlloc(ppdev, pohThis, cxThis, cyThis);
        }
    }
    else
    {
        pohThis  = ppdev->heap.ohFree.pohNext;
    
        // The free list shows holds all the unused (rectangular) regions 
        // on the heap. These are ordered by size. Search through the 
        // list to find the best fit
        while (pohThis->cxcy < cxcyThis)
        {
            ASSERTDD(pohThis->ohState == OH_FREE, 
                     "Non-free node in free list(1)");

            pohThis = pohThis->pohNext;
        }

        while (pohThis->cy < cyThis)
        {
            ASSERTDD(pohThis->ohState == OH_FREE, 
                     "Non-free node in free list(2)");

            pohThis = pohThis->pohNext;
        }
    }
    return(pohThis);
}

/******************************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;
    }

    DISPDBG((DBGLVL, "vOhFreeNode(): freeing poh %ph", poh));

    poh->pohNext            = ppdev->heap.pohFreeList;
    ppdev->heap.pohFreeList = poh;
    poh->ohState            = OH_FREE; //azn was -1
}

/******************************Public*Routine******************************\
* VOID vCalculateMaximumNonPermanent
*
* Traverses the list of in-use and available rectangles to find the one
* with the maximal area.
*
\**************************************************************************/

VOID vCalculateMaximumNonPermanent(
PDEV*   ppdev)
{
    OH*     poh;
    OH*     pohSentinel;
    LONG    lArea;
    LONG    lMaxArea;
    LONG    cxMax;
    LONG    cyMax;
    LONG    cxBounds;
    LONG    cyBounds;
    LONG    i;

    lMaxArea = 0;
    cxMax    = 0;
    cyMax    = 0;
    cxBounds = 0;
    cyBounds = 0;

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

    pohSentinel = &ppdev->heap.ohFree;

    for (i = 2; i != 0; i--)
    {
        for (poh = pohSentinel->pohNext; poh != pohSentinel; poh = poh->pohNext)
        {
            ASSERTDD(poh->ohState != OH_PERMANENT,
                     "Permanent node in free or discardable list");

            if (poh->cx > cxBounds)
            {
                cxBounds = poh->cx;
            }
            
            if (poh->cy > cyBounds)
            {
                cyBounds = poh->cy;
            }

            // 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 discardable
        // rectangles:

        pohSentinel = &ppdev->heap.ohDiscardable;
    }

    // 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;
    ppdev->heap.cxBounds = cxBounds;
    ppdev->heap.cyBounds = cyBounds;
}

/******************************Public*Routine******************************\
* BOOL bDiscardEverythingInRectangle
*
* Throws out of the heap any discardable bitmaps that intersect with the
* specified rectangle.
*
\**************************************************************************/

BOOL bDiscardEverythingInRectangle(
PDEV*   ppdev,
LONG    x,
LONG    y,
LONG    cx,
LONG    cy)
{
    BOOL bRet;
    OH*  poh;
    OH*  pohNext;

    bRet = TRUE;        // Assume success

    poh = ppdev->heap.ohDiscardable.pohNext;
    while (poh != &ppdev->heap.ohDiscardable)
    {
        ASSERTDD(poh->ohState == OH_DISCARDABLE,
                 "Non-discardable node in discardable list");

        pohNext = poh->pohNext;

        if ((poh->x < x + cx) &&
            (poh->y < y + cy) &&
            (poh->x + poh->cx > x) &&
            (poh->y + poh->cy > y))
        {
            // The two rectangles intersect.  Give the boot to the
            // discardable bitmap:

            if (!pohMoveOffscreenDfbToDib(ppdev, poh))
            {
                bRet = FALSE;
            }
        }

        poh = pohNext;
    }

    return(bRet);
}

/******************************Public*Routine******************************\
* BOOL bFreeRightAndBottomSpace
*
* Given a free off-screen rectangle, allocates the upper-left part of
* the rectangle to hold the allocation request, and puts the two rectangles
* comprising the unused right and bottom portions on the free list.
*
\**************************************************************************/

BOOL bFreeRightAndBottomSpace(
PDEV*   ppdev,
OH*     pohThis,
LONG    cxThis,
LONG    cyThis,
BOOL    bQuantum)           // Set if inifitely small allocations should be
                            //   allowed
{
    ULONG cxcy;             // Temporary versions
    OH*   pohNext;
    OH*   pohPrev;
    LONG  cxRem;
    LONG  cyRem;
    OH*   pohBelow;
    LONG  cxBelow;
    LONG  cyBelow;
    OH*   pohBeside;
    LONG  cxBeside;
    LONG  cyBeside;
    LONG  cQuantum;
    GLINT_DECL;

    ASSERTDD(glintInfo != NULL, 
             "bFreeRightAndBottomSpace: ppdev->glintInfo is NULL");

    ASSERTDD(pohThis->bDXManaged == FALSE, 
             "bFreeRightAndBottomSpace: ERROR - called for linear DFB");

    // We're going to use the upper-left corner of our given rectangle,
    // and divide the unused remainder into two rectangles which will
    // go on the free 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
    // We may get better performance by keeping screen wide rectangles intact.
    if(cyRem < OH_QUANTUM || 
       cxRem < OH_QUANTUM || 
       pohThis->cx != ppdev->cxScreen)
    {
        if (cxRem <= cyRem)
        {
            cxBelow  = cxThis + cxRem;
            cyBeside = cyThis;
        }
        else
        {
            cxBelow  = cxThis;
            cyBeside = cyThis + cyRem;
        }
    }
    else
    {
        // we're allocating a block as wide as the screen: force a 
        // horizontal slice to be taken
        cxBelow  = cxThis + cxRem;
        cyBeside = cyThis;
    }
#else
    if (cxRem <= cyRem)
    {
        cxBelow  = cxThis + cxRem;
        cyBeside = cyThis;
    }
    else
    {
        cxBelow  = cxThis;
        cyBeside = cyThis + cyRem;
    }
#endif

    // If 'bQuantum' is set, 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):

    cQuantum = (bQuantum) ? 1 : OH_QUANTUM;

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

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

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

        cxcy    = CXCY(cxBelow, cyBelow);
        pohNext = ppdev->heap.ohFree.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->cxReserved = 0;
        pohBelow->cyReserved = 0;
        pohBelow->cxcy       = cxcy;
        pohBelow->ohState    = OH_FREE;
        pohBelow->x          = pohThis->x;
        pohBelow->y          = pohThis->y + cyThis;
        pohBelow->cx         = cxBelow;
        pohBelow->cy         = cyBelow;
        pohBelow->lPixDelta  = ppdev->cxMemory;
        POH_SET_RECTANGULAR_PIXEL_OFFSET(ppdev, pohBelow);

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

        pohThis->cy = cyThis;
    }

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

        cxcy    = CXCY(cxBeside, cyBeside);
        pohNext = ppdev->heap.ohFree.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->cxReserved = 0;
        pohBeside->cyReserved = 0;
        pohBeside->cxcy       = cxcy;
        pohBeside->ohState    = OH_FREE;
        pohBeside->x          = pohThis->x + cxThis;
        pohBeside->y          = pohThis->y;
        pohBeside->cx         = cxBeside;
        pohBeside->cy         = cyBeside;
        pohBeside->lPixDelta  = ppdev->cxMemory;
        POH_SET_RECTANGULAR_PIXEL_OFFSET(ppdev, pohBeside);

        // 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->cxcy = CXCY(pohThis->cx, pohThis->cy);

    return(TRUE);
}

/******************************Public*Routine******************************\
* OH* pohMakeRoomAtLocation
*
* Attempts to allocate a rectangle at a specific position.
*
\**************************************************************************/

OH* pohMakeRoomAtLocation(
PDEV*   ppdev,
POINTL* pptl,               // Requested position for the rectangle
LONG    cxThis,             // Width of rectangle to be allocated
LONG    cyThis,             // Height of rectangle to be allocated
FLONG   floh)               // Allocation flags
{
    OH*     poh;
    OH*     pohTop;
    OH*     pohLeft;
    LONG    cxLeft;
    LONG    cyTop;
    OH*     pohRight;


    ASSERTDD((ppdev->flStatus & STAT_LINEAR_HEAP) == FALSE, 
              "pohMakeRoomAtLocation: ERROR - called for linear DFB");

    if (!(floh & FLOH_ONLY_IF_ROOM))
    {
        // First off, discard any bitmaps that overlap the requested
        // rectangle, assuming we're allowed to:

        if (!bDiscardEverythingInRectangle(ppdev, 
                                           pptl->x, 
                                           pptl->y, 
                                           cxThis, 
                                           cyThis))
        {
            return(NULL);
        }
    }

    // Now see if there is a free rectangle that entirely contains the
    // requested rectangle.

    for (poh = ppdev->heap.ohFree.pohNext;
         poh != &ppdev->heap.ohFree;
         poh = poh->pohNext)
    {
        ASSERTDD(poh->ohState == OH_FREE, "Non-free node in free list(3)");

        // See if the current free rectangle completely contains the
        // requested rectangle:

        if ((poh->x <= pptl->x) &&
            (poh->y <= pptl->y) &&
            (poh->x + poh->cx >= pptl->x + cxThis) &&
            (poh->y + poh->cy >= pptl->y + cyThis))
        {
            // We can't reserve this rectangle, or make it permanent, if it's
            // already been reserved:

            if ((!poh->cxReserved) ||
                ((floh & (FLOH_RESERVE | FLOH_MAKE_PERMANENT)) == 0))
            {
                // The 'poh' rectangle entirely contains the requested
                // rectangle.  We may have a situation like this, where
                // the smaller rectangle is the requested rectangle, and
                // the larger rectangle is the available rectangle:
                //
                //     +-------------------+
                //     |                   |
                //     |    +---------+    |
                //     |    |Requested|    |
                //     |    |         |    |
                //     |    +---------+    |
                //     |                   |
                //     +-------------------+
                //
                // We want to make the space to the left and to the top of
                // the requested rectangle available to the heap.  Our
                // free-space routine only knows how to free space to the
                // right and bottom of an allocation, though.  So we will
                // temporarily allocate temporary rectangles to subdivide
                // our rectangle like the following:
                //
                //     +-------------------+
                //     |Top                |
                //     +----+--------------+
                //     |Left|Free          |
                //     |    |              |
                //     |    |              |
                //     |    |              |
                //     +----+--------------+
                //
                // Then, in the resulting 'Free' space, we will allocate the
                // upper-left corner for our requested rectangle, after which
                // we will go back and free the 'Top' and 'Left' temporary
                // rectangles.

                pohTop  = NULL;
                pohLeft = NULL;
                cxLeft  = pptl->x - poh->x;
                cyTop   = pptl->y - poh->y;

                if (cyTop > 0)
                {
                    if (!bFreeRightAndBottomSpace(ppdev, poh, poh->cx, cyTop,
                                                  TRUE))
                    {
                        return(NULL);
                    }

                    pohTop = poh;
                    poh    = pohTop->pohDown;
                }

                if (cxLeft > 0)
                {
                    if (!bFreeRightAndBottomSpace(ppdev, poh, cxLeft, poh->cy,
                                                  TRUE))
                    {
                        pohFree(ppdev, pohTop);
                        return(NULL);
                    }

                    pohLeft = poh;
                    poh     = pohLeft->pohRight;
                }

                ASSERTDD((poh->x == pptl->x) &&
                         (poh->y == pptl->y) &&
                         (poh->x + poh->cx >= poh->x + cxThis) &&
                         (poh->y + poh->cy >= poh->y + cyThis),
                        "poh must properly fit requested rectangle");

                // Finally, we can subdivide to get our requested rectangle:

                if (!bFreeRightAndBottomSpace(ppdev, poh, cxThis, cyThis, FALSE))
                {
                    poh = NULL;         // Fail this call
                }

                // Free our temporary rectangles, if there are any:

                pohFree(ppdev, pohTop);
                pohFree(ppdev, pohLeft);

                return(poh);
            }
        }
    }

    // There was no free rectangle that completely contains the requested
    // rectangle:

    return(NULL);
}

/******************************Public*Routine******************************\
* OH* pohMakeRoomAnywhere
*
* 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* pohMakeRoomAnywhere(
PDEV*   ppdev,
LONG    cxThis,             // Width of rectangle to be allocated
LONG    cyThis,             // Height of rectangle to be allocated
FLONG   floh)               // May have FLOH_ONLY_IF_ROOM set
{
    ULONG cxcyThis;         // Width and height search key
    OH*   pohThis;          // Points to found available rectangle we'll use
    GLINT_DECL;

    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_P3[ppdev->cPelSize] - 1)) & 
               ~(HEAP_X_ALIGNMENT_P3[ppdev->cPelSize] - 1);

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

    if ((cxThis > ppdev->heap.cxBounds) || (cyThis > ppdev->heap.cyBounds)) 
    {
        DISPDBG((WRNLVL, "Can't allocate (%d x %d) from (%d x %d)!",
                 cxThis, cyThis, ppdev->heap.cxBounds, ppdev->heap.cyBounds));
        return(NULL);
    }

    // Find the first available rectangle the same size 
    // or larger than the requested one:
    cxcyThis = CXCY(cxThis, cyThis);

    pohThis = GetFreeNode(ppdev, cxThis, cyThis);
    if(pohThis == NULL)
    {
        DISPDBG((WRNLVL, "pohMakeRoomAnywhere: "
                         "error, GetFreeNode() returned NULL"));
        return(NULL);
    }

    ASSERTDD(pohThis->ohState == OH_FREE, "Non-free node in free list(9)");

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

        if (floh & FLOH_ONLY_IF_ROOM)
        {
            return(NULL);
        }

        DISPDBG((DBGLVL, "> Making room for %li x %li allocation...", 
                         cxThis, cyThis));

        // 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, oldest allocations first:

        do {
            // (Least-recently created)
            pohThis = ppdev->heap.ohDiscardable.pohPrev;  
            if (pohThis == &ppdev->heap.ohDiscardable)
            {
                return(NULL);
            }

            ASSERTDD(pohThis != &ppdev->heap.ohDiscardable,
                     "Ran out of discardable entries -- Max not set correctly");
            ASSERTDD(pohThis->ohState == OH_DISCARDABLE,
                     "Non-discardable node in discardable list");

            // We can safely exit here if we have to:

            pohThis = pohMoveOffscreenDfbToDib(ppdev, pohThis);
            if (pohThis == NULL)
            {
                return(NULL);
            }

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

    if ((pohThis->cxReserved) && (floh & (FLOH_RESERVE | FLOH_MAKE_PERMANENT)))
    {
        // We can't reserve this rectangle, or make it permanent, if it's
        // already been reserved.  So throw absolutely everything out and
        // search the free list.
        //
        // NOTE: This is extremely painful!  A better approach would be to
        //       keep separate 'cxMax' and 'cyMax' variables kept for free
        //       rectangles that are not reserved (cxMax and cyMax
        //       currently include reserved free rectangles).

        if (!bDiscardEverythingInRectangle(ppdev, 0, 0,
                                           ppdev->cxMemory, ppdev->cyMemory))
        {
            return(NULL);
        }

        pohThis = &ppdev->heap.ohFree;
        do {
            pohThis = pohThis->pohNext;

            if (pohThis == &ppdev->heap.ohFree)
            {
                return(NULL);
            }

        } // the free list isn't ordered if the heap is DX managed
          while ((ppdev->flStatus & STAT_LINEAR_HEAP) == 0 && 
                 ((pohThis->cxReserved)  ||
                  (pohThis->cx < cxThis) ||
                  (pohThis->cy < cyThis)));
    }

    if((ppdev->flStatus & STAT_LINEAR_HEAP) == 0)
    {
        if (!bFreeRightAndBottomSpace(ppdev, pohThis, cxThis, cyThis, FALSE))
        {
            return(NULL);
        }
    }

    return(pohThis);
}

/******************************Public*Routine******************************\
* BOOL bOhCommit
*
* If 'bCommit' is TRUE, converts a 'reserved' allocation to 'permanent,'
* moving from off-screen memory any discardable allocations that may have
* been using the space.
*
* If 'bCommit' is FALSE, converts a 'permanent' allocation to 'reserved,'
* allowing the space to be used by discardable allocations.
*
\**************************************************************************/

BOOL bOhCommit(
PDEV*   ppdev,
OH*     poh,
BOOL    bCommit)
{
    BOOL    bRet;
    LONG    cx;
    LONG    cy;
    ULONG   cxcy;
    OH*     pohRoot;
    OH*     pohNext;
    OH*     pohPrev;

    bRet = FALSE;       // Assume failure

    if (poh == NULL)
    {
        return(bRet);
    }

    if ((bCommit) && (poh->cxReserved))
    {
        if (bDiscardEverythingInRectangle(ppdev, poh->x, poh->y,
                                          poh->cxReserved, poh->cyReserved))
        {
            DISPDBG((DBGLVL, "Commited %li x %li at (%li, %li)",
                        poh->cx, poh->cy, poh->x, poh->y));

            poh->ohState = OH_PERMANENT;

            // Remove this node from the free list:

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

            // Now insert the node at the head of the permanent list:

            pohRoot = &ppdev->heap.ohPermanent;

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

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

            bRet = TRUE;
        }
    }
    else if ((!bCommit) && (poh->ohState == OH_PERMANENT))
    {
        DISPDBG((DBGLVL, "Decommited %li x %li at (%li, %li)",
                    poh->cx, poh->cy, poh->x, poh->y));

        poh->ohState    = OH_FREE;
        poh->cxReserved = poh->cx;
        poh->cyReserved = poh->cy;

        // Remove this node from the permanent list:

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

        // Now insert the node, in order, into the free list:

        cxcy = poh->cxcy;

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

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

        bRet = TRUE;
    }

    // Recalculate the biggest rectangle available for allocation:
    vCalculateMaximumNonPermanent(ppdev);

    return(bRet);
}

/******************************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;
    GLINT_DECL;

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

    pdsurf = poh->pdsurf;

    ASSERTDD((poh->x != 0) || (poh->y != 0 || poh->bDXManaged),
            "Can't make the visible screen into a DIB");
    ASSERTDD((pdsurf->dt & DT_DIB) == 0,
            "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)
            {
                UploadDFBToDIB(ppdev, pso, pdsurf);

                // delete the screen DIB. Recreate it when 
                // we change the DIB back to a DFB
                vDeleteScreenDIBFromOH(poh);

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

                // 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)
{
    // Throw out any discardable bitmaps over the entire surface:

    return(bDiscardEverythingInRectangle(ppdev, 0, 0,
                                         ppdev->cxMemory, ppdev->cyMemory));
}


/******************************Public*Routine******************************\
* OH* pohAllocate
*
* Allocates a rectangle in off-screen memory.
*
* Types:
*
*   FLOH_RESERVE
*
*     Reserves an off-screen rectangle.  The space may still be used by
*     discardable bitmaps until the rectangle is committed via 'bOhCommit'.
*
*   FLOH_MAKE_PERMANENT
*
*     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.
*
*   Default
*
*     Allocates a 'discardable' off-screen rectangle for a DFB that may
*     be  kicked out of off-screen if the space is needed.
*
* Options:
*
*   FLOH_ONLY_IF_ROOM
*
*     Allocates an off-screen rectangle only if there is free space
*     available -- i.e., no discardable rectangles will be moved out of
*     off-screen to make room.
*
*   Default
*
*     May move discardable rectangles out of off-screen to make room.
*
* Arguments:
*
*   pptl
*
*     If NULL, the rectangle will be allocated anywhere in un-used offscreen
*     memory.
*
*     If non-NULL, is a requested position for the rectangle.
*
*     NOTE: The heap will quickly fragment if arbitrary positions are
*           requested.  This position option works best if there is only
*           one specific rectangle ever requested, or if the allocations
*           are always wider than they are high.
*
\**************************************************************************/

OH* pohAllocate(
PDEV*   ppdev,
POINTL* pptl,           // Optional requested position of rectangle
LONG    cxThis,         // Width of rectangle to be allocated
LONG    cyThis,         // Height of rectangle to be allocated
FLOH    floh)           // Allocation flags
{
    OH*     pohThis;    // Points to found available rectangle we'll use
    OH*     pohRoot;    // Point to root of list where we'll insert node
    ULONG   cxcy;
    OH*     pohNext;
    OH*     pohPrev;

    ASSERTDD((floh & (FLOH_RESERVE | FLOH_MAKE_PERMANENT))
                  != (FLOH_RESERVE | FLOH_MAKE_PERMANENT),
             "Illegal flags -- can't set both "
             "FLOH_RESERVE and FLOH_MAKE_PERMANENT");

    DISPDBG((DBGLVL, "pohAllocate: size  %d %d", cxThis, cyThis));

    if (pptl == NULL)
    {
        pohThis = pohMakeRoomAnywhere(ppdev, cxThis, cyThis, floh);
        if (pohThis == NULL)
        {
            DISPDBG((DBGLVL, "Can't allocate %li x %li with flags %li",
                             cxThis, cyThis, floh));
        }
    }
    else
    {
        pohThis = pohMakeRoomAtLocation(ppdev, pptl, cxThis, cyThis, floh);
        if (pohThis == NULL)
        {
            DISPDBG((DBGLVL, "Can't allocate %li x %li at %li, "
                             "%li with flags %li",
                             cxThis, cyThis, pptl->x, pptl->y, floh));
        }
    }

    if (pohThis == NULL)
    {
        return(NULL);
    }

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

    if(pohThis->bDXManaged)
    {
        // in the linear coordinate system: 
        // pixOffset == offset of DFB from the start of the FB
        // y == scanline offset from pixOffset (always 0)
        // x == pixel offset from pixOffset+y*lDelta 
        // (always the pixel offset from the nearest dword aligned pixel)
        pohThis->pvScan0 = ppdev->pjScreen + 
                           ( ( pohThis->pixOffset              + 
                               pohThis->y * pohThis->lPixDelta + 
                               pohThis->x) 
                             << ppdev->cPelSize );
    }
    else
    {
        // in the rectangular coordinate system, for non-PX/RX chips: 
        // pixOffset == value of y expressed in pixels from the start of FB
        // y == pixOffset / lDelta (lDelta is always cxMemory)
        // x == pixel offset to DFB from the beginning of its scanline.
        // For PX/RX chips pixOffset is always 0, y is the number of scanlines 
        // to the DFB from the start of the FB
        pohThis->pvScan0 = ppdev->pjScreen + 
                           ( ( pohThis->y * pohThis->lPixDelta + 
                               pohThis->x) 
                             << ppdev->cPelSize );
    }

    // The caller is responsible for setting this field:

    pohThis->pdsurf = NULL;

    // Our 'reserve' logic expects the node to have 'free' status:

    ASSERTDD(pohThis->ohState == OH_FREE, "Node not free after making room");
    ASSERTDD(((floh & (FLOH_RESERVE | FLOH_MAKE_PERMANENT)) == 0) ||
             (pohThis->cxReserved == 0),
             "Can't reserve a rectangle that's already reserved");

    if (floh & FLOH_RESERVE)
    {

        ASSERTDD((ppdev->flStatus & STAT_LINEAR_HEAP) == FALSE, 
                  "pohAllocate() - can't reserve when the heap is DX managed");

        // A non-zero value for 'cxReserved' means it's reserved:

        pohThis->cxReserved = pohThis->cx;
        pohThis->cyReserved = pohThis->cy;

        // Remove this node from its place in the free list:

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

        // Now insert the node, in order, back into the free list:

        cxcy = pohThis->cxcy;

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

        pohPrev->pohNext = pohThis;
        pohNext->pohPrev = pohThis;
        pohThis->pohPrev = pohPrev;
        pohThis->pohNext = pohNext;
    }
    else
    {
        // Remove this node from the free list:

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

        if (floh & FLOH_MAKE_PERMANENT)
        {
            // Change status of node and insert into permanent list:

            pohThis->ohState = OH_PERMANENT;
            pohRoot = &ppdev->heap.ohPermanent;

            // Calculate the new maximum size rectangle available
            // for allocation:

            vCalculateMaximumNonPermanent(ppdev);
        }
        else
        {
            // Change status of node and insert into discardable list:

            pohThis->ohState = OH_DISCARDABLE;
            pohRoot = &ppdev->heap.ohDiscardable;
        }

        // Now insert the node at the head of the appropriate list:

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

        pohRoot->pohNext->pohPrev = pohThis;
        pohRoot->pohNext          = pohThis;
    }

    DISPDBG((DBGLVL, "   Allocated (%li x %li) at (%li, %li) with flags %li",
                cxThis, cyThis, pohThis->x, pohThis->y, floh));

    return(pohThis);
}

/******************************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;
    OHSTATE oldState;

    if (poh == NULL)
    {
        DISPDBG((WRNLVL, "pohFree: passed in NULL poh"));
        return(NULL);
    }

    DISPDBG((DBGLVL, "Freeing poh %p -- %li x %li at (%li, %li)", 
                     poh, poh->cx, poh->cy, poh->x, poh->y));

    oldState = poh->ohState;
    if (oldState != OH_DISCARDABLE)
    {
        // We can remove the 'reserved' status unless we are merely
        // deleting a discardable rectangle that was temporarily
        // placed in a reserve rectangle:

        poh->cxReserved = 0;
        poh->cyReserved = 0;
    }

    // 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++;

    if(poh->bDXManaged)
    {
#if WNT_DDRAW
        
        DISPDBG((DBGLVL, "pohFree: calling DX free for item %p on heap %p", 
                (VOID *)poh->fpMem, poh->pvmHeap));
                
        if(poh->pvmHeap == NULL)
        {
            DISPDBG((ERRLVL,"pohFree: poh %p -- linear DFB is invalid!", poh));
        }
        else
        {
            _DX_LIN_FreeLinearMemory(poh->pvmHeap, (ULONG)(poh->fpMem) );
            poh->pvmHeap = NULL;
            poh->fpMem = 0;
            
#if 1 //azntst for MP leak      
            // Remove this node from whatever list it's in:
            poh->pohNext->pohPrev = poh->pohPrev;
            poh->pohPrev->pohNext = poh->pohNext;

            // Add the node the the list of free nodes
            vOhFreeNode(ppdev, poh);      

            // Set the current state as FREE
            poh->ohState     = OH_FREE;
            
            return(poh);             // with DX managing it, we can return now.
#endif               
        }
#endif // WNT_DDRAW

     
        goto MergeComplete; //azntst Now a NOP
    }


MergeLoop:

    // Try merging with the right sibling:

    pohBeside = poh->pohRight;
    if ((poh->cxReserved    != poh->cx)         &&
        (pohBeside->ohState == OH_FREE)         &&
        (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 free 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 ((poh->cyReserved     != poh->cy)        &&
        (pohBeside->ohState  == OH_FREE)        &&
        (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;
    }

    // Don't do any more merge this rectangle into anything to the
    // top or to the left if it's reserved:

    if (!poh->cxReserved)
    {
        // Try merging with the left sibling:

        pohBeside = poh->pohLeft;
        if ((pohBeside->cxReserved != pohBeside->cx) &&
            (pohBeside->ohState    == OH_FREE)       &&
            (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 whatever list it was in (if we were
            // asked to free a 'permanent' node, it will have been in
            // the permanent 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->cyReserved != pohBeside->cy) &&
            (pohBeside->ohState    == OH_FREE)       &&
            (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;
        }
    }

MergeComplete:

    // Remove this node from whatever list it's in:

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

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

    // Insert the node, in order, into the free list:
    // NB. DX managed DFBs don't need to go in any order - 
    // they are organised by DirectX instead

    pohNext = ppdev->heap.ohFree.pohNext;
    if(!poh->bDXManaged)
    {
        while (pohNext->cxcy < cxcy)
        {
            pohNext = pohNext->pohNext;
        }
    }
    pohPrev = pohNext->pohPrev;

    pohPrev->pohNext = poh;
    pohNext->pohPrev = poh;
    poh->pohPrev     = pohPrev;
    poh->pohNext     = pohNext;
    poh->cxcy        = cxcy;
    poh->ohState     = OH_FREE;

    if (oldState == OH_PERMANENT)
    {
        // Removing the permanent entry means that we may be able to
        // enlarge the maximum possible rectangle we can allow:

        vCalculateMaximumNonPermanent(ppdev);
    }

    // Return the node pointer for the new and improved available rectangle:

    return(poh);
}

/******************************Public*Routine******************************\
* BOOL bCreateScreenDIBForOH
*
* Given an OH create a surface for the bitmap which is accessible by GDI.
* So if we can't handle any drawing using GLINT we can get GDI to draw
* driectly to the screen. This is possible because we map the screen in
* fully and linearly. We can use this for the screen and off-screen bitmaps.
*
* Returns: FALSE if we didn't create the surface, TRUE if we did.
*
\**************************************************************************/

BOOL
bCreateScreenDIBForOH(PPDEV ppdev, OH *poh, ULONG hooks)
{
    DSURF   *pdsurf = poh->pdsurf;
    UCHAR   *pvBits = poh->pvScan0;
    LONG    lDelta = poh->lPixDelta << ppdev->cPelSize;
    HBITMAP hbmDib;
    SURFOBJ *pso;    

    DISPDBG((DBGLVL, "bCreateScreenDIBForOH: poh at 0x%x, pdsurf at 0x%x, "
                     "pvBits 0x%x", poh, pdsurf, pvBits));

    hbmDib = EngCreateBitmap(pdsurf->sizl,
                            (ULONG)lDelta,
                            (ULONG)(ppdev->iBitmapFormat),
                            (FLONG)(((lDelta > 0) ? BMF_TOPDOWN : 0)),
                            (PVOID)pvBits);
    if (hbmDib) 
    {
        
        // set HOOK_SYNCHRONIZE so that GDI will call DrvSynchronize before
        // drawing on this surface. This means we can call Eng anytime safe
        // in the knowledge that DrvSynchronize will sync for us.
        //
        if (EngAssociateSurface((HSURF)hbmDib, ppdev->hdevEng, hooks))
        {            
            // NB: use the temporary pso so we don't overwrite pdsurf->pso 
            // if we fail
            if (pso = EngLockSurface((HSURF)hbmDib)) 
            {
                pdsurf->pso = pso;
                DISPDBG((DBGLVL, "created surface 0x%x", pso));
                return(TRUE);
            }
        }

        EngDeleteSurface((HSURF)hbmDib);
    }

    DISPDBG((DBGLVL, "bCreateScreenDIBForOH failed"));
    return(FALSE);
}

/******************************Public*Routine******************************\
* BOOL vDeleteScreenDIBFromOH
*
* Given an OH delete any screen DIB surface associated with it. We choose to
* do a lazy creation of GDI accessible bitmaps for DFBs. So there may not be
* any surface to delete. 
*
* Returns:
*
\**************************************************************************/

VOID
vDeleteScreenDIBFromOH(OH *poh)
{
    DSURF   *pdsurf = poh->pdsurf;
    SURFOBJ *pso;
    HSURF   hsurf;

    DISPDBG((DBGLVL, "vDeleteScreenDIBFromOH called"));
    if (!(pso = pdsurf->pso)) 
    {
        DISPDBG((DBGLVL, "no surface to delete"));       
        return;
    }

    hsurf = pso->hsurf;         // can't dereference pso when unlocked
    EngUnlockSurface(pso);
    EngDeleteSurface(hsurf);    // pdsurf->pso can now be reassigned 
                                // to a memory DIB
    DISPDBG((DBGLVL, "surface 0x%x deleted", pso));
}


/******************************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;
    HSURF       hsurf;
    LONG        cy;

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

    // If we're in full-screen mode, we can't move anything to off-screen
    // memory:
    if (!ppdev->bEnabled || !(ppdev->flStatus & STAT_DEV_BITMAPS))
    {
        return(FALSE);
    }

    // XXX
    // 
    // for the GeoTwin all off-screen bitmaps must start on an even scanline.
    // This is so that even coordinates always map to the same chip.
    cy = pdsurf->sizl.cy;
    if (ppdev->flCaps & CAPS_SPLIT_FRAMEBUFFER)
    {
        cy = (cy + 1) & ~1;
        DISPDBG((DBGLVL, "move: sizl.cy evened up to %d for GeoTwin", cy));
    }

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

        return(FALSE);
    }

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

    pso           = pdsurf->pso;
    poh->pdsurf   = pdsurf;
    pdsurf->poh   = poh;

    // recreate the screen DIB. Do it here so that if we fail we can zap poh and
    // leave the bitmap as a memory DIB.

    if (!bCreateScreenDIBForOH(ppdev, poh, HOOK_SYNCHRONIZE)) 
    {
        DISPDBG((DBGLVL, "bCreateScreenDIBForOH failed"));
        goto ReturnFail;
    }
    
    pdsurf->dt         = DT_SCREEN;
    pdsurf->bOffScreen = TRUE;
    DownloadDIBToDFB(ppdev, pso, 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);

ReturnFail:
    pohFree(ppdev, poh);
    DISPDBG((DBGLVL, "bMoveDibToOffscreenDfbIfRoom failed"));
    return(FALSE);
}

/******************************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 = (PDEV*) dhpdev;
    OH*     poh;
    DSURF*  pdsurf;
    HBITMAP hbmDevice;
    FLONG   flHooks;
    LONG    cy = sizl.cy;
    LONG    cx = sizl.cx;
    GLINT_DECL;

    // 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.
    // Also check that off-screen DFBs are configured.

    if (!ppdev->bEnabled || !(ppdev->flStatus & STAT_DEV_BITMAPS))
    {
        return(0);
    }

    // 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)
    {
        DISPDBG((DBGLVL, "DrvCreateDeviceBitmap(): can't create bitmap of "
                         "format %d size(%d,%d), only bitmaps of format %d "
                         "supported!", iFormat, cx, cy, 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 ((cx <= 8) && (cy <= 8))
    {
        return(0);
    }

    // XXX
    // 
    // for the GeoTwin all off-screen bitmaps must start on an even scanline.
    // This is so that even coordinates always map to the same chip.
    if (ppdev->flCaps & CAPS_SPLIT_FRAMEBUFFER)
    {
        cy = (cy + 1) & ~1;
        DISPDBG((DBGLVL, "create: sizl.cy evened up to %d for GeoTwin", cy));
    }

    if(ppdev->pohImageDownloadArea)
    {
        DISPDBG((DBGLVL, "DrvCreateDeviceBitmap: discarding image download "
                         "scratch area"));
        pohFree(ppdev, ppdev->pohImageDownloadArea);
        ppdev->pohImageDownloadArea = NULL;
        ppdev->cbImageDownloadArea = 0;
    }

    poh = pohAllocate(ppdev, NULL, cx, cy, 0);
    if (poh != NULL)
    {
        pdsurf = ENGALLOCMEM(FL_ZERO_MEMORY, sizeof(DSURF), ALLOC_TAG_GDI(E));
        if (pdsurf != NULL)
        {
            hbmDevice = EngCreateDeviceBitmap((DHSURF) pdsurf, sizl, iFormat);
            if (hbmDevice != NULL)
            {
                flHooks = ppdev->flHooks;

                #if SYNCHRONIZEACCESS_WORKS && (_WIN32_WINNT < 0x500)
                {
                // Setting the SYNCHRONIZEACCESS flag tells GDI that we
                // want all drawing to the bitmaps to be synchronized (GDI
                // is multi-threaded and by default does not synchronize
                // device bitmap drawing -- it would be a Bad Thing for us
                // to have multiple threads using the accelerator at the
                // same time):

                flHooks |= HOOK_SYNCHRONIZEACCESS;
                }
                #endif // SYNCHRONIZEACCESS_WORKS && (_WIN32_WINNT < 0x500)

                // It's a device-managed surface; make sure we don't set
                // HOOK_SYNCHRONIZE, otherwise we may confuse GDI:

                flHooks &= ~HOOK_SYNCHRONIZE;

                if (EngAssociateSurface((HSURF) hbmDevice, ppdev->hdevEng,
                                        flHooks))
                {
                    pdsurf->dt    = DT_SCREEN;
                    pdsurf->bOffScreen = TRUE;
                    pdsurf->poh   = poh;
                    pdsurf->sizl  = sizl;
                    pdsurf->ppdev = ppdev;
                    poh->pdsurf   = pdsurf;

                    // create the GDI accessible screen bitmap
                    if (bCreateScreenDIBForOH(ppdev, poh, HOOK_SYNCHRONIZE)) 
                    {
                        DISPDBG((DBGLVL, "DFB created at (%d,%d), w %d, h %d",
                            poh->x, poh->y, poh->cx, poh->cy));

                        return(hbmDevice);
                    }

                    EngDeleteSurface((HSURF) hbmDevice);

                    // Once association is done, EngDeleteSurface
                    // callback driver's DrvDeleteDeviceBitmap,
                    // then pdsurf and poh are freed there, so that
                    // we don't need to free it here.
                }
                else
                {
                    EngDeleteSurface((HSURF) hbmDevice);
                    ENGFREEMEM(pdsurf);
                    pohFree(ppdev, poh);
                }
            }
            else
            {
                ENGFREEMEM(pdsurf);
                pohFree(ppdev, poh);
            }
        }
        else
        {
            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_DIB) ||
        (pdsurf->dt & DT_DIRECTDRAW))
    {
        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);
    }
    else if (pdsurf->dt & DT_SCREEN)
    {
        vDeleteScreenDIBFromOH(pdsurf->poh);
        pohFree(ppdev, pdsurf->poh);
    }

    ENGFREEMEM(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
        ENGFREEMEM(poha);
        poha = pohaNext;
    }

    // the linear heap, if enabled, must be disabled now
    ppdev->flStatus &= ~STAT_LINEAR_HEAP;

}

/******************************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;
    POINTL      ptlScreen;
    LONG        virtualcxMemory;
    GLINT_DECL;

    virtualcxMemory = ppdev->cxMemory;

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

    ASSERTDD((ppdev->cxScreen <= virtualcxMemory) &&
             (ppdev->cyScreen <= ppdev->cyMemory),
             "Memory must not have smaller dimensions than visible screen!");

    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.ohFree;
    poh->pohPrev      = &ppdev->heap.ohFree;
    poh->ohState      = OH_FREE;
    poh->x            = 0;
    poh->y            = 0;
    poh->lPixDelta    = ppdev->cxMemory;
    poh->cx           = virtualcxMemory;
    poh->cy           = ppdev->cyMemory;
    poh->cxcy         = CXCY(virtualcxMemory, ppdev->cyMemory);
    poh->pohLeft      = &ppdev->heap.ohFree;
    poh->pohUp        = &ppdev->heap.ohFree;
    poh->pohRight     = &ppdev->heap.ohFree;
    poh->pohDown      = &ppdev->heap.ohFree;
    poh->pvScan0      = ppdev->pjScreen;
    poh->pixOffset    = 0;

    // The second node is our free list sentinel:

    ppdev->heap.ohFree.pohNext         = poh;
    ppdev->heap.ohFree.pohPrev         = poh;
    ppdev->heap.ohFree.cxcy            = CXCY_SENTINEL;
    ppdev->heap.ohFree.cx              = 0x7fffffff;
    ppdev->heap.ohFree.cy              = 0x7fffffff;
    ppdev->heap.ohFree.ohState         = OH_FREE;

    // Initialize the discardable 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.ohDiscardable.pohNext = &ppdev->heap.ohDiscardable;
    ppdev->heap.ohDiscardable.pohPrev = &ppdev->heap.ohDiscardable;
    ppdev->heap.ohDiscardable.ohState = OH_DISCARDABLE;

    // Initialize the permanent list, which will be a circular
    // doubly-linked list kept in order, with a sentinel at the end.

    ppdev->heap.ohPermanent.pohNext = &ppdev->heap.ohPermanent;
    ppdev->heap.ohPermanent.pohPrev = &ppdev->heap.ohPermanent;
    ppdev->heap.ohPermanent.ohState = OH_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;

#if (_WIN32_WINNT >= 0x500)

    if(ppdev->flStatus & ENABLE_LINEAR_HEAP)
    {
        // in Windows 2000 we use the DX linear heap for DFBs. 
        // NB. the DX heaps aren't initialized until after the display 
        //     driver has initialized, therefore
        //     we use the old rectangular heap for 2D cache allocation.
        ppdev->heap.pvmLinearHeap = NULL;
        ppdev->heap.cLinearHeaps = 0;
    }

#endif //(_WIN32_WINNT >= 0x500)

    ptlScreen.x = 0;
    ptlScreen.y = 0;

    // Finally, reserve the upper-left corner for the screen.  We can
    // actually throw away 'poh' because we'll never need it again
    // (not even for disabling the off-screen heap since everything is
    // freed using OHALLOCs):

    poh = pohAllocate(ppdev, &ptlScreen, ppdev->cxScreen, ppdev->cyScreen,
                      FLOH_MAKE_PERMANENT);

    ASSERTDD((poh != NULL) && (poh->x == 0) && (poh->y == 0) &&
             (poh->cx >= ppdev->cxScreen) && (poh->cy >= ppdev->cyScreen),
             "Screen allocation messed up");

    // Remember it so that we can associate the screen SURFOBJ with this
    // poh:

    ppdev->pohScreen = poh;

    // 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 = virtualcxMemory;
    sizl.cy = ppdev->cyMemory;

    // We want to create it with exactly the same capabilities
    // as our primary surface.  We will override the 'lDelta' and 'pvScan0'
    // fields later:

    // We do NOT want to hook any of the drawing functions.  Once we
    // send this surface into the engine, we don't want the driver to
    // get called with it again.  Otherwise we could get into a situation
    // where both the source and dest SURFOBJs for a blt were marked as DIBs.

    hsurf = (HSURF) EngCreateBitmap(sizl,
                                    0xbadf00d,
                                    ppdev->iBitmapFormat,
                                    BMF_TOPDOWN,
                                    (VOID*) 0xbadf00d);

    if ((hsurf == 0)                                                  ||
        (!EngAssociateSurface(hsurf, ppdev->hdevEng, 0)) ||
        (!(ppdev->psoPunt = EngLockSurface(hsurf))))
    {
        DISPDBG((DBGLVL, "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,
                                    0xbadf00d,
                                    ppdev->iBitmapFormat,
                                    BMF_TOPDOWN,
                                    (VOID*) 0xbadf00d);

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

        EngDeleteSurface(hsurf);
        goto ReturnFalse;
    }

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

    // enable off-screen bitmaps by if configured
    if (ppdev->flStatus & ENABLE_DEV_BITMAPS)
    {
        ppdev->flStatus |= STAT_DEV_BITMAPS;
    }

    if (poh != NULL)
    {
        return(TRUE);
    }

ReturnFalse:

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

    return(FALSE);
}

/******************************Public*Routine******************************\
* BOOL vDisable2DOffscreenMemory
*
* 3D apps want to use the offscreen memory. Prevent 2D from using it.
\**************************************************************************/

BOOL bDisable2DOffscreenMemory(PDEV* ppdev)
{
    GLINT_DECL;
    
    if (ppdev->Disable2DCount++ > 0)
    {
        return(TRUE);
    }

    if (ppdev->flStatus & STAT_DEV_BITMAPS)
    {
        if (!bMoveAllDfbsFromOffscreenToDibs(ppdev))
        {
            DISPDBG((DBGLVL, "bDisable2DOffscreenMemory failed"));
            return FALSE;
        }
        ppdev->flStatus &= ~STAT_DEV_BITMAPS;
    }
    
    return TRUE;
}

/******************************Public*Routine******************************\
* VOID vEnable2DOffscreenMemory
*
* 3D apps no longer need offscreen memory. Use it for 2D instead.
\**************************************************************************/

VOID vEnable2DOffscreenMemory(PDEV *ppdev)
{
    GLINT_DECL;

    if (--ppdev->Disable2DCount > 0)
    {
        return;
    }

    if (ppdev->flStatus & ENABLE_DEV_BITMAPS)
    {
        ppdev->flStatus |= STAT_DEV_BITMAPS;
    }
}

#if !defined(_WIN64) && WNT_DDRAW

/******************************Public*Routine******************************\
* VOID vSurfUsed
*
* Notify the heap manager that this surface is touched and it should be
* moved to the end of DdFreeDriverMemory's priority queue 
\**************************************************************************/
VOID vSurfUsed(SURFOBJ *psoSurf)
{
    DSURF* pSurf;
    OH* pohSurf;
    OH* pohHead;
 
    // When psoSurf is the original source surface, it can be NULL
    if (! psoSurf) 
    {
        return;
    }

    // Cast the dhsurf back to the Perm3 GDI surface pointer
    pSurf = (DSURF *)psoSurf->dhsurf;

    // If the surface is a DIB managed by the driver, it shoulf be ignored
    if ((! pSurf) || (pSurf->dt & (DT_DIB | DT_DIRECTDRAW)))
    {
        return;
    }

    // Get the heap node pointer for the surface
    pohSurf = pSurf->poh;

    // Only surface in the discardable chain should be considered
    if ((! pSurf->bOffScreen) || (pohSurf->ohState != OH_DISCARDABLE))
    {
        return;
    }

    // Get the head of the discardable surface chain
    pohHead = &pSurf->ppdev->heap.ohDiscardable;

    // It is quite possible that the surface is already at the end of the queue
    if (pohSurf->pohNext == pohHead)
    {
        return;
    }

    // Remove the surf the priority queue
    pohSurf->pohPrev->pohNext = pohSurf->pohNext;    
    pohSurf->pohNext->pohPrev = pohSurf->pohPrev;

    // Link the surf into the priority queue at the end
    pohSurf->pohPrev = pohHead->pohPrev;
    pohSurf->pohNext = pohHead;

    pohSurf->pohPrev->pohNext = pohSurf;
    pohHead->pohPrev = pohSurf;
}


/******************************Callback*Routine****************************\
* DWORD DdFreeDriverMemory
*
* This function called by DirectDraw when it's running low on memory in
* our heap.  You only need to implement this function if you use the
* DirectDraw 'HeapVidMemAllocAligned' function in your driver, and you
* can boot those allocations out of memory to make room for DirectDraw.
*
* We implement this function in the P3 driver because we have DirectDraw
* entirely manage our off-screen heap, and we use HeapVidMemAllocAligned
* to put GDI device-bitmaps in off-screen memory.  DirectDraw applications
* have a higher priority for getting stuff into video memory, though, and
* so this function is used to boot those GDI surfaces out of memory in
* order to make room for DirectDraw.
*
\**************************************************************************/
DWORD CALLBACK
DdFreeDriverMemory(PDD_FREEDRIVERMEMORYDATA lpFreeDriverMemory)
{
    PPDEV ppdev;
    OH* pohSurf;

    DISPDBG((DBGLVL, "DdFreeDriverMemory is called"));
    
    // Set the return value in case no VM is available
    lpFreeDriverMemory->ddRVal = DDERR_OUTOFMEMORY;

    // Get the head of discardable surface queue
    ppdev = (PPDEV)lpFreeDriverMemory->lpDD->dhpdev;
    pohSurf = ppdev->heap.ohDiscardable.pohNext;

    while (pohSurf != &ppdev->heap.ohDiscardable)
    {
        if (! pohSurf->bDXManaged)
        {
            pohSurf = pohSurf->pohNext;
            continue;
        }

        // Try to demote this VM bitmap to SM
        if (pohMoveOffscreenDfbToDib(ppdev, pohSurf))
        {
            lpFreeDriverMemory->ddRVal = DD_OK;
        }

        break;
    }

    return (DDHAL_DRIVER_HANDLED);
}


/******************************Callback*Routine****************************\
* DdSetExclusiveMode
*
* This function is called by DirectDraw when we switch from the GDI surface,
* to DirectDraw exclusive mode, e.g. to run a game in fullcreen mode.
* You only need to implement this function when you are using the
* 'HeapVidMemAllocAligned' function and allocate memory for Device Bitmaps
* and DirectDraw surfaces from the same heap.
*
* We use this call to disable GDI DeviceBitMaps when we are running in
* DirectDraw exclusive mode. Otherwise a DD app gets confused if both GDI and
* DirectDraw allocate memory from the same heap.
*
*
\**************************************************************************/
DWORD CALLBACK
DdSetExclusiveMode(PDD_SETEXCLUSIVEMODEDATA lpSetExclusiveMode)
{
    DISPDBG((DBGLVL, "DdSetExclusiveMode is called"));

    if (lpSetExclusiveMode->dwEnterExcl)
    {
        // Remove all GDI device bitmaps from video memory here
        // and make sure they will not be promoted to videomemory
        // until we leave exclusive mode.

        bMoveAllDfbsFromOffscreenToDibs(
            (PDEV*)lpSetExclusiveMode->lpDD->dhpdev);
    }

    lpSetExclusiveMode->ddRVal = DD_OK;

    return (DDHAL_DRIVER_HANDLED);
}


/******************************Callback*Routine****************************\
* DWORD DdFlipToGDISurface
*
* This function is called by DirectDraw when it flips to the surface on which
* GDI can write to.
\**************************************************************************/

#if DX7_STEREO
#define __VIDEO_STEREOENABLE    0x800
#endif

DWORD CALLBACK
DdFlipToGDISurface(PDD_FLIPTOGDISURFACEDATA lpFlipToGDISurface)
{
    PDEV* ppdev = (PDEV *)lpFlipToGDISurface->lpDD->dhpdev;
    GLINT_DECL;
    
    DISPDBG((DBGLVL, "DdFlipToGDISurface is called"));

    lpFlipToGDISurface->ddRVal = DD_OK;

#if DX7_STEREO
    READ_GLINT_CTRL_REG(VideoControl, dwVideoControl);
    WRITE_GLINT_CTRL_REG(VideoControl, 
                         (dwVideoControl & (~__VIDEO_STEREOENABLE)));
#endif

    //
    //  Return NOTHANDLED, so that the ddraw runtime takes
    //  care that we flip back to the primary...
    //

    return (DDHAL_DRIVER_NOTHANDLED);
}

#endif

/*****************************Public*Routine*******************************\
*
* HBITMAP DrvDeriveSurface
*
* This function derives and creates a GDI surface from the specified
* DirectDraw surface.
*
* Parameters
*  pDirectDraw-----Points to a DD_DIRECTDRAW_GLOBAL structure that describes
*                  the DirectDraw object. 
*  pSurface--------Points to a DD_SURFACE_LOCAL structure that describes the
*                  DirectDraw surface around which to wrap a GDI surface.
*
* Return Value
*  DrvDeriveSurface returns a handle to the created GDI surface upon success.
*  It returns NULL if the call fails or if the driver cannot accelerate GDI
*  drawing to the specified DirectDraw surface.
*
* Comments
*  DrvDeriveSurface allows the driver to create a GDI surface around a
*  DirectDraw video memory or AGP surface object in order to allow accelerated
*  GDI drawing to the surface. If the driver does not hook this call, all GDI
*  drawing to DirectDraw surfaces is done in software using the DIB engine.
*
*  GDI calls DrvDeriveSurface with RGB surfaces only.
*
*  The driver should call DrvCreateDeviceBitmap to create a GDI surface of the
*  same size and format as that of the DirectDraw surface. Space for the
*  actual pixels need not be allocated since it already exists.
*
\**************************************************************************/

HBITMAP
DrvDeriveSurface(DD_DIRECTDRAW_GLOBAL*  pDirectDraw,
                 DD_SURFACE_LOCAL*      pSurface)
{
    PDEV*               ppdev;
    DSURF*              pdsurf;
    HBITMAP             hbmDevice;
    DD_SURFACE_GLOBAL*  pSurfaceGlobal;
    SIZEL               sizl;

    ppdev = (PDEV*)pDirectDraw->dhpdev;
    pSurfaceGlobal = pSurface->lpGbl;

    // Only accel. primary surface.

    if (pSurface->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)
    {
        pdsurf = (DSURF*)ENGALLOCMEM(FL_ZERO_MEMORY, sizeof(DSURF), ALLOC_TAG_GDI(E));

        if (pdsurf != NULL)
        {
            sizl.cx = pSurfaceGlobal->wWidth;
            sizl.cy = pSurfaceGlobal->wHeight;

            hbmDevice = EngCreateDeviceBitmap((DHSURF)pdsurf,
                                               sizl,
                                               ppdev->iBitmapFormat);

            if ((hbmDevice != NULL) &&
                (EngAssociateSurface((HSURF)hbmDevice, ppdev->hdevEng, ppdev->flHooks)))
            {    
                PVOID   pvScan0 = ppdev->pjScreen + pSurfaceGlobal->fpVidMem;

                HBITMAP hbmDib = EngCreateBitmap(
                                     sizl,
                                     (ULONG) pSurfaceGlobal->lPitch,
                                     (ULONG)(ppdev->iBitmapFormat),
                                     (FLONG)(((pSurfaceGlobal->lPitch > 0) ? BMF_TOPDOWN : 0)),
                                     (PVOID) pvScan0);

                if ((hbmDib != NULL) &&
                    (EngAssociateSurface((HSURF)hbmDib, ppdev->hdevEng, HOOK_SYNCHRONIZE)))
                {
                    pdsurf->dt    = DT_SCREEN | DT_DIRECTDRAW;
                    pdsurf->bOffScreen = FALSE;
                    pdsurf->poh   = ppdev->pohScreen;
                    pdsurf->sizl  = sizl;
                    pdsurf->ppdev = ppdev;

                    if (pdsurf->pso = EngLockSurface((HSURF)hbmDib)) 
                    {
                        return (hbmDevice);
                    }
                }

                if (hbmDib)
                {
                    EngDeleteSurface((HSURF)hbmDib);
                }
            }

            if (hbmDevice)
            {
                EngDeleteSurface((HSURF)hbmDevice);
            }

            ENGFREEMEM(pdsurf);
        }
    }

    return(0);

} // DrvDeriveSurface()