/***************************************************************************\ * * 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(pbuf)); break; case DuSurface::stGdiPlus: m_cacheGpBmpCached.Release(static_cast(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(pObj); ClientDelete(DCBmpBuffer, pbufBmp); } /***************************************************************************\ ***************************************************************************** * * class GpBmpBufferCache * ***************************************************************************** \***************************************************************************/ //------------------------------------------------------------------------------ void * GpBmpBufferCache::Build() { return ClientNew(GpBmpBuffer); } //------------------------------------------------------------------------------ void GpBmpBufferCache::DestroyObject(void * pObj) { GpBmpBuffer * pbufBmp = reinterpret_cast(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]); } } }