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