Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

667 lines
22 KiB

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