/** * 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; } }
/** * 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, (pointer) 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; }
/** * 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; }
void exaDoMigration_mixed(ExaMigrationPtr pixmaps, int npixmaps, Bool can_accel) { int 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)) { can_accel = FALSE; break; } } /* We can do nothing. */ if (!can_accel) return; for (i = 0; i < npixmaps; i++) { PixmapPtr pPixmap = pixmaps[i].pPix; ExaPixmapPriv(pPixmap); if (!pExaPixmap->driverPriv) exaCreateDriverPixmap_mixed(pPixmap); if (pExaPixmap->pDamage && exaPixmapHasGpuCopy(pPixmap)) { ExaScreenPriv(pPixmap->drawable.pScreen); /* This pitch is needed for proper acceleration. For some reason * there are pixmaps without pDamage and a bad fb_pitch value. * So setting devKind when only exaPixmapHasGpuCopy() is true * causes corruption. Pixmaps without pDamage are not migrated * and should have a valid devKind at all times, so that's why this * isn't causing problems. Pixmaps have their gpu pitch set the * first time in the MPH call from exaCreateDriverPixmap_mixed(). */ pPixmap->devKind = pExaPixmap->fb_pitch; exaCopyDirtyToFb(pixmaps + i); if (pExaScr->deferred_mixed_pixmap == pPixmap && !pixmaps[i].as_dst && !pixmaps[i].pReg) pExaScr->deferred_mixed_pixmap = NULL; } pExaPixmap->use_gpu_copy = exaPixmapHasGpuCopy(pPixmap); } }
void exaCreateDriverPixmap_mixed(PixmapPtr pPixmap) { ScreenPtr pScreen = pPixmap->drawable.pScreen; ExaScreenPriv(pScreen); ExaPixmapPriv(pPixmap); int w = pPixmap->drawable.width, h = pPixmap->drawable.height; int depth = pPixmap->drawable.depth, bpp = pPixmap->drawable.bitsPerPixel; int usage_hint = pPixmap->usage_hint; int paddedWidth = pExaPixmap->sys_pitch; /* Already done. */ if (pExaPixmap->driverPriv) return; if (exaPixmapIsPinned(pPixmap)) return; /* Can't accel 1/4 bpp. */ if (pExaPixmap->accel_blocked || bpp < 8) return; if (pExaScr->info->CreatePixmap2) { int new_pitch = 0; pExaPixmap->driverPriv = pExaScr->info->CreatePixmap2(pScreen, w, h, depth, usage_hint, bpp, &new_pitch); paddedWidth = pExaPixmap->fb_pitch = new_pitch; } else { if (paddedWidth < pExaPixmap->fb_pitch) paddedWidth = pExaPixmap->fb_pitch; pExaPixmap->driverPriv = pExaScr->info->CreatePixmap(pScreen, paddedWidth * h, 0); } if (!pExaPixmap->driverPriv) return; (*pScreen->ModifyPixmapHeader) (pPixmap, w, h, 0, 0, paddedWidth, NULL); }
/** * Switches the current active location of the pixmap to system memory, copying * updated data out if necessary. */ void exaMoveOutPixmap (PixmapPtr pPixmap) { ExaPixmapPriv (pPixmap); if (exaPixmapIsPinned(pPixmap)) return; if (exaPixmapIsOffscreen(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')); exaCopyDirtyToSys (pPixmap); pPixmap->devPrivate.ptr = pExaPixmap->sys_ptr; pPixmap->devKind = pExaPixmap->sys_pitch; pPixmap->drawable.serialNumber = NEXT_SERIAL_NUMBER; } }
/** * 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 (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) && !exaPixmapIsOffscreen (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)) exaMoveOutPixmap (pixmaps[i].pPix); } 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].pPix); if (!exaPixmapIsDirty (pixmaps[i].pPix)) exaMoveOutPixmap (pixmaps[i].pPix); } return; } /* Finally, the acceleration path. Move them all in. */ for (i = 0; i < npixmaps; i++) { exaMigrateTowardFb(pixmaps[i].pPix); exaMoveInPixmap(pixmaps[i].pPix); } } 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].pPix); return; } for (i = 0; i < npixmaps; i++) { if (exaPixmapIsOffscreen(pixmaps[i].pPix)) { /* Found one in FB, so move all to FB. */ for (j = 0; j < npixmaps; j++) exaMigrateTowardFb(pixmaps[j].pPix); return; } } /* Nobody's in FB, so move all away from FB. */ for (i = 0; i < npixmaps; i++) exaMigrateTowardSys(pixmaps[i].pPix); } 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++) exaMoveOutPixmap(pixmaps[i].pPix); return; } /* Now, try to move them all into FB */ for (i = 0; i < npixmaps; i++) { exaMoveInPixmap(pixmaps[i].pPix); ExaOffscreenMarkUsed (pixmaps[i].pPix); } /* If we couldn't fit everything in, then kick back out */ for (i = 0; i < npixmaps; i++) { if (!exaPixmapIsOffscreen(pixmaps[i].pPix)) { EXA_FALLBACK(("Pixmap %p (%dx%d) not in fb\n", pixmaps[i].pPix, pixmaps[i].pPix->drawable.width, pixmaps[i].pPix->drawable.height)); for (j = 0; j < npixmaps; j++) exaMoveOutPixmap(pixmaps[j].pPix); break; } } } }
/** * 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; }
/* With mixed pixmaps, if we fail to get direct access to the driver pixmap, we * use the DownloadFromScreen hook to retrieve contents to a copy in system * memory, perform software rendering on that and move back the results with the * UploadToScreen hook (see exaDamageReport_mixed). */ void exaPrepareAccessReg_mixed(PixmapPtr pPixmap, int index, RegionPtr pReg) { ExaPixmapPriv(pPixmap); Bool has_gpu_copy = exaPixmapHasGpuCopy(pPixmap); Bool success; success = ExaDoPrepareAccess(pPixmap, index); if (success && has_gpu_copy && pExaPixmap->pDamage) { /* You cannot do accelerated operations while a buffer is mapped. */ exaFinishAccess(&pPixmap->drawable, index); /* Update the gpu view of both deferred destination pixmaps and of * source pixmaps that were migrated with a bounding region. */ exaMoveInPixmap_mixed(pPixmap); success = ExaDoPrepareAccess(pPixmap, index); if (success) { /* We have a gpu pixmap that can be accessed, we don't need the cpu * copy anymore. Drivers that prefer DFS, should fail prepare * access. */ DamageDestroy(pExaPixmap->pDamage); pExaPixmap->pDamage = NULL; free(pExaPixmap->sys_ptr); pExaPixmap->sys_ptr = NULL; return; } } if (!success) { ExaMigrationRec pixmaps[1]; /* Do we need to allocate our system buffer? */ if (!pExaPixmap->sys_ptr) { pExaPixmap->sys_ptr = malloc(pExaPixmap->sys_pitch * pPixmap->drawable.height); if (!pExaPixmap->sys_ptr) FatalError("EXA: malloc failed for size %d bytes\n", pExaPixmap->sys_pitch * pPixmap->drawable.height); } 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; if (!pExaPixmap->pDamage && (has_gpu_copy || !exaPixmapIsPinned(pPixmap))) { Bool as_dst = pixmaps[0].as_dst; /* Set up damage tracking */ pExaPixmap->pDamage = DamageCreate(exaDamageReport_mixed, NULL, DamageReportNonEmpty, TRUE, pPixmap->drawable.pScreen, pPixmap); if (pExaPixmap->pDamage) { DamageRegister(&pPixmap->drawable, pExaPixmap->pDamage); /* This ensures that pending damage reflects the current * operation. This is used by exa to optimize migration. */ DamageSetReportAfterOp(pExaPixmap->pDamage, TRUE); } if (has_gpu_copy) { exaPixmapDirty(pPixmap, 0, 0, pPixmap->drawable.width, pPixmap->drawable.height); /* We don't know which region of the destination will be damaged, * have to assume all of it */ if (as_dst) { pixmaps[0].as_dst = FALSE; pixmaps[0].as_src = TRUE; pixmaps[0].pReg = NULL; } exaCopyDirtyToSys(pixmaps); } if (as_dst) exaPixmapDirty(pPixmap, 0, 0, pPixmap->drawable.width, pPixmap->drawable.height); } else if (has_gpu_copy) exaCopyDirtyToSys(pixmaps); pPixmap->devPrivate.ptr = pExaPixmap->sys_ptr; pPixmap->devKind = pExaPixmap->sys_pitch; pExaPixmap->use_gpu_copy = FALSE; } }