/*
 * Copyright © 2006 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Authors:
 *    Eric Anholt <eric@anholt.net>
 *    Michel Dänzer <michel@tungstengraphics.com>
 *
 */

#include <dix-config.h>

#include <string.h>

#include "exa_priv.h"
#include "exa.h"

#if DEBUG_MIGRATE
#define DBG_MIGRATE(a) ErrorF a
#else
#define DBG_MIGRATE(a)
#endif

/**
 * The fallback path for UTS/DFS failing is to just memcpy.  exaCopyDirtyToSys
 * and exaCopyDirtyToFb both needed to do this loop.
 */
static void
exaMemcpyBox(PixmapPtr pPixmap, BoxPtr pbox, CARD8 *src, int src_pitch,
             CARD8 *dst, int dst_pitch)
{
    int i, cpp = pPixmap->drawable.bitsPerPixel / 8;
    int bytes = (pbox->x2 - pbox->x1) * cpp;

    src += pbox->y1 * src_pitch + pbox->x1 * cpp;
    dst += pbox->y1 * dst_pitch + pbox->x1 * cpp;

    for (i = pbox->y2 - pbox->y1; i; i--) {
        memcpy(dst, src, bytes);
        src += src_pitch;
        dst += dst_pitch;
    }
}

/**
 * Returns TRUE if the pixmap is dirty (has been modified in its current
 * location compared to the other), or lacks a private for tracking
 * dirtiness.
 */
static Bool
exaPixmapIsDirty(PixmapPtr pPix)
{
    ExaPixmapPriv(pPix);

    if (pExaPixmap == NULL)
        EXA_FatalErrorDebugWithRet(("EXA bug: exaPixmapIsDirty was called on a non-exa pixmap.\n"), TRUE);

    if (!pExaPixmap->pDamage)
        return FALSE;

    return RegionNotEmpty(DamageRegion(pExaPixmap->pDamage)) ||
        !RegionEqual(&pExaPixmap->validSys, &pExaPixmap->validFB);
}

/**
 * Returns TRUE if the pixmap is either pinned in FB, or has a sufficient score
 * to be considered "should be in framebuffer".  That's just anything that has
 * had more acceleration than fallbacks, or has no score yet.
 *
 * Only valid if using a migration scheme that tracks score.
 */
static Bool
exaPixmapShouldBeInFB(PixmapPtr pPix)
{
    ExaPixmapPriv(pPix);

    if (exaPixmapIsPinned(pPix))
        return TRUE;

    return pExaPixmap->score >= 0;
}

/**
 * If the pixmap is currently dirty, this copies at least the dirty area from
 * FB to system or vice versa.  Both areas must be allocated.
 */
static void
exaCopyDirty(ExaMigrationPtr migrate, RegionPtr pValidDst, RegionPtr pValidSrc,
             Bool (*transfer) (PixmapPtr pPix, int x, int y, int w, int h,
                               char *sys, int sys_pitch), int fallback_index,
             void (*sync) (ScreenPtr pScreen))
{
    PixmapPtr pPixmap = migrate->pPix;

    ExaPixmapPriv(pPixmap);
    RegionPtr damage = DamageRegion(pExaPixmap->pDamage);
    RegionRec CopyReg;
    Bool save_use_gpu_copy;
    int save_pitch;
    BoxPtr pBox;
    int nbox;
    Bool access_prepared = FALSE;
    Bool need_sync = FALSE;

    /* Damaged bits are valid in current copy but invalid in other one */
    if (pExaPixmap->use_gpu_copy) {
        RegionUnion(&pExaPixmap->validFB, &pExaPixmap->validFB, damage);
        RegionSubtract(&pExaPixmap->validSys, &pExaPixmap->validSys, damage);
    }
    else {
        RegionUnion(&pExaPixmap->validSys, &pExaPixmap->validSys, damage);
        RegionSubtract(&pExaPixmap->validFB, &pExaPixmap->validFB, damage);
    }

    RegionEmpty(damage);

    /* Copy bits valid in source but not in destination */
    RegionNull(&CopyReg);
    RegionSubtract(&CopyReg, pValidSrc, pValidDst);

    if (migrate->as_dst) {
        ExaScreenPriv(pPixmap->drawable.pScreen);

        /* XXX: The pending damage region will be marked as damaged after the
         * operation, so it should serve as an upper bound for the region that
         * needs to be synchronized for the operation. Unfortunately, this
         * causes corruption in some cases, e.g. when starting compiz. See
         * https://bugs.freedesktop.org/show_bug.cgi?id=12916 .
         */
        if (pExaScr->optimize_migration) {
            RegionPtr pending_damage = DamagePendingRegion(pExaPixmap->pDamage);

#if DEBUG_MIGRATE
            if (RegionNil(pending_damage)) {
                static Bool firsttime = TRUE;

                if (firsttime) {
                    ErrorF("%s: Pending damage region empty!\n", __func__);
                    firsttime = FALSE;
                }
            }
#endif

            /* Try to prevent destination valid region from growing too many
             * rects by filling it up to the extents of the union of the
             * destination valid region and the pending damage region.
             */
            if (RegionNumRects(pValidDst) > 10) {
                BoxRec box;
                BoxPtr pValidExt, pDamageExt;
                RegionRec closure;

                pValidExt = RegionExtents(pValidDst);
                pDamageExt = RegionExtents(pending_damage);

                box.x1 = min(pValidExt->x1, pDamageExt->x1);
                box.y1 = min(pValidExt->y1, pDamageExt->y1);
                box.x2 = max(pValidExt->x2, pDamageExt->x2);
                box.y2 = max(pValidExt->y2, pDamageExt->y2);

                RegionInit(&closure, &box, 0);
                RegionIntersect(&CopyReg, &CopyReg, &closure);
            }
            else
                RegionIntersect(&CopyReg, &CopyReg, pending_damage);
        }

        /* The caller may provide a region to be subtracted from the calculated
         * dirty region. This is to avoid migration of bits that don't
         * contribute to the result of the operation.
         */
        if (migrate->pReg)
            RegionSubtract(&CopyReg, &CopyReg, migrate->pReg);
    }
    else {
        /* The caller may restrict the region to be migrated for source pixmaps
         * to what's relevant for the operation.
         */
        if (migrate->pReg)
            RegionIntersect(&CopyReg, &CopyReg, migrate->pReg);
    }

    pBox = RegionRects(&CopyReg);
    nbox = RegionNumRects(&CopyReg);

    save_use_gpu_copy = pExaPixmap->use_gpu_copy;
    save_pitch = pPixmap->devKind;
    pExaPixmap->use_gpu_copy = TRUE;
    pPixmap->devKind = pExaPixmap->fb_pitch;

    while (nbox--) {
        pBox->x1 = max(pBox->x1, 0);
        pBox->y1 = max(pBox->y1, 0);
        pBox->x2 = min(pBox->x2, pPixmap->drawable.width);
        pBox->y2 = min(pBox->y2, pPixmap->drawable.height);

        if (pBox->x1 >= pBox->x2 || pBox->y1 >= pBox->y2)
            continue;

        if (!transfer || !transfer(pPixmap,
                                   pBox->x1, pBox->y1,
                                   pBox->x2 - pBox->x1,
                                   pBox->y2 - pBox->y1,
                                   (char *) (pExaPixmap->sys_ptr
                                             + pBox->y1 * pExaPixmap->sys_pitch
                                             +
                                             pBox->x1 *
                                             pPixmap->drawable.bitsPerPixel /
                                             8), pExaPixmap->sys_pitch)) {
            if (!access_prepared) {
                ExaDoPrepareAccess(pPixmap, fallback_index);
                access_prepared = TRUE;
            }
            if (fallback_index == EXA_PREPARE_DEST) {
                exaMemcpyBox(pPixmap, pBox,
                             pExaPixmap->sys_ptr, pExaPixmap->sys_pitch,
                             pPixmap->devPrivate.ptr, pPixmap->devKind);
            }
            else {
                exaMemcpyBox(pPixmap, pBox,
                             pPixmap->devPrivate.ptr, pPixmap->devKind,
                             pExaPixmap->sys_ptr, pExaPixmap->sys_pitch);
            }
        }
        else
            need_sync = TRUE;

        pBox++;
    }

    pExaPixmap->use_gpu_copy = save_use_gpu_copy;
    pPixmap->devKind = save_pitch;

    /* Try to prevent source valid region from growing too many rects by
     * removing parts of it which are also in the destination valid region.
     * Removing anything beyond that would lead to data loss.
     */
    if (RegionNumRects(pValidSrc) > 20)
        RegionSubtract(pValidSrc, pValidSrc, pValidDst);

    /* The copied bits are now valid in destination */
    RegionUnion(pValidDst, pValidDst, &CopyReg);

    RegionUninit(&CopyReg);

    if (access_prepared)
        exaFinishAccess(&pPixmap->drawable, fallback_index);
    else if (need_sync && sync)
        sync(pPixmap->drawable.pScreen);
}

/**
 * If the pixmap is currently dirty, this copies at least the dirty area from
 * the framebuffer  memory copy to the system memory copy.  Both areas must be
 * allocated.
 */
void
exaCopyDirtyToSys(ExaMigrationPtr migrate)
{
    PixmapPtr pPixmap = migrate->pPix;

    ExaScreenPriv(pPixmap->drawable.pScreen);
    ExaPixmapPriv(pPixmap);

    exaCopyDirty(migrate, &pExaPixmap->validSys, &pExaPixmap->validFB,
                 pExaScr->info->DownloadFromScreen, EXA_PREPARE_SRC,
                 exaWaitSync);
}

/**
 * If the pixmap is currently dirty, this copies at least the dirty area from
 * the system memory copy to the framebuffer memory copy.  Both areas must be
 * allocated.
 */
void
exaCopyDirtyToFb(ExaMigrationPtr migrate)
{
    PixmapPtr pPixmap = migrate->pPix;

    ExaScreenPriv(pPixmap->drawable.pScreen);
    ExaPixmapPriv(pPixmap);

    exaCopyDirty(migrate, &pExaPixmap->validFB, &pExaPixmap->validSys,
                 pExaScr->info->UploadToScreen, EXA_PREPARE_DEST, NULL);
}

/**
 * Allocates a framebuffer copy of the pixmap if necessary, and then copies
 * any necessary pixmap data into the framebuffer copy and points the pixmap at
 * it.
 *
 * Note that when first allocated, a pixmap will have FALSE dirty flag.
 * This is intentional because pixmap data starts out undefined.  So if we move
 * it in due to the first operation against it being accelerated, it will have
 * undefined framebuffer contents that we didn't have to upload.  If we do
 * moveouts (and moveins) after the first movein, then we will only have to copy
 * back and forth if the pixmap was written to after the last synchronization of
 * the two copies.  Then, at exaPixmapSave (when the framebuffer copy goes away)
 * we mark the pixmap dirty, so that the next exaMoveInPixmap will actually move
 * all the data, since it's almost surely all valid now.
 */
static void
exaDoMoveInPixmap(ExaMigrationPtr migrate)
{
    PixmapPtr pPixmap = migrate->pPix;
    ScreenPtr pScreen = pPixmap->drawable.pScreen;

    ExaScreenPriv(pScreen);
    ExaPixmapPriv(pPixmap);

    /* If we're VT-switched away, no touching card memory allowed. */
    if (pExaScr->swappedOut)
        return;

    /* If we're not allowed to move, then fail. */
    if (exaPixmapIsPinned(pPixmap))
        return;

    /* Don't migrate in pixmaps which are less than 8bpp.  This avoids a lot of
     * fragility in EXA, and <8bpp is probably not used enough any more to care
     * (at least, not in acceleratd paths).
     */
    if (pPixmap->drawable.bitsPerPixel < 8)
        return;

    if (pExaPixmap->accel_blocked)
        return;

    if (pExaPixmap->area == NULL) {
        pExaPixmap->area =
            exaOffscreenAlloc(pScreen, pExaPixmap->fb_size,
                              pExaScr->info->pixmapOffsetAlign, FALSE,
                              exaPixmapSave, (void *) pPixmap);
        if (pExaPixmap->area == NULL)
            return;

        pExaPixmap->fb_ptr = (CARD8 *) pExaScr->info->memoryBase +
            pExaPixmap->area->offset;
    }

    exaCopyDirtyToFb(migrate);

    if (exaPixmapHasGpuCopy(pPixmap))
        return;

    DBG_MIGRATE(("-> %p (0x%x) (%dx%d) (%c)\n", pPixmap,
                 (ExaGetPixmapPriv(pPixmap)->area ?
                  ExaGetPixmapPriv(pPixmap)->area->offset : 0),
                 pPixmap->drawable.width,
                 pPixmap->drawable.height,
                 exaPixmapIsDirty(pPixmap) ? 'd' : 'c'));

    pExaPixmap->use_gpu_copy = TRUE;

    pPixmap->devKind = pExaPixmap->fb_pitch;
    pPixmap->drawable.serialNumber = NEXT_SERIAL_NUMBER;
}

void
exaMoveInPixmap_classic(PixmapPtr pPixmap)
{
    static ExaMigrationRec migrate = {.as_dst = FALSE,.as_src = TRUE,
        .pReg = NULL
    };

    migrate.pPix = pPixmap;
    exaDoMoveInPixmap(&migrate);
}

/**
 * Switches the current active location of the pixmap to system memory, copying
 * updated data out if necessary.
 */
static void
exaDoMoveOutPixmap(ExaMigrationPtr migrate)
{
    PixmapPtr pPixmap = migrate->pPix;

    ExaPixmapPriv(pPixmap);

    if (!pExaPixmap->area || exaPixmapIsPinned(pPixmap))
        return;

    exaCopyDirtyToSys(migrate);

    if (exaPixmapHasGpuCopy(pPixmap)) {

        DBG_MIGRATE(("<- %p (%p) (%dx%d) (%c)\n", pPixmap,
                     (void *) (ExaGetPixmapPriv(pPixmap)->area ?
                               ExaGetPixmapPriv(pPixmap)->area->offset : 0),
                     pPixmap->drawable.width,
                     pPixmap->drawable.height,
                     exaPixmapIsDirty(pPixmap) ? 'd' : 'c'));

        pExaPixmap->use_gpu_copy = FALSE;

        pPixmap->devKind = pExaPixmap->sys_pitch;
        pPixmap->drawable.serialNumber = NEXT_SERIAL_NUMBER;
    }
}

void
exaMoveOutPixmap_classic(PixmapPtr pPixmap)
{
    static ExaMigrationRec migrate = {.as_dst = FALSE,.as_src = TRUE,
        .pReg = NULL
    };

    migrate.pPix = pPixmap;
    exaDoMoveOutPixmap(&migrate);
}

/**
 * Copies out important pixmap data and removes references to framebuffer area.
 * Called when the memory manager decides it's time to kick the pixmap out of
 * framebuffer entirely.
 */
void
exaPixmapSave(ScreenPtr pScreen, ExaOffscreenArea * area)
{
    PixmapPtr pPixmap = area->privData;

    ExaPixmapPriv(pPixmap);

    exaMoveOutPixmap(pPixmap);

    pExaPixmap->fb_ptr = NULL;
    pExaPixmap->area = NULL;

    /* Mark all FB bits as invalid, so all valid system bits get copied to FB
     * next time */
    RegionEmpty(&pExaPixmap->validFB);
}

/**
 * For the "greedy" migration scheme, pushes the pixmap toward being located in
 * framebuffer memory.
 */
static void
exaMigrateTowardFb(ExaMigrationPtr migrate)
{
    PixmapPtr pPixmap = migrate->pPix;

    ExaPixmapPriv(pPixmap);

    if (pExaPixmap->score == EXA_PIXMAP_SCORE_PINNED) {
        DBG_MIGRATE(("UseScreen: not migrating pinned pixmap %p\n",
                     (void *) pPixmap));
        return;
    }

    DBG_MIGRATE(("UseScreen %p score %d\n",
                 (void *) pPixmap, pExaPixmap->score));

    if (pExaPixmap->score == EXA_PIXMAP_SCORE_INIT) {
        exaDoMoveInPixmap(migrate);
        pExaPixmap->score = 0;
    }

    if (pExaPixmap->score < EXA_PIXMAP_SCORE_MAX)
        pExaPixmap->score++;

    if (pExaPixmap->score >= EXA_PIXMAP_SCORE_MOVE_IN &&
        !exaPixmapHasGpuCopy(pPixmap)) {
        exaDoMoveInPixmap(migrate);
    }

    if (exaPixmapHasGpuCopy(pPixmap)) {
        exaCopyDirtyToFb(migrate);
        ExaOffscreenMarkUsed(pPixmap);
    }
    else
        exaCopyDirtyToSys(migrate);
}

/**
 * For the "greedy" migration scheme, pushes the pixmap toward being located in
 * system memory.
 */
static void
exaMigrateTowardSys(ExaMigrationPtr migrate)
{
    PixmapPtr pPixmap = migrate->pPix;

    ExaPixmapPriv(pPixmap);

    DBG_MIGRATE(("UseMem: %p score %d\n", (void *) pPixmap,
                 pExaPixmap->score));

    if (pExaPixmap->score == EXA_PIXMAP_SCORE_PINNED)
        return;

    if (pExaPixmap->score == EXA_PIXMAP_SCORE_INIT)
        pExaPixmap->score = 0;

    if (pExaPixmap->score > EXA_PIXMAP_SCORE_MIN)
        pExaPixmap->score--;

    if (pExaPixmap->score <= EXA_PIXMAP_SCORE_MOVE_OUT && pExaPixmap->area)
        exaDoMoveOutPixmap(migrate);

    if (exaPixmapHasGpuCopy(pPixmap)) {
        exaCopyDirtyToFb(migrate);
        ExaOffscreenMarkUsed(pPixmap);
    }
    else
        exaCopyDirtyToSys(migrate);
}

/**
 * If the pixmap has both a framebuffer and system memory copy, this function
 * asserts that both of them are the same.
 */
static Bool
exaAssertNotDirty(PixmapPtr pPixmap)
{
    ExaPixmapPriv(pPixmap);
    CARD8 *dst, *src;
    RegionRec ValidReg;
    int dst_pitch, src_pitch, cpp, y, nbox, save_pitch;
    BoxPtr pBox;
    Bool ret = TRUE, save_use_gpu_copy;

    if (exaPixmapIsPinned(pPixmap) || pExaPixmap->area == NULL)
        return ret;

    RegionNull(&ValidReg);
    RegionIntersect(&ValidReg, &pExaPixmap->validFB, &pExaPixmap->validSys);
    nbox = RegionNumRects(&ValidReg);

    if (!nbox)
        goto out;

    pBox = RegionRects(&ValidReg);

    dst_pitch = pExaPixmap->sys_pitch;
    src_pitch = pExaPixmap->fb_pitch;
    cpp = pPixmap->drawable.bitsPerPixel / 8;

    save_use_gpu_copy = pExaPixmap->use_gpu_copy;
    save_pitch = pPixmap->devKind;
    pExaPixmap->use_gpu_copy = TRUE;
    pPixmap->devKind = pExaPixmap->fb_pitch;

    if (!ExaDoPrepareAccess(pPixmap, EXA_PREPARE_SRC))
        goto skip;

    while (nbox--) {
        int rowbytes;

        pBox->x1 = max(pBox->x1, 0);
        pBox->y1 = max(pBox->y1, 0);
        pBox->x2 = min(pBox->x2, pPixmap->drawable.width);
        pBox->y2 = min(pBox->y2, pPixmap->drawable.height);

        if (pBox->x1 >= pBox->x2 || pBox->y1 >= pBox->y2)
            continue;

        rowbytes = (pBox->x2 - pBox->x1) * cpp;
        src =
            (CARD8 *) pPixmap->devPrivate.ptr + pBox->y1 * src_pitch +
            pBox->x1 * cpp;
        dst = pExaPixmap->sys_ptr + pBox->y1 * dst_pitch + pBox->x1 * cpp;

        for (y = pBox->y1; y < pBox->y2;
             y++, src += src_pitch, dst += dst_pitch) {
            if (memcmp(dst, src, rowbytes) != 0) {
                ret = FALSE;
                exaPixmapDirty(pPixmap, pBox->x1, pBox->y1, pBox->x2, pBox->y2);
                break;
            }
        }
    }

 skip:
    exaFinishAccess(&pPixmap->drawable, EXA_PREPARE_SRC);

    pExaPixmap->use_gpu_copy = save_use_gpu_copy;
    pPixmap->devKind = save_pitch;

 out:
    RegionUninit(&ValidReg);
    return ret;
}

/**
 * Performs migration of the pixmaps according to the operation information
 * provided in pixmaps and can_accel and the migration scheme chosen in the
 * config file.
 */
void
exaDoMigration_classic(ExaMigrationPtr pixmaps, int npixmaps, Bool can_accel)
{
    ScreenPtr pScreen = pixmaps[0].pPix->drawable.pScreen;

    ExaScreenPriv(pScreen);
    int i, j;

    /* If this debugging flag is set, check each pixmap for whether it is marked
     * as clean, and if so, actually check if that's the case.  This should help
     * catch issues with failing to mark a drawable as dirty.  While it will
     * catch them late (after the operation happened), it at least explains what
     * went wrong, and instrumenting the code to find what operation happened
     * to the pixmap last shouldn't be hard.
     */
    if (pExaScr->checkDirtyCorrectness) {
        for (i = 0; i < npixmaps; i++) {
            if (!exaPixmapIsDirty(pixmaps[i].pPix) &&
                !exaAssertNotDirty(pixmaps[i].pPix))
                ErrorF("%s: Pixmap %d dirty but not marked as such!\n",
                       __func__, i);
        }
    }
    /* If anything is pinned in system memory, we won't be able to
     * accelerate.
     */
    for (i = 0; i < npixmaps; i++) {
        if (exaPixmapIsPinned(pixmaps[i].pPix) &&
            !exaPixmapHasGpuCopy(pixmaps[i].pPix)) {
            EXA_FALLBACK(("Pixmap %p (%dx%d) pinned in sys\n", pixmaps[i].pPix,
                          pixmaps[i].pPix->drawable.width,
                          pixmaps[i].pPix->drawable.height));
            can_accel = FALSE;
            break;
        }
    }

    if (pExaScr->migration == ExaMigrationSmart) {
        /* If we've got something as a destination that we shouldn't cause to
         * become newly dirtied, take the unaccelerated route.
         */
        for (i = 0; i < npixmaps; i++) {
            if (pixmaps[i].as_dst && !exaPixmapShouldBeInFB(pixmaps[i].pPix) &&
                !exaPixmapIsDirty(pixmaps[i].pPix)) {
                for (i = 0; i < npixmaps; i++) {
                    if (!exaPixmapIsDirty(pixmaps[i].pPix))
                        exaDoMoveOutPixmap(pixmaps + i);
                }
                return;
            }
        }

        /* If we aren't going to accelerate, then we migrate everybody toward
         * system memory, and kick out if it's free.
         */
        if (!can_accel) {
            for (i = 0; i < npixmaps; i++) {
                exaMigrateTowardSys(pixmaps + i);
                if (!exaPixmapIsDirty(pixmaps[i].pPix))
                    exaDoMoveOutPixmap(pixmaps + i);
            }
            return;
        }

        /* Finally, the acceleration path.  Move them all in. */
        for (i = 0; i < npixmaps; i++) {
            exaMigrateTowardFb(pixmaps + i);
            exaDoMoveInPixmap(pixmaps + i);
        }
    }
    else if (pExaScr->migration == ExaMigrationGreedy) {
        /* If we can't accelerate, either because the driver can't or because one of
         * the pixmaps is pinned in system memory, then we migrate everybody toward
         * system memory.
         *
         * We also migrate toward system if all pixmaps involved are currently in
         * system memory -- this can mitigate thrashing when there are significantly
         * more pixmaps active than would fit in memory.
         *
         * If not, then we migrate toward FB so that hopefully acceleration can
         * happen.
         */
        if (!can_accel) {
            for (i = 0; i < npixmaps; i++)
                exaMigrateTowardSys(pixmaps + i);
            return;
        }

        for (i = 0; i < npixmaps; i++) {
            if (exaPixmapHasGpuCopy(pixmaps[i].pPix)) {
                /* Found one in FB, so move all to FB. */
                for (j = 0; j < npixmaps; j++)
                    exaMigrateTowardFb(pixmaps + i);
                return;
            }
        }

        /* Nobody's in FB, so move all away from FB. */
        for (i = 0; i < npixmaps; i++)
            exaMigrateTowardSys(pixmaps + i);
    }
    else if (pExaScr->migration == ExaMigrationAlways) {
        /* Always move the pixmaps out if we can't accelerate.  If we can
         * accelerate, try to move them all in.  If that fails, then move them
         * back out.
         */
        if (!can_accel) {
            for (i = 0; i < npixmaps; i++)
                exaDoMoveOutPixmap(pixmaps + i);
            return;
        }

        /* Now, try to move them all into FB */
        for (i = 0; i < npixmaps; i++) {
            exaDoMoveInPixmap(pixmaps + i);
        }

        /* If we couldn't fit everything in, abort */
        for (i = 0; i < npixmaps; i++) {
            if (!exaPixmapHasGpuCopy(pixmaps[i].pPix)) {
                return;
            }
        }

        /* Yay, everything has a gpu copy, mark memory as used */
        for (i = 0; i < npixmaps; i++) {
            ExaOffscreenMarkUsed(pixmaps[i].pPix);
        }
    }
}

void
exaPrepareAccessReg_classic(PixmapPtr pPixmap, int index, RegionPtr pReg)
{
    ExaMigrationRec pixmaps[1];

    if (index == EXA_PREPARE_DEST || index == EXA_PREPARE_AUX_DEST) {
        pixmaps[0].as_dst = TRUE;
        pixmaps[0].as_src = FALSE;
    }
    else {
        pixmaps[0].as_dst = FALSE;
        pixmaps[0].as_src = TRUE;
    }
    pixmaps[0].pPix = pPixmap;
    pixmaps[0].pReg = pReg;

    exaDoMigration(pixmaps, 1, FALSE);

    (void) ExaDoPrepareAccess(pPixmap, index);
}
