/**************************************************************************\ * * Copyright (c) 1998-2000 Microsoft Corporation * * Abstract: * * Contains the scan-buffer routines for DCI and GDI. * * Plan: * * !!! [agodfrey] * For batching across primitives, the DpContext code * needs to flush the batch whenever the Context changes state. * * If that isn't feasible for some kinds of state, then EpScanGdiDci * could keep its own DpContext, updated when "context update" records * are inserted into the batch. (This would happen during a call to * Start()). * * Revision History: * * 12/08/1998 andrewgo * Created it. * 01/20/2000 agodfrey * Moved it from Engine\Entry. * 03/23/2000 andrewgo * Integrate DCI and GDI scan cases, for IsMoveSizeActive handling. * 02/22/2000 agodfrey * For ClearType, but also useful for other future improvements: * Expanded the batch structure to allow different types of record. * \**************************************************************************/ #include "precomp.hpp" #include /**************************************************************************\ * * Function Description: * * Downloads the clipping rectangles for the specified window. Updates * internal class clipping variables and returns the window offset to * be used. * * Return Value: * * None * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ VOID EpScanGdiDci::DownloadClipping_Dci( HDC hdc, POINT *clientOffset // In/Out ) { INT i; RECT *rect; HRGN regionHandle = CacheRegionHandle; RGNDATA *data = CacheRegionData; INT size = CacheDataSize; // Query the VisRgn: INT getResult = GetRandomRgn(hdc, regionHandle, SYSRGN); if (getResult == TRUE) { INT newSize = GetRegionData(regionHandle, size, data); // The spec says that GetRegionData returns '1' in the event of // success, but NT returns the actual number of bytes written if // successful, and returns '0' if the buffer wasn't large enough: if ((newSize < 1) || (newSize > size)) { do { newSize = GetRegionData(regionHandle, 0, NULL); // Free the old buffer and allocate a new one: GpFree(data); data = NULL; size = 0; if (newSize < 1) break; data = static_cast(GpMalloc(newSize)); if (data == NULL) break; size = newSize; newSize = GetRegionData(CacheRegionHandle, size, data); // On NT, it's possible due to multithreading that the // regionHandle could have increased in complexity since we // asked for the size. (On Win9x, this isn't possible due // to the fact that BeginAccess acquires the Win16Lock.) // So in the rare case that this might happen, loop again: } while (newSize < size); CacheRegionData = data; CacheDataSize = size; } if (data != NULL) { INT xOffset = clientOffset->x; INT yOffset = clientOffset->y; // Set up some enumeration state: EnumerateCount = data->rdh.nCount; EnumerateRect = reinterpret_cast(&data->Buffer[0]); // Handle our multimon goop: INT screenOffsetX = Device->ScreenOffsetX; INT screenOffsetY = Device->ScreenOffsetY; if ((screenOffsetX != 0) || (screenOffsetY != 0)) { // Adjust for screen offset for the multimon case: xOffset -= screenOffsetX; yOffset -= screenOffsetY; // Adjust and intersect every clip rectangle to account for // this monitor's location: if(Globals::IsNt) { for (rect = EnumerateRect, i = EnumerateCount; i != 0; i--, rect++) { // subtract off the screen origin so we can get // screen relative rectangles. This is only appropriate // on NT - Win9x is window relative. rect->left -= screenOffsetX; rect->right -= screenOffsetX; rect->top -= screenOffsetY; rect->bottom -= screenOffsetY; // Clamp to the screen dimension. if (rect->left < 0) { rect->left = 0; } if (rect->right > Device->ScreenWidth) { rect->right = Device->ScreenWidth; } if (rect->top < 0) { rect->top = 0; } if (rect->bottom > Device->ScreenHeight) { rect->bottom = Device->ScreenHeight; } } } } // On Win9x, GetRandomRgn returns the rectangles in window // coordinates, not screen coordinates, so we adjust them // here: if (!Globals::IsNt) { for (rect = EnumerateRect, i = EnumerateCount; i != 0; rect++, i--) { // Add the screen relative window offset to the rect. // The rect is window relative on Win9x, so this // calculation will give us the screen relative rectangle // we need. rect->left += xOffset; rect->right += xOffset; rect->top += yOffset; rect->bottom += yOffset; // clamp to the screen dimentions. if (rect->left < 0) { rect->left = 0; } if (rect->top < 0) { rect->top = 0; } if (rect->right > Device->ScreenWidth) { rect->right = Device->ScreenWidth; } if (rect->bottom > Device->ScreenHeight) { rect->bottom = Device->ScreenHeight; } } } // Return the offset: clientOffset->x = xOffset; clientOffset->y = yOffset; return; } } // Something failed (could have been a bad 'hdc' specified, or low // memory). As a result we set the clip regionHandle to 'empty': EnumerateCount = 0; } /**************************************************************************\ * * Function Description: * * Sits in a tight loop to read the scan data structures, do any * necessary clipping, and render the result. * * Return Value: * * Points to the queue record it couldn't understand (typically a * header record), or end of the buffer if reached. * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ EpScanRecord* FASTCALL EpScanGdiDci::DrawScanRecords_Dci( BYTE* bits, INT stride, EpScanRecord* record, EpScanRecord* endRecord, INT xOffset, INT yOffset, INT xClipLeft, INT yClipTop, INT xClipRight, INT yClipBottom ) { INT blenderNum; INT x; INT y; INT width; INT xLeft; INT xRight; INT count; INT pixelSize = PixelSize; // Set up the AlphaBlender objects PixelFormatID dstFormat = Surface->PixelFormat; BOOL result = Device->GetScanBuffers( Surface->Width, NULL, NULL, NULL, Buffers); if (result && (dstFormat != PIXFMT_UNDEFINED)) { // Palette and PaletteMap are set up by Start(). // initialize the AlphaBlenders. BlenderConfig[0].Initialize( dstFormat, Context, Context->Palette ? Context->Palette : Device->Palette, Buffers, TRUE, TRUE, SolidColor ); BlenderConfig[1].Initialize( dstFormat, Context, Context->Palette ? Context->Palette : Device->Palette, Buffers, TRUE, TRUE, SolidColor ); } else { ONCE(WARNING(("DrawScanRecords_Dci: Unrecognized pixel format"))); return endRecord; } INT ditherOriginX = DitherOriginX; INT ditherOriginY = DitherOriginY; do { // SrcOver_Gdi_ARGB assumes that if the format is lower than 8bpp, // then the DIBSection format is 8bpp. // Bug 310285: This assert is disabled and should be reinvestigated // for v2. It is likely the Surface pointer needs to be changed // to the real surface that's being rendered. //ASSERT( (Surface->PixelFormat == PixelFormatUndefined) // || (GetPixelFormatSize(Surface->PixelFormat) >= 8) // || (dstFormat == PixelFormat8bppIndexed)); blenderNum = record->BlenderNum; ASSERT(record->GetScanType() == BlenderConfig[blenderNum].ScanType); x = record->X + xOffset + BatchOffsetX; y = record->Y + yOffset + BatchOffsetY; width = record->Width; INT recordFormatSize = GetPixelFormatSize(BlenderConfig[blenderNum].SourcePixelFormat) >> 3; if ((y >= yClipTop) && (y < yClipBottom)) { xRight = x + width; if (xRight > xClipRight) xRight = xClipRight; xLeft = x; if (xLeft < xClipLeft) xLeft = xClipLeft; count = xRight - xLeft; if (count > 0) { BYTE *src = NULL; BYTE *ctBuffer = NULL; EpScanType scanType = record->GetScanType(); if (scanType != EpScanTypeCTSolidFill) { src = reinterpret_cast(record->GetColorBuffer()); src += (xLeft - x)*recordFormatSize; } if ( (scanType == EpScanTypeCT) || (scanType == EpScanTypeCTSolidFill)) { ctBuffer = record->GetCTBuffer( GetPixelFormatSize(BlenderConfig[0].SourcePixelFormat) >> 3 ); ctBuffer += (xLeft - x); } BYTE *dst = bits + (y * stride) + (xLeft * pixelSize); BlenderConfig[blenderNum].AlphaBlender.Blend( dst, src, count, xLeft - ditherOriginX, y - ditherOriginY, ctBuffer ); } } // Advance to the next record: record = record->NextScanRecord(recordFormatSize); } while (record < endRecord); return(record); } /**************************************************************************\ * * Function Description: * * Processes all the data in the queue. * * Note that it does not empty the queue; the caller is responsible. * * Return Value: * * None * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ VOID EpScanGdiDci::ProcessBatch_Dci( HDC hdc, // Only used to query window offset and clipping EpScanRecord *buffer, EpScanRecord *bufferEnd ) { INT i; RECT *clipRect; EpScanRecord *nextBuffer; POINT clientOffset; POINT duplicateOffset; INT result; while (TRUE) { // Peek at the window offset so that we can specify the bounds on // the DCI lock properly. Note that the window offset may change // between the time we do this peak, and the time we do the // BeginAccess. // // GetDCOrgEx may fail if the DC is bad, in which case we shouldn't // draw anything: if (!GetDCOrgEx(hdc, &clientOffset)) { return; } // Adjust for the monitor's offset: INT xOffset = clientOffset.x - Device->ScreenOffsetX; INT yOffset = clientOffset.y - Device->ScreenOffsetY; // Clip to the surface bounds (multi-mon might cause the bounds // to be bigger than our surface): INT x = max(MinX + xOffset, 0); INT y = max(MinY + yOffset, 0); // MaxY is inclusive: INT width = min(MaxX + xOffset, Device->ScreenWidth) - x; INT height = min(MaxY + yOffset + 1, Device->ScreenHeight) - y; if ((width <= 0) || (height <= 0)) { return; } // Acquire the DCI lock: result = Globals::DciBeginAccessFunction( DciSurface, x, y, width, height ); if (result < DCI_OK) { // The DCI lock failed. There are really two possible reasons: // // 1. There was a mode change; // 2. The system at some point switched to a secure desktop // (such as by Ctrl-Alt-Del or by a screen saver) on NT. // // For the former case, we get a WM_DISPLAYCHANGED notification // message, and we have code that recreates all the GDI+ surface // representations (because a color-depth change happened, or // the multi-mon configuration might have changed, and we can't // recover from either of those this deep in the rendering // pipeline). // // For the second case, we get no notification other than the // DCI lock failure. So for this case, we try to reinitialize // our DCI state right here. if (!Reinitialize_Dci()) { return; } result = Globals::DciBeginAccessFunction( DciSurface, x, y, width, height ); } // If we failed to get a DCI lock, don't bother processing any // of the queue. We're outta here: if (result < DCI_OK) { return; } // Check the window offset again and verify that it's still // the same as the value we used to compute the lock rectangle: GetDCOrgEx(hdc, &duplicateOffset); if ((duplicateOffset.x == clientOffset.x) && (duplicateOffset.y == clientOffset.y)) { break; } // The window moved between the time we computed the // DCI lock area and the time we actually did the DCI // lock. Unlock and repeat: Globals::DciEndAccessFunction(DciSurface); } // Every time we acquire the DCI lock, we have to requery the // clipping. // // Now that we've acquired the DCI lock (or we failed to acquire // it but won't actually draw anything), then it's safe to // download the clipping, because it won't change until we do // the DCI unlock: DownloadClipping_Dci(hdc, &clientOffset); // Copy the data to the surface: BYTE *bits = reinterpret_cast(DciSurface->dwOffSurface); INT stride = DciSurface->lStride; // We don't have to do any rendering when the clipping is empty: if (EnumerateCount != 0) { while (buffer < bufferEnd) { // Redraw each scan buffer once for every clip rectangle: i = EnumerateCount; clipRect = EnumerateRect; do { nextBuffer = DrawScanRecords_Dci( bits, stride, buffer, bufferEnd, clientOffset.x, clientOffset.y, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom ); } while (clipRect++, --i); buffer = nextBuffer; } } // Unlock the primary: Globals::DciEndAccessFunction(DciSurface); } /**************************************************************************\ * * Function Description: * * Try to re-initialize DCI after a Lock failure (as may be caused by * a switch to a secure desktop). This will succeed only if the mode * is exactly the same resolution and color depth as it was before * (otherwise our clipping or halftone or whatever would be wrong if * we continued). * * Return Value: * * TRUE if successfully re-initialized; FALSE if not (with the side * effect that we switch to using GDI). * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ BOOL EpScanGdiDci::Reinitialize_Dci() { ASSERT(Status == GdiDciStatus_UseDci); DWORD oldBitCount = DciSurface->dwBitCount; DWORD oldWidth = DciSurface->dwWidth; DWORD oldHeight = DciSurface->dwHeight; Globals::DciDestroyFunction(DciSurface); DciSurface = NULL; if (Globals::DciCreatePrimaryFunction(Device->DeviceHdc, &DciSurface) == DCI_OK) { if ((DciSurface->dwBitCount == oldBitCount) && (DciSurface->dwWidth == oldWidth) && (DciSurface->dwHeight == oldHeight)) { return(TRUE); } } // Uh oh, we failed to recreate exactly the same surface. Switch // over to using GDI: Status = GdiDciStatus_UseGdi; return(FALSE); } /**************************************************************************\ * * Function Description: * * Return the PixelFormatID for the given DCI surface. * * Added because of bug #96879 - when I fixed it, I found that * DCI would then succeed on the ATI Mach 64 GX's * non-standard 32bpp mode, and we were happily trying to draw it * (and getting our colors mixed up). * * Arguments: * * si - The DCISURFACEINFO to examine * * Return Value: * * The PixelFormatID. * * History: * * 09/10/2000 agodfrey * Created it. * \**************************************************************************/ static PixelFormatID ExtractPixelFormatFromDCISurface( DCISURFACEINFO *si ) { // 8bpp if (si->dwBitCount == 8) { return PixelFormat8bppIndexed; } // 24bpp RGB and 32bpp RGB if ((si->dwMask[0] == 0x00ff0000) && (si->dwMask[1] == 0x0000ff00) && (si->dwMask[2] == 0x000000ff)) { if (si->dwBitCount == 24) { return PixelFormat24bppRGB; } else if (si->dwBitCount == 32) { return PixelFormat32bppRGB; } } // 16bpp 555 if ((si->dwMask[0] == 0x00007c00) && (si->dwMask[1] == 0x000003e0) && (si->dwMask[2] == 0x0000001f) && (si->dwBitCount == 16)) { return PixelFormat16bppRGB555; } // 16bpp 565 if ((si->dwMask[0] == 0x0000f800) && (si->dwMask[1] == 0x000007e0) && (si->dwMask[2] == 0x0000001f) && (si->dwBitCount == 16)) { return PixelFormat16bppRGB565; } // Unsupported format return PixelFormatUndefined; } /**************************************************************************\ * * Function Description: * * Does all the initialization needed for DCI. * * Return Value: * * None * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ VOID EpScanGdiDci::LazyInitialize_Dci() { // If a mirror driver is active, never use DCI: if (Globals::IsMirrorDriverActive || Globals::g_fAccessibilityPresent) { Status = GdiDciStatus_UseGdi; return; } // Use the LoadLibrary critical section to protect access // to our global variables. LoadLibraryCriticalSection llcs; // We'll lose memory if we're called more than once: ASSERT(Status == GdiDciStatus_TryDci); // DCIMAN32 exists on all versions of Win9x and NT4 and newer. if (Globals::DcimanHandle == NULL) { // Note: [minliu: 12/18/2000] The reason I added this following line // here: // 1) This is Office bug #300329 // 2) The basic problem is that on Windows 98, with ATI Fury Pro/Xpert // 2000 Pro (English) card, with latest display driver, // wme_w98_r128_4_12_6292.exe. The floating point control state get // changed after we call LoadLibraryA("dciman32.dll"). Apparently the // display driver changes it. We are going to reporting this problem to // ATI Tech. In the meantime we need to check this in so that // PowerPointer can be launched on that machine FPUStateSandbox fps; HMODULE module = LoadLibraryA("dciman32.dll"); if (module) { Globals::DciEndAccessFunction = (DCIENDACCESSFUNCTION) GetProcAddress(module, "DCIEndAccess"); Globals::DciBeginAccessFunction = (DCIBEGINACCESSFUNCTION) GetProcAddress(module, "DCIBeginAccess"); Globals::DciDestroyFunction = (DCIDESTROYFUNCTION) GetProcAddress(module, "DCIDestroy"); Globals::DciCreatePrimaryFunction = (DCICREATEPRIMARYFUNCTION) GetProcAddress(module, "DCICreatePrimary"); if ((Globals::DciEndAccessFunction != NULL) && (Globals::DciBeginAccessFunction != NULL) && (Globals::DciDestroyFunction != NULL) && (Globals::DciCreatePrimaryFunction != NULL)) { Globals::DcimanHandle = module; } else { // It failed, so free the library. FreeLibrary(module); } } } if (Globals::DcimanHandle != NULL) { if (Globals::DciCreatePrimaryFunction(Device->DeviceHdc, &DciSurface) == DCI_OK) { // Check that the format is one we can handle natively. if (EpAlphaBlender::IsSupportedPixelFormat( ExtractPixelFormatFromDCISurface(DciSurface))) { PixelSize = DciSurface->dwBitCount >> 3; CacheRegionHandle = CreateRectRgn(0, 0, 0, 0); if (CacheRegionHandle) { // Okay, initialize the whole class: // We're all set to use DCI: Status = GdiDciStatus_UseDci; return; } } Globals::DciDestroyFunction(DciSurface); DciSurface = NULL; } } // Darn, we can't use DCI. Status = GdiDciStatus_UseGdi; } /**************************************************************************\ * * Function Description: * * Handles a SrcOver call via GDI routines, making sure to do the * smallest GDI calls possible * * Return Value: * * None * * History: * * 03/22/2000 andrewgo * Created it. * \**************************************************************************/ static BOOL OptimizeRuns(ARGB *src, INT width) { if (width <= 8) return TRUE; BYTE * alpha = reinterpret_cast(src) + 3; UINT numberOfRuns = 0; BOOL inRun = FALSE; for (INT pos = 0; pos < width; ++pos, alpha += 4) { if (static_cast(*alpha + 1) <= 1) { if (inRun) { ++numberOfRuns; if (numberOfRuns > 4) return TRUE; inRun = FALSE; } } else { inRun = TRUE; } } return FALSE; } // OptimizeRuns VOID EpScanGdiDci::SrcOver_Gdi_ARGB( HDC destinationHdc, HDC dibSectionHdc, VOID *dibSection, EpScanRecord *scanRecord ) { BYTE* alpha; INT left; INT right; INT bltWidth; VOID *src = NULL; BYTE *ctBuffer = NULL; EpScanType scanType = scanRecord->GetScanType(); if (scanType != EpScanTypeCTSolidFill) { src = scanRecord->GetColorBuffer(); } if ( (scanType == EpScanTypeCT) || (scanType == EpScanTypeCTSolidFill)) { ctBuffer = scanRecord->GetCTBuffer( GetPixelFormatSize(BlenderConfig[0].SourcePixelFormat) >> 3 ); } INT x = scanRecord->X; INT y = scanRecord->Y; INT width = scanRecord->Width; if (GetPixelFormatSize(Surface->PixelFormat) < 8) { // [agodfrey]: #98904 - we hit an assert or potential AV in // Convert_8_sRGB, because the buffer contains values that // are out-of-range of the palette. To fix this, zero the // temporary buffer whenever we're in less than 8bpp mode. GpMemset(dibSection, 0, width); } BOOL optimizeStretchBlt = FALSE; if ( (BlenderConfig[0].ScanType == EpScanTypeCT) || (BlenderConfig[0].ScanType == EpScanTypeCTSolidFill)) optimizeStretchBlt = TRUE; else optimizeStretchBlt = OptimizeRuns( reinterpret_cast(src), width ); if (optimizeStretchBlt) { StretchBlt(dibSectionHdc, 0, 0, width, 1, destinationHdc, x, y, width, 1, SRCCOPY); } else { ASSERT(src != NULL); // We discovered on NT5 that some printer drivers will fall over // if we ask to blt from their surface. Consequently, we must // ensure we never get into this code path for printers! // [ericvan] This improperly asserts on compatible printer DC's // [agodfrey] No, see Start(). // ASSERT(GetDeviceCaps(destinationHdc, TECHNOLOGY) != DT_RASPRINTER); // Only read those pixels that we have to read. In benchmarks // where we're going through GDI to the screen or to a compatible // bitmap, we're getting killed by our per-pixel read costs. // Terminal Server at least has the 'frame buffer' sitting in // system memory, but with NetMeeting we still have to read from // video memory. // // Unfortunately, the per-call overhead of StretchBlt is fairly // high (but not too bad when we're using a DIB-section - it // beats the heck out of StretchDIBits). alpha = reinterpret_cast(src) + 3; right = 0; while (TRUE) { // Find the first translucent pixel: left = right; while ((left < width) && (static_cast(*alpha + 1) <= 1)) { left++; alpha += 4; } // If there are no more runs of translucent pixels, // we're done: if (left >= width) break; // Now find the next completely transparent or opaque // pixel: right = left; while ((right < width) && (static_cast(*alpha + 1) > 1)) { right++; alpha += 4; } bltWidth = right - left; // BitBlt doesn't work on Multimon on Win2K when the destinationHdc // is 8bpp. But StretchBlt does work. StretchBlt(dibSectionHdc, left, 0, bltWidth, 1, destinationHdc, x + left, y, bltWidth, 1, SRCCOPY); } } // Do the blend: BlenderConfig[scanRecord->BlenderNum].AlphaBlender.Blend( dibSection, src, width, x - DitherOriginX, y - DitherOriginY, ctBuffer ); if (optimizeStretchBlt) { StretchBlt(destinationHdc, x, y, width, 1, dibSectionHdc, 0, 0, width, 1, SRCCOPY); } else { // Write the portions that aren't completely transparent back // to the screen: alpha = reinterpret_cast(src) + 3; right = 0; while (TRUE) { // Find the first non-transparent pixel: left = right; while ((left < width) && (*alpha == 0)) { left++; alpha += 4; } // If there are no more runs of non-transparent pixels, // we're done: if (left >= width) break; // Now find the next completely transparent pixel: right = left; while ((right < width) && (*alpha != 0)) { right++; alpha += 4; } bltWidth = right - left; // BitBlt doesn't work on Multimon on Win2K when the // destinationHdc is 8bpp. But StretchBlt does work. StretchBlt(destinationHdc, x + left, y, bltWidth, 1, dibSectionHdc, left, 0, bltWidth, 1, SRCCOPY); } } } /**************************************************************************\ * * Function Description: * * Processes all the data in the queue and resets it to be empty. * * Return Value: * * None * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ VOID EpScanGdiDci::ProcessBatch_Gdi( HDC destinationHdc, EpScanRecord *buffer, EpScanRecord *bufferEnd ) { ULONG type; INT x; INT y; INT width; VOID *dibSection; HDC dibSectionHdc; // Set up the AlphaBlender objects PixelFormatID dstFormat; // We must never be called with an empty batch. ASSERT(MaxX >= MinX); // We should be using Surface->Width as an upper bound on // our internal blending buffer, but because of other bugs // we're potentially using the wrong Surface here. // Instead we use the width of the bounding rectangle for // all the spans in the batch. BOOL result = Device->GetScanBuffers( //Surface->Width, MaxX - MinX, &dibSection, &dibSectionHdc, &dstFormat, Buffers ); if (result && (dstFormat != PIXFMT_UNDEFINED)) { // Palette and PaletteMap are set up by Start(). // initialize the AlphaBlenders. BlenderConfig[0].Initialize( dstFormat, Context, Context->Palette ? Context->Palette : Device->Palette, Buffers, TRUE, TRUE, SolidColor ); BlenderConfig[1].Initialize( dstFormat, Context, Context->Palette ? Context->Palette : Device->Palette, Buffers, TRUE, TRUE, SolidColor ); } else { ONCE(WARNING(("EmptyBatch_Gdi: Unrecognized pixel format"))); return; } INT ditherOriginX = DitherOriginX; INT ditherOriginY = DitherOriginY; do { INT blenderNum = buffer->BlenderNum; x = buffer->X + BatchOffsetX; y = buffer->Y + BatchOffsetY; width = buffer->Width; // This must never happen. If it does, we are going to write off // the end of our DIBSection and blending buffers. On win9x this will // overwrite some stuff in GDI and bring down the entire system. // On NT we'll AV and bring down the app. ASSERT(width <= Device->BufferWidth); EpScanType scanType = buffer->GetScanType(); if (scanType != EpScanTypeOpaque) { SrcOver_Gdi_ARGB( destinationHdc, dibSectionHdc, dibSection, buffer ); } else { ASSERT(scanType == EpScanTypeOpaque); // Do the copy: BlenderConfig[blenderNum].AlphaBlender.Blend( dibSection, buffer->GetColorBuffer(), width, x - ditherOriginX, y - ditherOriginY, NULL ); // Write the result back to the screen: StretchBlt(destinationHdc, x, y, width, 1, dibSectionHdc, 0, 0, width, 1, SRCCOPY); } // Advance to the next buffer: buffer = buffer->NextScanRecord( GetPixelFormatSize(BlenderConfig[blenderNum].SourcePixelFormat) >> 3 ); } while (buffer < bufferEnd); } /**************************************************************************\ * * Function Description: * * Takes a batch as input and calls the internal flush * mechanism to process it after which it restores the * internal state. * * Notes * * This routine can handle multiple pixel formats with different * SourceOver or SourceCopy combinations. * * Return Value: * * TRUE * * History: * * 5/4/2000 asecchia * Created it. * \**************************************************************************/ BOOL EpScanGdiDci::ProcessBatch( EpScanRecord *batchStart, EpScanRecord *batchEnd, INT minX, INT minY, INT maxX, INT maxY ) { // NOTE: From the comments for class EpScan: // NOTE: These classes are not reentrant, and therefore cannot be used // by more than one thread at a time. In actual use, this means // that their use must be synchronized under the device lock. // Flush the batch Flush(); // Save the buffers EpScanRecord *bs = BufferStart; // Points to queue buffer start EpScanRecord *be = BufferEnd; // Points to end of queue buffer EpScanRecord *bc = BufferCurrent; // Points to current queue position INT size = BufferSize; // Size of queue buffer in bytes // Set up the buffers for the new batch. BufferStart = batchStart; BufferEnd = batchEnd; BufferCurrent = batchEnd; // note this implies that the buffer is not larger than MAXINT // !!! [asecchia] not sure if the Flush needs this to be set. BufferSize = (INT)((INT_PTR)batchEnd - (INT_PTR)batchStart); // Set the bounds: // Don't need to save the old bounds because the flush will reset them. MinX = minX; MinY = minY; MaxX = maxX; MaxY = maxY; // Set the batch offset to the drawing offset. BatchOffsetX = minX; BatchOffsetY = minY; // Flush the batch. Flush(); // Restore the buffers. BufferStart = bs; BufferEnd = be; BufferCurrent = bc; BufferSize = size; BatchOffsetX = 0; BatchOffsetY = 0; return TRUE; } /**************************************************************************\ * * Function Description: * * Instantiates a scan instance for rendering to the screen via either * DCI or GDI. * * Return Value: * * FALSE if all the necessary buffers couldn't be created * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ BOOL EpScanGdiDci::Start( DpDriver *driver, DpContext *context, DpBitmap *surface, NEXTBUFFERFUNCTION *nextBuffer, EpScanType scanType, PixelFormatID pixFmtGeneral, PixelFormatID pixFmtOpaque, ARGB solidColor ) { // Inherit initialization // This sets up BlenderConfig[]. Mostly, these will be used at the time // we flush the batch. But BlenderConfig[0].ScanType is also used in // GetCurrentCTBuffer(). EpScan::Start( driver, context, surface, nextBuffer, scanType, pixFmtGeneral, pixFmtOpaque, solidColor ); BOOL bRet = TRUE; // Record the solid color for later SolidColor = solidColor; // The ScanBuffer and EpScan classes _MUST_ be protected by // the Device Lock - if you hit this ASSERT, go and acquire // the Devlock before entering the driver. // EpScanGdiDci is used exclusively for drawing to the screen, therefore // we assert on the DesktopDriver Device being locked. If you're locking // some other device, that's also a bug - use a different EpScan class. ASSERT(Globals::DesktopDriver->Device->DeviceLock.IsLockedByCurrentThread()); // First check to see if we have something in the queue for a different // Graphics (which can happen with multiple threads drawing to different // windows, since we only have one queue): if ((context != Context) || (surface != Surface)) { // Check to see if we have to do a lazy initialize: if (Status == GdiDciStatus_TryDci) { LazyInitialize_Dci(); } EmptyBatch(); // We stash away a pointer to the context. Note that the GpGraphics // destructor always calls us to flush, thus ensuring that we // won't use a stale Context pointer in the EmptyBatch call above. Context = context; Surface = surface; } GpCompositingMode compositingMode = context->CompositingMode; // !!![andrewgo] We discovered that NT will fall over with some printer // drivers if we ask to read from the printer surface. // But we should never be hitting the scan interface in // the printer case! (Scans are too much overhead, and // alpha should be handled via screen-door anyway.) // [ericvan] This improperly asserts on compatible printer DC's // [agodfrey] No, we should have a separate scan class for that case // - one which doesn't batch up the scans, and doesn't try to // use DCI. Printer DC's also shouldn't have a pointer // to the desktop device. // If this is fixed, the assertion below can be reenabled. //ASSERT(GetDeviceCaps(Context->Hdc, TECHNOLOGY) != DT_RASPRINTER); // GDI and DCI destinations don't have an alpha channel: ASSERT(surface->SurfaceTransparency == TransparencyNoAlpha); // Allocate our queue buffer, if necessary: // This makes some assumptions: // 1) The records in blender 0 are bigger than records in blender 1. // 2) The biggest color buffer format is 4 bytes per pixel. EpScanRecord *maxRecordEnd = EpScanRecord::CalculateNextScanRecord( BufferCurrent, BlenderConfig[0].ScanType, surface->Width, 4); INT_PTR requiredSize = reinterpret_cast(maxRecordEnd) - reinterpret_cast(BufferCurrent); if (requiredSize > BufferSize) { // If we need to resize, it follows that there should be nothing // sitting in the queue for this surface: // // [agodfrey] It does? More explanation needed. ASSERT(BufferCurrent == BufferStart); // Free the old queue and allocate a new one: GpFree(BufferMemory); // Scan records are much smaller than 2GB, so 'requiredSize' will fit // into an INT. ASSERT(requiredSize < INT_MAX); BufferSize = (INT)max(requiredSize, SCAN_BUFFER_SIZE); // We may need up to 7 extra bytes in order to QWORD align BufferStart. BufferMemory = GpMalloc(BufferSize+7); if (BufferMemory == NULL) { BufferSize = 0; bRet = FALSE; return bRet; } BufferStart = MAKE_QWORD_ALIGNED(EpScanRecord *, BufferMemory); // Make sure that we didn't overstep the 7 // padding bytes in the allocation. ASSERT(((INT_PTR) BufferStart) - ((INT_PTR) BufferMemory) <= 7); BufferEnd = reinterpret_cast (reinterpret_cast(BufferStart) + BufferSize); BufferCurrent = BufferStart; } *nextBuffer = (NEXTBUFFERFUNCTION) EpScanGdiDci::NextBuffer; // Cache the translation vector and palette for the device. We only // need to do so in 8bpp mode. // // Update the color palette and palette map if necessary. if (Device->Palette != NULL) { // Grab the DC just for the purposes of looking at the palette // selected: HDC destinationHdc = context->GetHdc(surface); EpPaletteMap *paletteMap = context->PaletteMap; if (paletteMap != NULL) { // IsValid() check isn't necessary because if we are in an // invalid state, we may be able to get out of it in // UpdateTranslate() if (paletteMap->GetUniqueness() != Globals::PaletteChangeCount) { paletteMap->UpdateTranslate(destinationHdc); paletteMap->SetUniqueness(Globals::PaletteChangeCount); } } else { paletteMap = new EpPaletteMap(destinationHdc); if (paletteMap != NULL) { paletteMap->SetUniqueness(Globals::PaletteChangeCount); // This is very silly, but we must update this map to // the entire DpContext chain... // !!![andrewgo] Is this thread safe? // !!![andrewgo] Is this stuff cleaned up? // !!![andrewgo] This all looks really wrong... DpContext* curContext = Context; while (curContext != NULL) { curContext->PaletteMap = paletteMap; curContext = curContext->Prev; } } else { bRet = FALSE; } } context->ReleaseHdc(destinationHdc); } return bRet; } /**************************************************************************\ * * Function Description: * * Processes all the data in the queue and resets it to be empty. * * Return Value: * * None * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ VOID EpScanGdiDci::EmptyBatch() { // Watch out for an empty batch (which can happen with Flush or with // the first allocation of the queue buffer): if (BufferCurrent != BufferStart) { // If we're emptying a non-empty batch, it follows that we // should no longer be unsure whether we'll be using DCI or GDI: ASSERT(Status != GdiDciStatus_TryDci); // Remember where we are in the queue: EpScanRecord *bufferStart = BufferStart; EpScanRecord *bufferEnd = BufferCurrent; // Reset the queue before doing anything else, in case whatever // we're doing causes us to force a 'surface->Flush()' call // (which would re-enter this routine): BufferCurrent = BufferStart; // Use DCI to process the queue if DCI was successfully enabled. // // On NT we also add the weird condition that we won't invoke // DCI if the user is actively moving a window around. The // reason is that NT is forced to repaint the whole desktop if // the Visrgn for any window changes while a DCI primary surface // lock is held (this is how NT avoids having something like // the Win16Lock, which would let a user-mode app prevent windows // from moving, an obvious robustness issue). // // Note that the 'IsMoveSizeActive' thing is not a fool-proof // solution for avoiding whole-desktop repaints (since there's // a chance the user could still move a window while we're // in ProcessBatch_Dci), but as a heuristic it works quite well. // // To summarize, drop through to GDI rendering codepath if any // of the following conditions are TRUE: // // ICM required // Thus we must go through GDI to use ICM2.0 support. // // DCI disabled // We have no choice but to fallback to GDI. // // GDI layering // GDI layering means that GDI is hooking rendering to the // screen and invisibly redirecting output to a backing store. // Thus, the actual rendering surface is inaccessible via DCI // and we must fallback to GDI. // // Window move or resize processing // To prevent excessive repainting if ((Context->IcmMode != IcmModeOn) && (Context->GdiLayered == FALSE) && (Status == GdiDciStatus_UseDci) && (!Globals::IsMoveSizeActive) && (!Globals::g_fAccessibilityPresent)) { // If the Graphics was derived using an Hwnd, we have to use that // to first get an HDC which we can query for clipping. // // Note that we don't need a 'clean' DC in order to query the // clipping, so we don't call GetHdc() if we already have a DC // hanging around: HDC hdc = Context->Hdc; if (Context->Hwnd != NULL) { hdc = Context->GetHdc(Surface); } ProcessBatch_Dci(hdc, bufferStart, bufferEnd); if (Context->Hwnd != NULL) { Context->ReleaseHdc(hdc, Surface); } } else { // We need a clean DC if we're going to draw using GDI: HDC hdc = Context->GetHdc(Surface); ProcessBatch_Gdi(hdc, bufferStart, bufferEnd); Context->ReleaseHdc(hdc); } // Reset our bounds. MinX = INT_MAX; MinY = INT_MAX; MaxX = INT_MIN; MaxY = INT_MIN; } } /**************************************************************************\ * * Function Description: * * Flushes any buffers in the DCI queue. Note that the DCI queue can * be accumulated over numerous API calls without flushing, which forces * us to expose a Flush mechanism to the application. * * Return Value: * * None * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ VOID EpScanGdiDci::Flush() { // Note that we might be called to Flush even before we've // initialized DCI: EmptyBatch(); } /**************************************************************************\ * * Function Description: * * Ends the previous buffer (if there was one), and returns the * next buffer for doing a SrcOver blend. * * Return Value: * * Points to the resulting scan buffer * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ VOID *EpScanGdiDci::NextBuffer( INT x, INT y, INT nextWidth, INT currentWidth, INT blenderNum ) { ASSERT(nextWidth >= 0); ASSERT(currentWidth >= 0); // Avoid pointer aliasing by loading up a local copy: EpScanRecord *bufferCurrent = BufferCurrent; // The first call that a drawing routine makes to us always has // a 'currentWidth' of 0 (since it doesn't have a 'current' // buffer yet): if (currentWidth != 0) { // Accumulate the bounds using the final, completed scan: INT xCurrent = bufferCurrent->X; MinX = min(MinX, xCurrent); MaxX = max(MaxX, xCurrent + currentWidth); INT yCurrent = bufferCurrent->Y; MinY = min(MinY, yCurrent); MaxY = max(MaxY, yCurrent); // Complete the previous scan request. // Now that we know how much the caller actually wrote into // the buffer, update the width in the old record and advance // to the next: bufferCurrent->Width = currentWidth; bufferCurrent = bufferCurrent->NextScanRecord( GetPixelFormatSize( BlenderConfig[bufferCurrent->BlenderNum].SourcePixelFormat ) >> 3 ); // Don't forget to update the class version: BufferCurrent = bufferCurrent; } // From here on, the code is operating on the current scan request // I.e. bufferCurrent applies to the current scan - it has been updated // and no longer refers to the last scan. // See if there's room in the buffer for the next scan: // bufferCurrent is not initialized for this scan yet, so we can't rely // on it having a valid PixelFormat - we need to get the PixelFormat // from the context of the call. PixelFormatID pixFmt = BlenderConfig[blenderNum].SourcePixelFormat; EpScanRecord* scanEnd = EpScanRecord::CalculateNextScanRecord( bufferCurrent, BlenderConfig[blenderNum].ScanType, nextWidth, GetPixelFormatSize(pixFmt) >> 3 ); if (scanEnd > BufferEnd) { EmptyBatch(); // Reload our local variable: bufferCurrent = BufferCurrent; } // Remember the x and y for the brush offset (halftone & dither). // Note: We do not have to remember x and y in CurrentX and CurrentY // because we remember them in bufferCurrent (below). // CurrentX = x; // CurrentY = y; // Initialize the bufferCurrent. // We initialize everything except the width - which we don't know till // the caller is done and we get called for the subsequent scan. bufferCurrent->SetScanType(BlenderConfig[blenderNum].ScanType); bufferCurrent->SetBlenderNum(blenderNum); bufferCurrent->X = x; bufferCurrent->Y = y; bufferCurrent->OrgWidth = nextWidth; // Note: we don't actually use LastBlenderNum in the EpScanGdiDci // See EpScanEngine for a class that uses it. // LastBlenderNum = blenderNum; return bufferCurrent->GetColorBuffer(); } /**************************************************************************\ * * Function Description: * * Denotes the end of the use of the scan buffer for this API call. * Note that this does not force a flush of the buffer. * * Arguments: * * NONE * * Return Value: * * NONE * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ VOID EpScanGdiDci::End( INT updateWidth ) { // Get the last record into the queue: NextBuffer(0, 0, 0, updateWidth, 0); // Note that we do not flush the buffer here for DCI! This is VERY // INTENDED, to allow spans to be batched across primitives. In fact, // THAT'S THE WHOLE POINT OF THE BATCHING! // !!![andrewgo] Actually, our scan architecture needs to be fixed // to allow this for the GDI cases too. If I don't // flush here for the GDI case, we die running // Office CITs in Graphics::GetHdc when we do the // EmptyBatch, because there's a stale Context in // the non-empty batch buffer from previous drawing // that didn't get flushed on ~GpGraphics because // the driver didn't pass the Flush through to the // scan class! // if (Status != GdiDciStatus_UseDci) { EmptyBatch(); } } /**************************************************************************\ * * Function Description: * * Constructor for all GDI/DCI drawing. * * Return Value: * * None * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ EpScanGdiDci::EpScanGdiDci(GpDevice *device, BOOL tryDci) { Device = device; Status = (tryDci) ? GdiDciStatus_TryDci : GdiDciStatus_UseGdi; Context = NULL; Surface = NULL; BufferSize = 0; BufferMemory = NULL; BufferCurrent = NULL; BufferStart = NULL; BufferEnd = NULL; CacheRegionHandle = NULL; CacheRegionData = NULL; CacheDataSize = 0; MinX = INT_MAX; MinY = INT_MAX; MaxX = INT_MIN; MaxY = INT_MIN; BatchOffsetX = 0; BatchOffsetY = 0; } /**************************************************************************\ * * Function Description: * * Destructor for the GDI/DCI interface. Typically only called when * the 'device' is destroyed. * * Return Value: * * None * * History: * * 04/04/1999 andrewgo * Created it. * \**************************************************************************/ EpScanGdiDci::~EpScanGdiDci() { if (Status == GdiDciStatus_UseDci) { // DciDestroy doesn't do anything if 'DciSurface' is NULL: Globals::DciDestroyFunction(DciSurface); } DeleteObject(CacheRegionHandle); GpFree(CacheRegionData); GpFree(BufferMemory); }