Leaked source code of windows server 2003
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.
 
 
 
 
 
 

1919 lines
55 KiB

/**************************************************************************\
*
* 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 <limits.h>
/**************************************************************************\
*
* 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<RGNDATA*>(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<RECT*>(&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<BYTE*>(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<BYTE*>(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<BYTE*>(src) + 3;
UINT numberOfRuns = 0;
BOOL inRun = FALSE;
for (INT pos = 0; pos < width; ++pos, alpha += 4)
{
if (static_cast<BYTE>(*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<ARGB *>(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<BYTE*>(src) + 3;
right = 0;
while (TRUE)
{
// Find the first translucent pixel:
left = right;
while ((left < width) && (static_cast<BYTE>(*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<BYTE>(*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<BYTE*>(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<BYTE *>(maxRecordEnd) -
reinterpret_cast<BYTE *>(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<EpScanRecord *>
(reinterpret_cast<BYTE*>(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);
}