Windows2003-3790/windows/advcore/duser/engine/services/buffer.cpp

1587 lines
44 KiB
C++
Raw Normal View History

2001-01-01 00:00:00 +01:00
/***************************************************************************\
*
* File: Buffer.cpp
*
* Description:
* Buffer.cpp implementats objects used in buffering operations, including
* double buffering, DX-Transforms, etc. These objects are maintained by a
* central BufferManager that is available process-wide.
*
*
* History:
* 1/18/2000: JStall: Created
*
* Copyright (C) 2000 by Microsoft Corporation. All rights reserved.
*
\***************************************************************************/
#include "stdafx.h"
#include "Services.h"
#include "Buffer.h"
#include "FastDib.h"
#include "GdiCache.h"
#include "OSAL.h"
#include "ResourceManager.h"
#include "Surface.h"
#include "Context.h"
#define DEBUG_COPYTOCLIPBOARD 0 // Copy buffer to clipboard
#define DEBUG_DUMPREGION 0 // Dump regions
#if DEBUG_DUMPREGION
#define DUMP_REGION(name, rgn)
DumpRegion(name, rgn)
void
DumpRegion(LPCTSTR pszName, HRGN hrgn)
{
RECT rc;
int nType = GetRgnBox(hrgn, &rc);
switch (nType)
{
case NULLREGION:
Trace("Null region %s = 0x%p\n", pszName, hrgn);
break;
case SIMPLEREGION:
Trace("Simple region %s = 0x%p (%d,%d)-(%d,%d)\n", pszName, hrgn, rc.left, rc.top, rc.right, rc.bottom);
break;
case COMPLEXREGION:
Trace("Complex region %s = 0x%p (%d,%d)-(%d,%d)\n", pszName, hrgn, rc.left, rc.top, rc.right, rc.bottom);
break;
default:
Trace("Illegal region %s = 0x%p\n", pszName, hrgn);
}
}
#else
#define DUMP_REGION(name, rgn) ((void) 0)
#endif
/***************************************************************************\
*****************************************************************************
*
* class BufferManager
*
*****************************************************************************
\***************************************************************************/
//------------------------------------------------------------------------------
BufferManager::BufferManager()
{
#if ENABLE_DUMPCACHESTATS
m_cacheDCBmpCached.SetName("DCBmpCached");
m_cacheGpBmpCached.SetName("GpBmpCached");
#endif
}
//------------------------------------------------------------------------------
BufferManager::~BufferManager()
{
if (m_pbufGpBmpShared != NULL) {
ProcessDelete(GpBmpBuffer, m_pbufGpBmpShared);
m_pbufGpBmpShared = NULL;
}
AssertMsg(m_pbufTrx == NULL, "Ensure all buffers have been destroyed");
}
//------------------------------------------------------------------------------
void
BufferManager::Destroy()
{
RemoveAllTrxBuffers();
m_cacheDCBmpCached.Destroy();
m_cacheGpBmpCached.Destroy();
}
/***************************************************************************\
*
* BufferManager::BeginTransition
*
* BeginTransition() finds a TrxBuffer to be used for a new Transition. If
* no cached, correct-format TrxBuffers are available, a new TrxBuffer is
* created. When the caller is finished with the buffer, it should be
* returned with EndTransition().
*
\***************************************************************************/
HRESULT
BufferManager::BeginTransition(
IN SIZE sizePxl, // Minimum size of each buffer, in pixels
IN int cSurfaces, // Number of buffers
IN BOOL fExactSize, // Buffer size must be an exact match
OUT TrxBuffer ** ppbuf) // Transition Buffer
{
AssertMsg((sizePxl.cx > 0) && (sizePxl.cy > 0) && (sizePxl.cx < 2000) && (sizePxl.cy < 2000),
"Ensure reasonable buffer size");
AssertMsg((cSurfaces > 0) && (cSurfaces <= 3), "Ensure reasonable number of buffers");
AssertWritePtr(ppbuf);
HRESULT hr;
*ppbuf = NULL;
//
// Part 1: Check if an existing buffer is large enough / the correct size.
//
if (m_pbufTrx != NULL) {
SIZE sizeExistPxl = m_pbufTrx->GetSize();
if (fExactSize) {
if ((sizePxl.cx != sizeExistPxl.cx) || (sizePxl.cy != sizeExistPxl.cy)) {
ClientDelete(TrxBuffer, m_pbufTrx);
m_pbufTrx = NULL;
}
} else {
if ((sizePxl.cx > sizeExistPxl.cx) || (sizePxl.cy > sizeExistPxl.cy)) {
ClientDelete(TrxBuffer, m_pbufTrx);
m_pbufTrx = NULL;
}
}
}
//
// Part 2: Create a new buffer, if needed
//
if (m_pbufTrx == NULL) {
hr = TrxBuffer::Build(sizePxl, cSurfaces, &m_pbufTrx);
if (FAILED(hr)) {
return hr;
}
}
AssertMsg(!m_pbufTrx->GetInUse(), "Ensure the buffer isn't already in use");
m_pbufTrx->SetInUse(TRUE);
*ppbuf = m_pbufTrx;
return S_OK;
}
/***************************************************************************\
*
* BufferManager::EndTransition
*
* EndTransition() is called to return a TrxBuffer to the BufferManager
* after use.
*
\***************************************************************************/
void
BufferManager::EndTransition(
IN TrxBuffer * pbufTrx, // TrxBuffer being returned
IN BOOL fCache) // Add the buffer to the available cache
{
AssertMsg(m_pbufTrx->GetInUse(), "Ensure the buffer was being used");
AssertMsg(m_pbufTrx == pbufTrx, "Ensure correct buffer");
pbufTrx->SetInUse(FALSE);
if (!fCache) {
//
// For now, since DxXForm's must always be the correct size, there
// isn't much point in caching them and we can reclaim the memory. If
// this changes, reinvestigate caching them.
//
ClientDelete(TrxBuffer, m_pbufTrx);
m_pbufTrx = NULL;
}
}
/***************************************************************************\
*
* BufferManager::FlushTrxBuffers
*
* FlushTrxBuffers() is called when all TrxBuffers must be forceably released,
* for example when DXTX shuts down.
*
\***************************************************************************/
void
BufferManager::FlushTrxBuffers()
{
RemoveAllTrxBuffers();
}
/***************************************************************************\
*
* BufferManager::RemoveAllTrxBuffers
*
* RemoveAllTrxBuffers() cleans up all resources associated with all
* TrxBuffers.
*
\***************************************************************************/
void
BufferManager::RemoveAllTrxBuffers()
{
if (m_pbufTrx != NULL) {
AssertMsg(!m_pbufTrx->GetInUse(), "Buffer should not be in use");
ClientDelete(TrxBuffer, m_pbufTrx);
m_pbufTrx = NULL;
}
}
//------------------------------------------------------------------------------
HRESULT
BufferManager::GetCachedBuffer(DuSurface::EType type, BmpBuffer ** ppbuf)
{
AssertWritePtr(ppbuf);
switch (type)
{
case DuSurface::stDC:
if ((*ppbuf = m_cacheDCBmpCached.Get()) == NULL) {
return E_OUTOFMEMORY;
}
break;
case DuSurface::stGdiPlus:
if ((*ppbuf = m_cacheGpBmpCached.Get()) == NULL) {
return E_OUTOFMEMORY;
}
break;
default:
AssertMsg(0, "Unknown surface type");
return E_NOTIMPL;
}
return S_OK;
}
//------------------------------------------------------------------------------
void
BufferManager::ReleaseCachedBuffer(BmpBuffer * pbuf)
{
AssertReadPtr(pbuf);
switch (pbuf->GetType())
{
case DuSurface::stDC:
m_cacheDCBmpCached.Release(static_cast<DCBmpBuffer*>(pbuf));
break;
case DuSurface::stGdiPlus:
m_cacheGpBmpCached.Release(static_cast<GpBmpBuffer*>(pbuf));
break;
default:
AssertMsg(0, "Unknown surface type");
}
}
/***************************************************************************\
*****************************************************************************
*
* class DCBmpBuffer
*
*****************************************************************************
\***************************************************************************/
/***************************************************************************\
*
* DCBmpBuffer::DCBmpBuffer
*
* DCBmpBuffer() fully initializes a new DCBmpBuffer object.
*
\***************************************************************************/
DCBmpBuffer::DCBmpBuffer()
{
}
/***************************************************************************\
*
* DCBmpBuffer::~DCBmpBuffer
*
* ~DCBmpBuffer() cleans up all resources associated with the buffer.
*
\***************************************************************************/
DCBmpBuffer::~DCBmpBuffer()
{
EndDraw(FALSE);
FreeBitmap();
}
/***************************************************************************\
*
* DCBmpBuffer::BeginDraw
*
* BeginDraw() sets up the DCBmpBuffer to begin a drawing cycle. The final
* destination HDC and size are passed in, and a new "temporary" HDC is
* returned out.
*
\***************************************************************************/
HRESULT
DCBmpBuffer::BeginDraw(
IN DuSurface * psrfDraw, // Final destination Surface
IN const RECT * prcInvalid, // Invalid area of destination
IN UINT nCmd, // How the buffer is to be used
OUT DuSurface ** ppsrfBuffer) // Surface of buffer or NULL if not needed
{
AssertMsg(prcInvalid != NULL, "Must specify a valid area");
AssertWritePtr(ppsrfBuffer);
*ppsrfBuffer = NULL;
HDC hdcDraw = CastHDC(psrfDraw);
//
// Ensure not in the middle of drawing.
//
EndDraw(FALSE);
//
// Determine the size of the area to draw and ensure that the buffer is
// large enough.
//
SIZE sizeBmp;
sizeBmp.cx = prcInvalid->right - prcInvalid->left;
sizeBmp.cy = prcInvalid->bottom - prcInvalid->top;
if ((sizeBmp.cx == 0) || (sizeBmp.cy == 0)) {
//
// Nothing to draw / buffer, so just let drawing occur in the given
// buffer. Signal this by returning NULL so that the caller knows
// not to create extra, unnecessary data.
//
AssertMsg(!m_fChangeOrg, "Ensure valid state");
AssertMsg(m_hdcDraw == NULL, "Ensure valid state");
return S_OK;
}
if ((sizeBmp.cx > m_sizeBmp.cx) || (sizeBmp.cy > m_sizeBmp.cy)) {
//
// When allocating a new bitmap, make it large enough to consume the
// existing bitmap. This helps avoid swapping between two different
// bitmaps.
//
// TODO: Need to add code into the BufferManager that maintains multiple
// bitmaps of different sizes so that we don't get many rarely-used,
// giant bitmaps.
//
if (!AllocBitmap(hdcDraw,
max(sizeBmp.cx, m_sizeBmp.cx),
max(sizeBmp.cy, m_sizeBmp.cy))) {
return DU_E_OUTOFGDIRESOURCES;
}
}
AssertMsg((prcInvalid->right >= prcInvalid->left) &&
(prcInvalid->bottom >= prcInvalid->top), "Check normalized");
//
// Setup the drawing
//
m_hdcBitmap = CreateCompatibleDC(hdcDraw);
if (m_hdcBitmap == NULL) {
return DU_E_OUTOFGDIRESOURCES;
}
if (SupportXForm()) {
m_nOldGfxMode = SetGraphicsMode(m_hdcBitmap, GM_ADVANCED);
}
m_hbmpOld = (HBITMAP) SelectObject(m_hdcBitmap, m_hbmpBuffer);
HPALETTE hpal = (HPALETTE) GetCurrentObject(hdcDraw, OBJ_PAL);
if (hpal != NULL) {
m_hpalOld = SelectPalette(m_hdcBitmap, hpal, FALSE);
} else {
m_hpalOld = NULL;
}
m_hdcDraw = hdcDraw;
m_ptDraw.x = prcInvalid->left;
m_ptDraw.y = prcInvalid->top;
m_sizeDraw.cx = prcInvalid->right - prcInvalid->left;
m_sizeDraw.cy = prcInvalid->bottom - prcInvalid->top;
m_nCmd = nCmd;
if ((m_ptDraw.x != 0) || (m_ptDraw.y != 0)) {
/*
* The buffer size is minimized to the painting area, so we need to
* setup some stuff to "fake" GDI and do the right thing:
* 1. Change the brush origin so that it the end result appears
* consistent
* 2. Change the window origin so that the drawing is in the upper
* left corner.
*
* We will set this back when we are done drawing.
*/
POINT ptBrushOrg;
GetBrushOrgEx(m_hdcBitmap, &ptBrushOrg);
SetBrushOrgEx(m_hdcBitmap,
ptBrushOrg.x - m_ptDraw.x, ptBrushOrg.y - m_ptDraw.y,
&m_ptOldBrushOrg);
m_fChangeOrg = TRUE;
} else {
m_fChangeOrg = FALSE;
}
OS()->PushXForm(m_hdcBitmap, &m_xfOldBitmap);
OS()->PushXForm(m_hdcDraw, &m_xfOldDraw);
m_fChangeXF = TRUE;
m_fClip = FALSE;
#if DEBUG_COPYTOCLIPBOARD
#if DBG
RECT rcFill;
rcFill.left = 0;
rcFill.top = 0;
rcFill.right = m_sizeBmp.cx;
rcFill.bottom = m_sizeBmp.cy;
FillRect(m_hdcBitmap, &rcFill, GetStdColorBrushI(SC_Plum));
#endif // DBG
#endif // DEBUG_COPYTOCLIPBOARD
//
// Setup the buffer as necessary
//
if (m_nCmd == BmpBuffer::dcCopyBkgnd) {
//
// Copy the destination to the buffer to be used as a background.
//
BitBlt(m_hdcBitmap, 0, 0, m_sizeDraw.cx, m_sizeDraw.cy,
m_hdcDraw, m_ptDraw.x, m_ptDraw.y, SRCCOPY);
}
//
// Clip drawing in the buffer to the actual used part of the buffer. Since
// this is the only part that will be copied over, we don't want to draw
// outside of this area since it slows down performance.
//
GdiCache * pgc = GetGdiCache();
HRGN hrgnClip = pgc->GetTempRgn();
if (hrgnClip != NULL) {
SetRectRgn(hrgnClip, 0, 0, m_sizeDraw.cx, m_sizeDraw.cy);
ExtSelectClipRgn(m_hdcBitmap, hrgnClip, RGN_COPY);
//
// If the destination surface had a clipping region, we need to
// propagate it over to the buffer.
//
HRGN hrgnDraw = pgc->GetTempRgn();
if (hrgnDraw != NULL) {
if (GetClipRgn(m_hdcDraw, hrgnDraw) == 1) {
OffsetRgn(hrgnDraw, -m_ptDraw.x, -m_ptDraw.y);
ExtSelectClipRgn(m_hdcBitmap, hrgnDraw, RGN_AND);
}
pgc->ReleaseTempRgn(hrgnDraw);
}
pgc->ReleaseTempRgn(hrgnClip);
}
//
// Create a new Surface to contain the buffer.
//
return DuDCSurface::Build(m_hdcBitmap, (DuDCSurface **) ppsrfBuffer);
}
/***************************************************************************\
*
* DCBmpBuffer::Fill
*
* TOOD:
*
\***************************************************************************/
void
DCBmpBuffer::Fill(COLORREF cr)
{
HBRUSH hbr = CreateSolidBrush(cr);
RECT rcFill;
rcFill.left = 0;
rcFill.top = 0;
rcFill.right = m_sizeDraw.cx;
rcFill.bottom = m_sizeDraw.cy;
FillRect(m_hdcBitmap, &rcFill, hbr);
DeleteObject(hbr);
}
/***************************************************************************\
*
* DCBmpBuffer::PreEndDraw
*
* PreEndDraw() finished a drawing cycle that was previously started with
* BeginDraw(). If fCommit is TRUE, the temporary buffer is prepared to be
* copied to the final destination. The called MUST also call EndDraw()
* to properly end the drawing cycle.
*
\***************************************************************************/
void
DCBmpBuffer::PreEndDraw(
IN BOOL fCommit) // Copy to final destination
{
//
// Resource the destination and buffer surfaces back to where they started.
// This is important because they will be changed during the drawing.
//
if (m_fChangeXF) {
OS()->PopXForm(m_hdcBitmap, &m_xfOldBitmap);
OS()->PopXForm(m_hdcDraw, &m_xfOldDraw);
m_fChangeXF = FALSE;
}
if (m_fChangeOrg) {
SetBrushOrgEx(m_hdcBitmap, m_ptOldBrushOrg.x, m_ptOldBrushOrg.y, NULL);
m_fChangeOrg = FALSE;
}
if ((fCommit) && (m_hdcDraw != NULL)) {
//
// Setup any clipping region needed to limit the buffer to an area
// in the destination surface.
//
if (m_fClip) {
AssertMsg(m_hrgnDrawClip != NULL, "Must have valid region");
AssertMsg(m_hrgnDrawOld == NULL, "Ensure no outstanding region");
m_hrgnDrawOld = GetGdiCache()->GetTempRgn();
if (m_hrgnDrawOld != NULL) {
DUMP_REGION("m_hrgnDrawOld", m_hrgnDrawOld);
if (GetClipRgn(m_hdcDraw, m_hrgnDrawOld) <= 0) {
GetGdiCache()->ReleaseTempRgn(m_hrgnDrawOld);
m_hrgnDrawOld = NULL;
}
DUMP_REGION("m_hrgnDrawClip", m_hrgnDrawClip);
ExtSelectClipRgn(m_hdcDraw, m_hrgnDrawClip, RGN_COPY);
}
}
}
}
/***************************************************************************\
*
* DCBmpBuffer::EndDraw
*
* EndDraw() presents a drawing cycle that was previously started with
* BeginDraw(). If fCommit is TRUE, the temporary buffer is copied to the
* final destination. The caller MUST first call PreEndDraw() to properly
* setup any state needed to end the drawing cycle.
*
\***************************************************************************/
void
DCBmpBuffer::EndDraw(
IN BOOL fCommit, // Copy to final destination
IN BYTE bAlphaLevel, // General alpha level
IN BYTE bAlphaFormat) // Pixel alpha format
{
if ((fCommit) && (m_hdcDraw != NULL)) {
//
// Copy the bits over
//
if (bAlphaLevel == BLEND_OPAQUE) {
BitBlt(m_hdcDraw, m_ptDraw.x, m_ptDraw.y, m_sizeDraw.cx, m_sizeDraw.cy,
m_hdcBitmap, 0, 0, SRCCOPY);
} else {
BLENDFUNCTION bf;
bf.AlphaFormat = bAlphaFormat;
bf.BlendFlags = 0;
bf.BlendOp = AC_SRC_OVER;
bf.SourceConstantAlpha = bAlphaLevel;
AlphaBlend(m_hdcDraw, m_ptDraw.x, m_ptDraw.y, m_sizeDraw.cx, m_sizeDraw.cy,
m_hdcBitmap, 0, 0, m_sizeDraw.cx, m_sizeDraw.cy, bf);
}
}
}
/***************************************************************************\
*
* DCBmpBuffer::PostEndDraw
*
* PostEndDraw() finished a drawing cycle that was previously started with
* BeginDraw(). After this function finishes, the buffer is ready to be used
* again.
*
\***************************************************************************/
void
DCBmpBuffer::PostEndDraw()
{
if (m_hdcDraw != NULL) {
//
// Cleanup the temporary clipping region. This is VERY important to
// do so that the destination surface can continued to be drawn on.
// (For example, more Gadget siblings...)
//
if (m_fClip) {
//
// NOTE: m_hrgnDrawOld may be NULL if there was no previous clipping
// region.
//
ExtSelectClipRgn(m_hdcDraw, m_hrgnDrawOld, RGN_COPY);
if (m_hrgnDrawOld != NULL) {
GetGdiCache()->ReleaseTempRgn(m_hrgnDrawOld);
m_hrgnDrawOld = NULL;
}
}
}
//
// Cleanup the clipping region
//
if (m_fClip) {
AssertMsg(m_hrgnDrawClip != NULL, "Must have a valid region");
GetGdiCache()->ReleaseTempRgn(m_hrgnDrawClip);
m_hrgnDrawClip = NULL;
m_fClip = FALSE;
}
AssertMsg(m_hrgnDrawClip == NULL, "Should no longer have a clipping region");
//
// Cleanup associated resources
//
if (m_hdcBitmap != NULL) {
if (m_hpalOld != NULL) {
SelectPalette(m_hdcBitmap, m_hpalOld, TRUE);
m_hpalOld = NULL;
}
SelectObject(m_hdcBitmap, m_hbmpOld);
if (SupportXForm()) {
SetGraphicsMode(m_hdcBitmap, m_nOldGfxMode);
}
DeleteDC(m_hdcBitmap);
m_hdcBitmap = NULL;
#if DEBUG_COPYTOCLIPBOARD
#if DBG
if (OpenClipboard(NULL)) {
EmptyClipboard();
HBITMAP hbmpCopy = (HBITMAP) CopyImage(m_hbmpBuffer, IMAGE_BITMAP, 0, 0, 0);
HDC hdc = GetGdiCache()->GetCompatibleDC();
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdc, hbmpCopy);
// Outline the actual drawn area
RECT rcDraw;
rcDraw.left = 0;
rcDraw.top = 0;
rcDraw.right = m_sizeDraw.cx;
rcDraw.bottom = m_sizeDraw.cy;
GdDrawOutlineRect(hdc, &rcDraw, GetStdColorBrushI(SC_Crimson), 1);
SelectObject(hdc, hbmpOld);
GetGdiCache()->ReleaseCompatibleDC(hdc);
SetClipboardData(CF_BITMAP, hbmpCopy);
CloseClipboard();
}
#endif // DBG
#endif // DEBUG_COPYTOCLIPBOARD
}
m_hdcDraw = NULL;
m_hbmpOld = NULL;
if (GetContext()->GetPerfMode() == IGPM_SIZE) {
FreeBitmap();
}
}
//------------------------------------------------------------------------------
void
DCBmpBuffer::SetupClipRgn()
{
AssertMsg(!m_fClip, "Only should setup clip region once per cycle");
AssertMsg(m_hrgnDrawClip == NULL, "Should not already have a clip region");
m_hrgnDrawClip = GetGdiCache()->GetTempRgn();
if (m_hrgnDrawClip == NULL) {
return;
}
//
// NOTE: GetClipRgn() does NOT return the standard region return values.
//
if (GetClipRgn(m_hdcBitmap, m_hrgnDrawClip) == 1) {
DUMP_REGION("m_hrgnDrawClip", m_hrgnDrawClip);
OffsetRgn(m_hrgnDrawClip, m_ptDraw.x, m_ptDraw.y);
m_fClip = TRUE;
} else {
GetGdiCache()->ReleaseTempRgn(m_hrgnDrawClip);
m_hrgnDrawClip = NULL;
}
}
/***************************************************************************\
*
* DCBmpBuffer::InUse
*
* InUse() returns if the DCBmpBuffer is currently in a draw cycle started from
* BeginDraw().
*
\***************************************************************************/
BOOL
DCBmpBuffer::InUse() const
{
return m_hdcDraw != NULL;
}
/***************************************************************************\
*
* DCBmpBuffer::AllocBitmap
*
* AllocBitmap() allocates the internal bitmap buffer that is used to
* temporarily draw into. This bitmap will be compatible with the final
* destination surface.
*
\***************************************************************************/
BOOL
DCBmpBuffer::AllocBitmap(
IN HDC hdcDraw, // Final destination HDC
IN int cx, // Width of new bitmap
IN int cy) // Height of new bitmap
{
FreeBitmap();
//
// When allocating a bitmap, round up to a multiple of 16 x 16. This helps
// to reduce unnecessary reallocations because we grew by one or two pixels.
//
cx = ((cx + 15) / 16) * 16;
cy = ((cy + 15) / 16) * 16;
//
// Allocate the bitmap
//
#if 0
m_hbmpBuffer = CreateCompatibleBitmap(hdcDraw, cx, cy);
#else
m_hbmpBuffer = ResourceManager::RequestCreateCompatibleBitmap(hdcDraw, cx, cy);
#endif
if (m_hbmpBuffer == NULL) {
return FALSE;
}
m_sizeBmp.cx = cx;
m_sizeBmp.cy = cy;
return TRUE;
}
/***************************************************************************\
*
* DCBmpBuffer::FreeBitmap
*
* FreeBitmap() cleans up allocated resources.
*
\***************************************************************************/
void
DCBmpBuffer::FreeBitmap()
{
if (m_hbmpBuffer != NULL) {
DeleteObject(m_hbmpBuffer);
m_hbmpBuffer = NULL;
m_sizeBmp.cx = 0;
m_sizeBmp.cy = 0;
}
}
/***************************************************************************\
*****************************************************************************
*
* class GpBmpBuffer
*
*****************************************************************************
\***************************************************************************/
/***************************************************************************\
*
* GpBmpBuffer::GpBmpBuffer
*
* GpBmpBuffer() fully initializes a new GpBmpBuffer object.
*
\***************************************************************************/
GpBmpBuffer::GpBmpBuffer()
{
}
/***************************************************************************\
*
* GpBmpBuffer::~GpBmpBuffer
*
* ~GpBmpBuffer() cleans up all resources associated with the buffer.
*
\***************************************************************************/
GpBmpBuffer::~GpBmpBuffer()
{
EndDraw(FALSE);
FreeBitmap();
}
/***************************************************************************\
*
* GpBmpBuffer::BeginDraw
*
* BeginDraw() sets up the GpBmpBuffer to begin a drawing cycle. The final
* destination HDC and size are passed in, and a new "temporary" HDC is
* returned out.
*
\***************************************************************************/
HRESULT
GpBmpBuffer::BeginDraw(
IN DuSurface * psrfDraw, // Final destination Surface
IN const RECT * prcInvalid, // Invalid area of destination
IN UINT nCmd, // How the buffer is to be used
OUT DuSurface ** ppsrfBuffer) // Surface of buffer or NULL if not needed
{
AssertMsg(prcInvalid != NULL, "Must specify a valid area");
AssertWritePtr(ppsrfBuffer);
*ppsrfBuffer = NULL;
Gdiplus::Graphics * pgpgrDraw = CastGraphics(psrfDraw);
if (!ResourceManager::IsInitGdiPlus()) {
return DU_E_NOTINITIALIZED;
}
//
// Ensure not in the middle of drawing.
//
EndDraw(FALSE);
//
// Determine the size of the area to draw and ensure that the buffer is
// large enough.
//
SIZE sizeBmp;
sizeBmp.cx = prcInvalid->right - prcInvalid->left;
sizeBmp.cy = prcInvalid->bottom - prcInvalid->top;
if ((sizeBmp.cx == 0) || (sizeBmp.cy == 0)) {
//
// Nothing to draw / buffer, so just let drawing occur in the given
// buffer. Signal this by returning NULL so that the caller knows
// not to create extra, unnecessary data.
//
AssertMsg(!m_fChangeOrg, "Ensure valid state");
AssertMsg(m_pgpgrDraw == NULL, "Ensure valid state");
return S_OK;
}
if ((sizeBmp.cx > m_sizeBmp.cx) || (sizeBmp.cy > m_sizeBmp.cy)) {
//
// When allocating a new bitmap, make it large enough to consume the
// existing bitmap. This helps avoid swapping between two different
// bitmaps.
//
// TODO: Need to add code into the BufferManager that maintains multiple
// bitmaps of different sizes so that we don't get many rarely-used,
// giant bitmaps.
//
if (!AllocBitmap(pgpgrDraw,
max(sizeBmp.cx, m_sizeBmp.cx),
max(sizeBmp.cy, m_sizeBmp.cy))) {
return DU_E_OUTOFGDIRESOURCES;
}
}
AssertMsg((prcInvalid->right >= prcInvalid->left) &&
(prcInvalid->bottom >= prcInvalid->top), "Check normalized");
//
// Setup the drawing
//
#if ENABLE_USEFASTDIB
HDC hdcTemp = GetGdiCache()->GetTempDC();
m_hdcBitmap = CreateCompatibleDC(hdcTemp);
GetGdiCache()->ReleaseTempDC(hdcTemp);
if (m_hdcBitmap == NULL) {
return DU_E_OUTOFGDIRESOURCES;
}
m_hbmpOld = (HBITMAP) SelectObject(m_hdcBitmap, m_hbmpBuffer);
m_pgpgrBitmap = new Gdiplus::Graphics(m_hdcBitmap);
if (m_pgpgrBitmap == NULL) {
return DU_E_OUTOFGDIRESOURCES;
}
#else
m_pgpgrBitmap = new Gdiplus::Graphics(m_pgpbmpBuffer);
if (m_pgpgrBitmap == NULL) {
return DU_E_OUTOFGDIRESOURCES;
}
#endif
#if 0
m_pgpgrBitmap->SetAlphaLevel(pgpgrDraw->GetAlphaLevel());
#endif
m_pgpgrBitmap->SetCompositingMode(pgpgrDraw->GetCompositingMode());
m_pgpgrBitmap->SetCompositingQuality(pgpgrDraw->GetCompositingQuality());
m_pgpgrBitmap->SetInterpolationMode(pgpgrDraw->GetInterpolationMode());
m_pgpgrBitmap->SetSmoothingMode(pgpgrDraw->GetSmoothingMode());
m_pgpgrBitmap->SetPixelOffsetMode(pgpgrDraw->GetPixelOffsetMode());
m_pgpgrBitmap->SetTextContrast(pgpgrDraw->GetTextContrast());
m_pgpgrBitmap->SetTextRenderingHint(pgpgrDraw->GetTextRenderingHint());
m_pgpgrDraw = pgpgrDraw;
m_ptDraw.x = prcInvalid->left;
m_ptDraw.y = prcInvalid->top;
m_sizeDraw.cx = prcInvalid->right - prcInvalid->left;
m_sizeDraw.cy = prcInvalid->bottom - prcInvalid->top;
m_nCmd = nCmd;
m_fChangeOrg = FALSE;
m_pgpgrBitmap->GetTransform(&m_gpmatOldBitmap);
m_pgpgrDraw->GetTransform(&m_gpmatOldDraw);
m_fChangeXF = TRUE;
m_fClip = FALSE;
//
// Setup the buffer as necessary
//
if (m_nCmd == BmpBuffer::dcCopyBkgnd) {
//
// Copy the destination to the buffer to be used as a background.
//
// TODO: This is not supported because GDI+ can't BLT from one
// Graphics directly to another.
}
//
// Clip drawing in the buffer to the actual used part of the buffer. Since
// this is the only part that will be copied over, we don't want to draw
// outside of this area since it slows down performance.
//
Gdiplus::RectF rc(0.0f, 0.0f, (float) m_sizeDraw.cx + 1.0f, (float) m_sizeDraw.cy + 1.0f);
m_pgpgrBitmap->SetClip(rc);
if (!m_pgpgrDraw->IsClipEmpty()) {
//
// Destination surface has a clipping region, so we need to propagate
// it over to the buffer.
//
Gdiplus::Region gprgn;
m_pgpgrDraw->GetClip(&gprgn);
gprgn.Translate(-m_ptDraw.x, -m_ptDraw.y);
m_pgpgrBitmap->SetClip(&gprgn, Gdiplus::CombineModeIntersect);
}
//
// Create a new Surface to contain the buffer.
//
return DuGpSurface::Build(m_pgpgrBitmap, (DuGpSurface **) ppsrfBuffer);
}
/***************************************************************************\
*
* GpBmpBuffer::Fill
*
* TOOD:
*
\***************************************************************************/
void
GpBmpBuffer::Fill(COLORREF cr)
{
//
// Determine packing
//
HBRUSH hbr = CreateSolidBrush(cr);
RECT rcFill;
rcFill.left = 0;
rcFill.top = 0;
rcFill.right = m_sizeDraw.cx;
rcFill.bottom = m_sizeDraw.cy;
FillRect(m_hdcBitmap, &rcFill, hbr);
DeleteObject(hbr);
}
/***************************************************************************\
*
* GpBmpBuffer::PreEndDraw
*
* PreEndDraw() finished a drawing cycle that was previously started with
* BeginDraw(). If fCommit is TRUE, the temporary buffer is prepared to be
* copied to the final destination. The called MUST also call EndDraw()
* to properly end the drawing cycle.
*
\***************************************************************************/
void
GpBmpBuffer::PreEndDraw(
IN BOOL fCommit) // Copy to final destination
{
//
// Resource the destination and buffer surfaces back to where they started.
// This is important because they will be changed during the drawing.
//
if (m_fChangeXF) {
m_pgpgrDraw->SetTransform(&m_gpmatOldDraw);
m_pgpgrBitmap->SetTransform(&m_gpmatOldBitmap);
m_fChangeXF = FALSE;
}
Assert(!m_fChangeOrg);
if ((fCommit) && (m_pgpgrDraw != NULL)) {
//
// Setup any clipping region needed to limit the buffer to an area
// in the destination surface.
//
if (m_fClip) {
AssertMsg(m_pgprgnDrawClip != NULL, "Must have valid region");
AssertMsg(m_pgprgnDrawOld == NULL, "Ensure no outstanding region");
m_pgprgnDrawOld = new Gdiplus::Region;
if (m_pgprgnDrawOld != NULL) {
m_pgpgrDraw->GetClip(m_pgprgnDrawOld);
m_pgpgrDraw->SetClip(m_pgprgnDrawClip);
}
}
}
}
/***************************************************************************\
*
* GpBmpBuffer::EndDraw
*
* EndDraw() finished a drawing cycle that was previously started with
* BeginDraw(). If fCommit is TRUE, the temporary buffer is copied to the
* final destination. After this function finishes, the GpBmpBuffer is ready
* be used again. The called MUST first call PreEndDraw() to properly setup
* any state needed to end the drawing cycle.
*
\***************************************************************************/
void
GpBmpBuffer::EndDraw(
IN BOOL fCommit, // Copy to final destination
IN BYTE bAlphaLevel, // General alpha level
IN BYTE bAlphaFormat) // Pixel alpha format
{
UNREFERENCED_PARAMETER(bAlphaFormat);
if ((fCommit) && (m_pgpgrDraw != NULL)) {
//
// Copy the bits over
//
#if ENABLE_USEFASTDIB
HDC hdcDraw = m_pgpgrDraw->GetHDC();
if (bAlphaLevel == BLEND_OPAQUE) {
BitBlt(hdcDraw, m_ptDraw.x, m_ptDraw.y, m_sizeDraw.cx, m_sizeDraw.cy,
m_hdcBitmap, 0, 0, SRCCOPY);
} else {
BLENDFUNCTION bf;
bf.AlphaFormat = bAlphaFormat;
bf.BlendFlags = 0;
bf.BlendOp = AC_SRC_OVER;
bf.SourceConstantAlpha = bAlphaLevel;
AlphaBlend(hdcDraw, m_ptDraw.x, m_ptDraw.y, m_sizeDraw.cx, m_sizeDraw.cy,
m_hdcBitmap, 0, 0, m_sizeDraw.cx, m_sizeDraw.cy, bf);
}
m_pgpgrDraw->ReleaseHDC(hdcDraw);
#else
if (bAlphaLevel == BLEND_OPAQUE) {
m_pgpgrDraw->DrawImage(m_pgpbmpBuffer, m_ptDraw.x, m_ptDraw.y,
0, 0, m_sizeDraw.cx, m_sizeDraw.cy, Gdiplus::UnitPixel);
} else {
// TODO: Need to alpha-blend using GDI+
}
#endif
}
}
/***************************************************************************\
*
* GpBmpBuffer::PostEndDraw
*
* PostEndDraw() finished a drawing cycle that was previously started with
* BeginDraw(). After this function finishes, the buffer is ready to be used
* again.
*
\***************************************************************************/
void
GpBmpBuffer::PostEndDraw()
{
if (m_pgpgrDraw != NULL) {
//
// Cleanup the temporary clipping region. This is VERY important to
// do so that the destination surface can continued to be drawn on.
// (For example, more Gadget siblings...)
//
if (m_fClip) {
m_pgpgrDraw->SetClip(m_pgprgnDrawOld);
if (m_pgprgnDrawOld != NULL) {
delete m_pgprgnDrawOld;
m_pgprgnDrawOld = NULL;
}
}
}
//
// Cleanup the clipping region
//
if (m_fClip) {
AssertMsg(m_pgprgnDrawClip!= NULL, "Must have a valid region");
delete m_pgprgnDrawClip;
m_pgprgnDrawClip = NULL;
m_fClip = FALSE;
}
AssertMsg(m_pgprgnDrawClip == NULL, "Should no longer have a clipping region");
//
// Cleanup associated resources
//
if (m_pgpgrBitmap != NULL) {
delete m_pgpgrBitmap;
m_pgpgrBitmap = NULL;
}
if (m_hdcBitmap != NULL) {
SelectObject(m_hdcBitmap, m_hbmpOld);
DeleteDC(m_hdcBitmap);
m_hdcBitmap = NULL;
}
m_pgpgrDraw = NULL;
if (GetContext()->GetPerfMode() == IGPM_SIZE) {
FreeBitmap();
}
}
//------------------------------------------------------------------------------
void
GpBmpBuffer::SetupClipRgn()
{
AssertMsg(!m_fClip, "Only should setup clip region once per cycle");
AssertMsg(m_pgprgnDrawClip == NULL, "Should not already have a clip region");
m_pgprgnDrawClip = new Gdiplus::Region;
if (m_pgprgnDrawClip == NULL) {
return;
}
m_pgpgrBitmap->GetClip(m_pgprgnDrawClip);
m_pgprgnDrawClip->Translate(m_ptDraw.x, m_ptDraw.y);
m_fClip = TRUE;
}
/***************************************************************************\
*
* GpBmpBuffer::InUse
*
* InUse() returns if the GpBmpBuffer is currently in a draw cycle started from
* BeginDraw().
*
\***************************************************************************/
BOOL
GpBmpBuffer::InUse() const
{
return m_pgpgrDraw != NULL;
}
/***************************************************************************\
*
* GpBmpBuffer::AllocBitmap
*
* AllocBitmap() allocates the internal bitmap buffer that is used to
* temporarily draw into. This bitmap will be compatible with the final
* destination surface.
*
\***************************************************************************/
BOOL
GpBmpBuffer::AllocBitmap(
IN Gdiplus::Graphics * pgpgr, // Final destination Graphics
IN int cx, // Width of new bitmap
IN int cy) // Height of new bitmap
{
FreeBitmap();
Assert(ResourceManager::IsInitGdiPlus());
//
// When allocating a bitmap, round up to a multiple of 16 x 16. This helps
// to reduce unnecessary reallocations because we grew by one or two pixels.
//
cx = ((cx + 15) / 16) * 16;
cy = ((cy + 15) / 16) * 16;
//
// Allocate the bitmap
//
#if ENABLE_USEFASTDIB
UNREFERENCED_PARAMETER(pgpgr);
HPALETTE hpal = NULL;
HDC hdcTemp = GetGdiCache()->GetTempDC();
ZeroMemory(&m_bmih, sizeof(m_bmih));
m_bmih.biSize = sizeof(m_bmih);
m_hbmpBuffer = CreateCompatibleDIB(hdcTemp, hpal, cx, cy, &m_pvBits, &m_bmih);
GetGdiCache()->ReleaseTempDC(hdcTemp);
if (m_hbmpBuffer == NULL) {
return FALSE;
}
#else
m_pgpbmpBuffer = new Gdiplus::Bitmap(cx, cy, pgpgr);
if (m_pgpbmpBuffer == NULL) {
return FALSE;
}
#endif
m_sizeBmp.cx = cx;
m_sizeBmp.cy = cy;
return TRUE;
}
/***************************************************************************\
*
* GpBmpBuffer::FreeBitmap
*
* FreeBitmap() cleans up allocated resources.
*
\***************************************************************************/
void
GpBmpBuffer::FreeBitmap()
{
#if ENABLE_USEFASTDIB
if (m_hbmpBuffer != NULL) {
DeleteObject(m_hbmpBuffer);
m_hbmpBuffer = NULL;
m_sizeBmp.cx = 0;
m_sizeBmp.cy = 0;
}
#else
if (m_pgpbmpBuffer != NULL) {
delete m_pgpbmpBuffer;
m_pgpbmpBuffer = NULL;
m_sizeBmp.cx = 0;
m_sizeBmp.cy = 0;
}
#endif
}
/***************************************************************************\
*****************************************************************************
*
* class DCBmpBufferCache
*
*****************************************************************************
\***************************************************************************/
//------------------------------------------------------------------------------
void *
DCBmpBufferCache::Build()
{
return ClientNew(DCBmpBuffer);
}
//------------------------------------------------------------------------------
void
DCBmpBufferCache::DestroyObject(void * pObj)
{
DCBmpBuffer * pbufBmp = reinterpret_cast<DCBmpBuffer *>(pObj);
ClientDelete(DCBmpBuffer, pbufBmp);
}
/***************************************************************************\
*****************************************************************************
*
* class GpBmpBufferCache
*
*****************************************************************************
\***************************************************************************/
//------------------------------------------------------------------------------
void *
GpBmpBufferCache::Build()
{
return ClientNew(GpBmpBuffer);
}
//------------------------------------------------------------------------------
void
GpBmpBufferCache::DestroyObject(void * pObj)
{
GpBmpBuffer * pbufBmp = reinterpret_cast<GpBmpBuffer *>(pObj);
ClientDelete(GpBmpBuffer, pbufBmp);
}
/***************************************************************************\
*****************************************************************************
*
* class TrxBuffer
*
*****************************************************************************
\***************************************************************************/
/***************************************************************************\
*
* TrxBuffer::TrxBuffer
*
* TrxBuffer() constructs a new TrxBuffer object. To create new, initialized
* TrxBuffer's, call the Build() function instead.
*
\***************************************************************************/
TrxBuffer::TrxBuffer()
{
ZeroMemory(m_rgpsur, sizeof(m_rgpsur));
}
/***************************************************************************\
*
* TrxBuffer::~TrxBuffer
*
* ~TrxBuffer() cleans up all resources associated with the buffer.
*
\***************************************************************************/
TrxBuffer::~TrxBuffer()
{
RemoveAllSurfaces();
}
/***************************************************************************\
*
* TrxBuffer::Build
*
* Build() fully initializes a new TrxBuffer object that contains several
* DxSurfaces of a common size. These buffers are used to provide both
* input and output buffers for DirectX Transforms.
*
\***************************************************************************/
HRESULT
TrxBuffer::Build(
IN SIZE sizePxl, // Size of each surface in pixels
IN int cSurfaces, // Number of surfaces
OUT TrxBuffer ** ppbufNew) // New buffer
{
HRESULT hr;
TrxBuffer * pbuf = ClientNew(TrxBuffer);
if (pbuf == NULL) {
return E_OUTOFMEMORY;
}
pbuf->m_cSurfaces = cSurfaces;
pbuf->m_sizePxl = sizePxl;
for (int idx = 0; idx < cSurfaces; idx++) {
hr = pbuf->BuildSurface(idx);
if (FAILED(hr)) {
ClientDelete(TrxBuffer, pbuf);
return hr;
}
}
*ppbufNew = pbuf;
return S_OK;
}
/***************************************************************************\
*
* TrxBuffer::BuildSurface
*
* BuildSurface() internally builds a new DxSurface and assigns it in the
* specified slot.
*
\***************************************************************************/
HRESULT
TrxBuffer::BuildSurface(
IN int idxSurface) // Surface slot
{
AssertMsg((idxSurface < m_cSurfaces) && (idxSurface >= 0), "Ensure valid index");
AssertMsg((m_sizePxl.cx <= 4000) && (m_sizePxl.cx >= 0) &&
(m_sizePxl.cy <= 4000) && (m_sizePxl.cy >= 0), "Ensure reasonable size");
AssertMsg(m_rgpsur[idxSurface] == NULL, "Ensure not already created");
HRESULT hr;
DxSurface * psurNew = ClientNew(DxSurface);
if (psurNew == NULL) {
return E_OUTOFMEMORY;
}
hr = psurNew->Create(m_sizePxl);
if (FAILED(hr)) {
ClientDelete(DxSurface, psurNew);
return hr;
}
m_rgpsur[idxSurface] = psurNew;
return S_OK;
}
/***************************************************************************\
*
* TrxBuffer::RemoveAllSurfaces
*
* RemoveAllSurfaces() destroys all DxSurfaces owned by the TrxBuffer.
*
\***************************************************************************/
void
TrxBuffer::RemoveAllSurfaces()
{
for (int idx = 0; idx < MAX_Surfaces; idx++) {
if (m_rgpsur != NULL) {
ClientDelete(DxSurface, m_rgpsur[idx]);
}
}
}