|
|
/******************************Module*Header*******************************\
* Module Name: textout.c * * There are three basic methods for drawing text with hardware * acceleration: * * 1) Glyph caching -- Glyph bitmaps are cached by the accelerator * (probably in off-screen memory), and text is drawn by * referring the hardware to the cached glyph locations. * * 2) Glyph expansion -- Each individual glyph is colour-expanded * directly to the screen from the monochrome glyph bitmap * supplied by GDI. * * 3) Buffer expansion -- The CPU is used to draw all the glyphs into * a 1bpp monochrome bitmap, and the hardware is then used * to colour-expand the result. * * The fastest method depends on a number of variables, such as the * colour expansion speed, bus speed, CPU speed, average glyph size, * and average string length. * * For the S3 with normal sized glyphs, I've found that caching the * glyphs in off-screen memory is typically the slowest method. * Buffer expansion is typically fastest on the slow ISA bus (or when * memory-mapped I/O isn't available on the x86), and glyph expansion * is best on fast buses such as VL and PCI. * * Glyph expansion is typically faster than buffer expansion for very * large glyphs, even on the ISA bus, because less copying by the CPU * needs to be done. Unfortunately, large glyphs are pretty rare. * * An advantange of the buffer expansion method is that opaque text will * never flash -- the other two methods typically need to draw the * opaquing rectangle before laying down the glyphs, which may cause * a flash if the raster is caught at the wrong time. * * This driver implements glyph expansion and buffer expansion -- * methods 2) and 3). Depending on the hardware capabilities at * run-time, we'll use whichever one will be faster. * * Copyright (c) 1992-1994 Microsoft Corporation * \**************************************************************************/
#include "precomp.h"
POINTL gptlZero = { 0, 0 }; // Specifies that the origin of the
// temporary buffer given to the 1bpp
// transfer routine for fasttext is
// at (0, 0)
#define FIFTEEN_BITS ((1 << 15)-1)
/******************************Public*Routine******************************\
* VOID vClipSolid * * Fills the specified rectangles with the specified colour, honouring * the requested clipping. No more than four rectangles should be passed in. * Intended for drawing the areas of the opaquing rectangle that extend * beyond the text box. The rectangles must be in left to right, top to * bottom order. Assumes there is at least one rectangle in the list. * \**************************************************************************/
VOID vClipSolid( PDEV* ppdev, LONG crcl, RECTL* prcl, ULONG iColor, CLIPOBJ* pco) { BOOL bMore; // Flag for clip enumeration
CLIPENUM ce; // Clip enumeration object
ULONG i; ULONG j; RECTL arclTmp[4]; ULONG crclTmp; RECTL* prclTmp; RECTL* prclClipTmp; LONG iLastBottom; RECTL* prclClip; RBRUSH_COLOR rbc;
ASSERTDD((crcl > 0) && (crcl <= 4), "Expected 1 to 4 rectangles"); ASSERTDD((pco != NULL) && (pco->iDComplexity != DC_TRIVIAL), "Expected a non-null clip object");
rbc.iSolidColor = iColor; if (pco->iDComplexity == DC_RECT) { crcl = cIntersect(&pco->rclBounds, prcl, crcl); if (crcl != 0) { (ppdev->pfnFillSolid)(ppdev, crcl, prcl, OVERPAINT, OVERPAINT, rbc, NULL); } } else // iDComplexity == DC_COMPLEX
{ // Bottom of last rectangle to fill
iLastBottom = prcl[crcl - 1].bottom;
// Initialize the clip rectangle enumeration to right-down so we can
// take advantage of the rectangle list being right-down:
CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, CD_RIGHTDOWN, 0);
// Scan through all the clip rectangles, looking for intersects
// of fill areas with region rectangles:
do { // Get a batch of region rectangles:
bMore = CLIPOBJ_bEnum(pco, sizeof(ce), (VOID*)&ce);
// Clip the rect list to each region rect:
for (j = ce.c, prclClip = ce.arcl; j-- > 0; prclClip++) { // Since the rectangles and the region enumeration are both
// right-down, we can zip through the region until we reach
// the first fill rect, and are done when we've passed the
// last fill rect.
if (prclClip->top >= iLastBottom) { // Past last fill rectangle; nothing left to do:
return; }
// Do intersection tests only if we've reached the top of
// the first rectangle to fill:
if (prclClip->bottom > prcl->top) { // We've reached the top Y scan of the first rect, so
// it's worth bothering checking for intersection.
// Generate a list of the rects clipped to this region
// rect:
prclTmp = prcl; prclClipTmp = arclTmp;
for (i = crcl, crclTmp = 0; i-- != 0; prclTmp++) { // Intersect fill and clip rectangles
if (bIntersect(prclTmp, prclClip, prclClipTmp)) { // Add to list if anything's left to draw:
crclTmp++; prclClipTmp++; } }
// Draw the clipped rects
if (crclTmp != 0) { (ppdev->pfnFillSolid)(ppdev, crclTmp, &arclTmp[0], OVERPAINT, OVERPAINT, rbc, NULL); } } } } while (bMore); } }
/******************************Public*Routine******************************\
* BOOL bBufferExpansion * * Outputs text using the 'buffer expansion' method. The CPU draws to a * 1bpp buffer, and the result is colour-expanded to the screen using the * hardware. * * Note that this is x86 only ('vFastText', which draws the glyphs to the * 1bpp buffer, is writen in Asm). * * If you're just getting your driver working, this is the fastest way to * bring up working accelerated text. All you have to do is write the * 'Xfer1bpp' function that's also used by the blt code. This * 'bBufferExpansion' routine shouldn't need to be modified at all. * \**************************************************************************/
#if defined(i386)
BOOL bBufferExpansion( PDEV* ppdev, STROBJ* pstro, CLIPOBJ* pco, RECTL* prclExtra, RECTL* prclOpaque, BRUSHOBJ* pboFore, BRUSHOBJ* pboOpaque) { BYTE jClip; BOOL bMore; // Flag for clip enumeration
GLYPHPOS* pgp; // Points to the first glyph
BOOL bMoreGlyphs; // Glyph enumeration flag
ULONG cGlyph; // # of glyphs in one batch
RECTL arclTmp[4]; // Temporary storage for portions
// of opaquing rectangle
RECTL* prclClip; // Points to list of clip rectangles
RECTL* prclDraw; // Actual text to be drawn
RECTL rclDraw; ULONG crcl; // Temporary rectangle count
ULONG ulBufferBytes; ULONG ulBufferHeight; BOOL bTextPerfectFit; ULONG flDraw; BOOL bTmpAlloc; SURFOBJ so; CLIPENUM ce; RBRUSH_COLOR rbc; ULONG ulHwBackMix; // Dictates whether opaque or
// transparent text
XLATEOBJ xlo; // Temporary for passing colours
XLATECOLORS xlc; // Temporary for keeping colours
jClip = (pco == NULL) ? DC_TRIVIAL : pco->iDComplexity;
// The foreground colour will always be solid:
xlc.iForeColor = pboFore->iSolidColor;
ASSERTDD(xlc.iForeColor != -1, "Expected solid foreground colour");
// See if the temporary buffer is big enough for the text; if
// not, try to allocate enough memory. We round up to the
// nearest dword multiple:
so.lDelta = ((((pstro->rclBkGround.right + 31) & ~31) - (pstro->rclBkGround.left & ~31)) >> 3);
ulBufferHeight = pstro->rclBkGround.bottom - pstro->rclBkGround.top;
ulBufferBytes = so.lDelta * ulBufferHeight;
if (((ULONG)so.lDelta > FIFTEEN_BITS) || (ulBufferHeight > FIFTEEN_BITS)) { // the math will have overflowed
return(FALSE); }
// Use our temporary buffer if it's big enough, otherwise
// allocate a buffer on the fly:
if (ulBufferBytes >= TMP_BUFFER_SIZE) { // The textout is so big that I doubt this allocation will
// cost a significant amount in performance:
bTmpAlloc = TRUE; so.pvScan0 = EngAllocUserMem(ulBufferBytes, ALLOC_TAG); if (so.pvScan0 == NULL) return(FALSE); } else { bTmpAlloc = FALSE; so.pvScan0 = ppdev->pvTmpBuffer; }
// Set fixed pitch, overlap, and top and bottom 'y' alignment
// flags:
if (!(pstro->flAccel & SO_HORIZONTAL) || (pstro->flAccel & SO_REVERSED)) { flDraw = 0; } else { flDraw = ((pstro->ulCharInc != 0) ? 0x01 : 0) | (((pstro->flAccel & (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT)) != (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT)) ? 0x02 : 0) | (((pstro->flAccel & (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT | SO_MAXEXT_EQUAL_BM_SIDE)) == (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT | SO_MAXEXT_EQUAL_BM_SIDE)) ? 0x04 : 0); }
// If there's an opaque rectangle, we'll do as much opaquing
// as possible as we do the text. If the opaque rectangle is
// larger than the text rectangle, then we'll do the fringe
// areas right now, and the text and associated background
// areas together later:
ulHwBackMix = LEAVE_ALONE; if (prclOpaque != NULL) { ulHwBackMix = OVERPAINT;
// Since we didn't set GCAPS_ARBRUSHOPAQUE (yes, it's
// missing a 'b'), we don't have to worry about getting
// anything other that a solid opaquing brush. I wouldn't
// recommend handling it anyway, since I'll bet it would
// break quite a few applications:
xlc.iBackColor = pboOpaque->iSolidColor;
ASSERTDD(xlc.iBackColor != -1, "Expected solid background colour");
// See if we have fringe areas to do. If so, build a list of
// rectangles to fill, in right-down order:
crcl = 0;
// Top fragment:
if (pstro->rclBkGround.top > prclOpaque->top) { arclTmp[crcl].top = prclOpaque->top; arclTmp[crcl].left = prclOpaque->left; arclTmp[crcl].right = prclOpaque->right; arclTmp[crcl++].bottom = pstro->rclBkGround.top; }
// Left fragment:
if (pstro->rclBkGround.left > prclOpaque->left) { arclTmp[crcl].top = pstro->rclBkGround.top; arclTmp[crcl].left = prclOpaque->left; arclTmp[crcl].right = pstro->rclBkGround.left; arclTmp[crcl++].bottom = pstro->rclBkGround.bottom; }
// Right fragment:
if (pstro->rclBkGround.right < prclOpaque->right) { arclTmp[crcl].top = pstro->rclBkGround.top; arclTmp[crcl].right = prclOpaque->right; arclTmp[crcl].left = pstro->rclBkGround.right; arclTmp[crcl++].bottom = pstro->rclBkGround.bottom; }
// Bottom fragment:
if (pstro->rclBkGround.bottom < prclOpaque->bottom) { arclTmp[crcl].bottom = prclOpaque->bottom; arclTmp[crcl].left = prclOpaque->left; arclTmp[crcl].right = prclOpaque->right; arclTmp[crcl++].top = pstro->rclBkGround.bottom; }
// Fill any fringe rectangles we found:
if (crcl != 0) { if (jClip == DC_TRIVIAL) { rbc.iSolidColor = xlc.iBackColor; (ppdev->pfnFillSolid)(ppdev, crcl, arclTmp, OVERPAINT, OVERPAINT, rbc, NULL); } else { vClipSolid(ppdev, crcl, arclTmp, xlc.iBackColor, pco); } } }
// We're done with separate opaquing; any further opaquing will
// happen as part of the text drawing.
// Clear the buffer if the text isn't going to set every bit:
bTextPerfectFit = (pstro->flAccel & (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT | SO_MAXEXT_EQUAL_BM_SIDE | SO_CHAR_INC_EQUAL_BM_BASE)) == (SO_ZERO_BEARINGS | SO_FLAG_DEFAULT_PLACEMENT | SO_MAXEXT_EQUAL_BM_SIDE | SO_CHAR_INC_EQUAL_BM_BASE);
if (!bTextPerfectFit) { // Note that we already rounded up to a dword multiple size.
vClearMemDword((ULONG*) so.pvScan0, ulBufferBytes >> 2); }
// Fake up the translate object that will provide the 1bpp
// transfer routine the foreground and background colours:
xlo.pulXlate = (ULONG*) &xlc;
// Draw the text into the temp buffer, and thence to the screen:
do { // Get the next batch of glyphs:
if (pstro->pgp != NULL) { // There's only the one batch of glyphs, so save ourselves
// a call:
pgp = pstro->pgp; cGlyph = pstro->cGlyphs; bMoreGlyphs = FALSE; } else { bMoreGlyphs = STROBJ_bEnum(pstro, &cGlyph, &pgp); }
// LATER: remove double clip intersection from ASM code
if (cGlyph) { prclClip = NULL; prclDraw = &pstro->rclBkGround;
if (jClip == DC_TRIVIAL) {
Output_Text:
vFastText(pgp, cGlyph, so.pvScan0, so.lDelta, pstro->ulCharInc, &pstro->rclBkGround, prclOpaque, flDraw, prclClip, prclExtra);
if (!bMoreGlyphs) { (ppdev->pfnXfer1bpp)(ppdev, 1, prclDraw, OVERPAINT, ulHwBackMix, &so, &gptlZero, &pstro->rclBkGround, &xlo); } } else if (jClip == DC_RECT) { if (bIntersect(&pco->rclBounds, &pstro->rclBkGround, &rclDraw)) { arclTmp[0] = pco->rclBounds; arclTmp[1].bottom = 0; // Terminate list
prclClip = &arclTmp[0]; prclDraw = &rclDraw;
// Save some code size by jumping to the common
// functions calls:
goto Output_Text; } } else // jClip == DC_COMPLEX
{ CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, CD_ANY, 0);
do { bMore = CLIPOBJ_bEnum(pco, sizeof(ce) - sizeof(RECTL), (ULONG*) &ce);
ce.c = cIntersect(&pstro->rclBkGround, ce.arcl, ce.c);
if (ce.c != 0) { ce.arcl[ce.c].bottom = 0; // Terminate list
vFastText(pgp, cGlyph, so.pvScan0, so.lDelta, pstro->ulCharInc, &pstro->rclBkGround, prclOpaque, flDraw, &ce.arcl[0], prclExtra);
if (!bMoreGlyphs) { (ppdev->pfnXfer1bpp)(ppdev, ce.c, &ce.arcl[0], OVERPAINT, ulHwBackMix, &so, &gptlZero, &pstro->rclBkGround, &xlo); } } } while (bMore);
break; } } } while (bMoreGlyphs);
// Free up any memory we allocated for the temp buffer:
if (bTmpAlloc) { EngFreeUserMem(so.pvScan0); }
return(TRUE); }
#endif // defined(i386)
/******************************Public*Routine******************************\
* BOOL DrvTextOut * * If it's the fastest method, outputs text using the 'glyph expansion' * method. Each individual glyph is colour-expanded directly to the * screen from the monochrome glyph bitmap supplied by GDI. * * If it's not the fastest method, calls the routine that implements the * 'buffer expansion' method. * \**************************************************************************/
BOOL DrvTextOut( SURFOBJ* pso, STROBJ* pstro, FONTOBJ* pfo, CLIPOBJ* pco, RECTL* prclExtra, // If we had set GCAPS_HORIZSTRIKE, we would have
// to fill these extra rectangles (it is used
// largely for underlines). It's not a big
// performance win (GDI will call our DrvBitBlt
// to draw the extra rectangles).
RECTL* prclOpaque, BRUSHOBJ* pboFore, BRUSHOBJ* pboOpaque, POINTL* pptlBrush, MIX mix) { PDEV* ppdev; DSURF* pdsurf; OH* poh;
// The DDI spec says we'll only ever get foreground and background
// mixes of R2_COPYPEN:
ASSERTDD(mix == 0x0d0d, "GDI should only give us a copy mix");
// Pass the surface off to GDI if it's a device bitmap that we've
// converted to a DIB:
pdsurf = (DSURF*) pso->dhsurf;
if (pdsurf->dt != DT_DIB) { // We'll be drawing to the screen or an off-screen DFB; copy the
// surface's offset now so that we won't need to refer to the DSURF
// again:
poh = pdsurf->poh; ppdev = (PDEV*) pso->dhpdev;
ppdev->xOffset = poh->x; ppdev->yOffset = poh->y;
// We don't want to use the 'glyph expansion' method, so use
// the 'buffer expansion' method instead:
return(bBufferExpansion(ppdev, pstro, pco, prclExtra, prclOpaque, pboFore, pboOpaque)); } else { // We're drawing to a DFB we've converted to a DIB, so just call GDI
// to handle it:
return(EngTextOut(pdsurf->pso, pstro, pfo, pco, prclExtra, prclOpaque, pboFore, pboOpaque, pptlBrush, mix)); }
return(TRUE); }
/******************************Public*Routine******************************\
* BOOL bEnableText * * Performs the necessary setup for the text drawing subcomponent. * \**************************************************************************/
BOOL bEnableText( PDEV* ppdev) { // Our text algorithms require no initialization. If we were to
// do glyph caching, we would probably want to allocate off-screen
// memory and do a bunch of other stuff here.
return(TRUE); }
/******************************Public*Routine******************************\
* VOID vDisableText * * Performs the necessary clean-up for the text drawing subcomponent. * \**************************************************************************/
VOID vDisableText(PDEV* ppdev) { // Here we free any stuff allocated in 'bEnableText'.
}
/******************************Public*Routine******************************\
* VOID vAssertModeText * * Disables or re-enables the text drawing subcomponent in preparation for * full-screen entry/exit. * \**************************************************************************/
VOID vAssertModeText( PDEV* ppdev, BOOL bEnable) { // If we were to do off-screen glyph caching, we would probably want
// to invalidate our cache here, because it will get destroyed when
// we switch to full-screen.
}
/******************************Public*Routine******************************\
* VOID DrvDestroyFont * * We're being notified that the given font is being deallocated; clean up * anything we've stashed in the 'pvConsumer' field of the 'pfo'. * \**************************************************************************/
VOID DrvDestroyFont(FONTOBJ *pfo) { // This call isn't hooked, so GDI will never call it.
//
// This merely exists as a stub function for the sample multi-screen
// support, so that MulDestroyFont can illustrate how multiple screen
// text supports when the driver caches glyphs. If this driver did
// glyph caching, we might have used the 'pvConsumer' field of the
// 'pfo', which we would have to clean up.
}
|