2 Attachment(s)
Can't seem to make double-buffered painting with multiple monitors
I'm trying to remake my Windows screensaver written with C++ and WinAPIs to work on multiple monitors. I found this article that gives the basics. But when I implement it in my own code, I get a weird result. Take a look at this code:
Code:
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdcE = BeginPaint(hWnd, &ps );
EnumDisplayMonitors(hdcE, NULL, MyPaintEnumProc, 0);
EndPaint(hWnd, &ps);
}
break;
BOOL CALLBACK MyPaintEnumProc(
HMONITOR hMonitor, // handle to display monitor
HDC hdc1, // handle to monitor DC
LPRECT lprcMonitor, // monitor intersection rectangle
LPARAM data // data
)
{
MONITORINFO mi = {0};
mi.cbSize = sizeof(mi);
if(GetMonitorInfo(hMonitor, &mi))
{
//Is it a primary monitor?
BOOL bPrimary = mi.dwFlags & MONITORINFOF_PRIMARY;
DoDrawing(bPrimary, hdc1, &mi.rcMonitor);
}
return 1;
}
void DoDrawing(BOOL bPrimaryMonitor, HDC hDC, RECT* pRcMonitor)
{
//#define DIRECT_PAINT //Comment out for double-buffering
int nMonitorW = abs(pRcMonitor->right - pRcMonitor->left);
int nMonitorH = abs(pRcMonitor->bottom - pRcMonitor->top);
HDC hMemDC = ::CreateCompatibleDC(hDC);
if(hMemDC)
{
HBITMAP hMemBmp = ::CreateCompatibleBitmap(hDC, nMonitorW, nMonitorH);
if(hMemBmp)
{
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hMemBmp);
COLORREF clr, clrBorder;
if(bPrimaryMonitor)
{
clr = RGB(0, 128, 0); //Green
clrBorder = RGB(255, 0, 0);
}
else
{
clr = RGB(128, 0, 0); //Red
clrBorder = RGB(0, 255, 0);
}
RECT rcRect;
#ifndef DIRECT_PAINT
//With double-buffering
rcRect.left = 0;
rcRect.top = 0;
rcRect.right = nMonitorW;
rcRect.bottom = nMonitorH;
#else
rcRect = *pRcMonitor;
#endif
HBRUSH hBrush = ::CreateSolidBrush(clr);
#ifndef DIRECT_PAINT
//With double-buffering
::FillRect(hMemDC, &rcRect, hBrush);
#else
::FillRect(hDC, &rcRect, hBrush);
#endif
#ifndef DIRECT_PAINT
//With double-buffering
::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, 0, 0, SRCCOPY);
#endif
//Debugging output
CString _s;
_s.Format(_T("%s\n")
_T("%s\n")
_T("hDC=0x%X\n")
_T("hMemDC=0x%X\n")
_T("RcMonitor: L=%d, T=%d, R=%d, B=%d")
,
bPrimaryMonitor ? _T("Primary") : _T("Secondary"),
#ifndef DIRECT_PAINT
_T("Double-buffering"),
#else
_T("Direct paint"),
#endif
hDC,
hMemDC,
pRcMonitor->left,
pRcMonitor->top,
pRcMonitor->right,
pRcMonitor->bottom);
::DrawText(hDC, _s, _s.GetLength(), pRcMonitor, DT_NOCLIP | DT_NOPREFIX);
SelectObject(hMemDC, hOldBmp);
::DeleteObject(hMemBmp);
}
::DeleteDC(hMemDC);
}
}
Painting always works on a primary monitor. But when I paint to the secondary monitor, I can only paint directly to its DC. When I use double-buffering technique (with DIRECT_PAINT pre-processor directive commented out) I only get a black screen on a secondary monitor when it should've been red.
I'm attaching two screenshots here.
First one with direct painting that works:
Attachment 30347
and then the one that doesn't, with double-buffering technique:
Attachment 30349
Any ideas what am I doing wrong here?
Re: Can't seem to make double-buffered painting with multiple monitors
I haven't done any Windows programming in a long time so my memory isn't the best these days...
Anyway, BitBlt uses logical coordinates so doesn't that mean that you have to set up mapmode and so on before calling it?
Re: Can't seem to make double-buffered painting with multiple monitors
Why would it work then without a double buffer?
Re: Can't seem to make double-buffered painting with multiple monitors
Code:
#ifndef DIRECT_PAINT
//With double-buffering
::FillRect(hMemDC, &rcRect, hBrush);
#else
::FillRect(hDC, &rcRect, hBrush);
#endif
#ifndef DIRECT_PAINT
//With double-buffering
::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, 0, 0, SRCCOPY);
#endif
These highlighted must just match in their top left, I believe. You seem just blitting from non-painted area.
Code:
#ifndef DIRECT_PAINT
//With double-buffering
::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, rcRect.left, rcRect.top, SRCCOPY);
#endif
Re: Can't seem to make double-buffered painting with multiple monitors
Quote:
Originally Posted by
ahmd
Why would it work then without a double buffer?
Without it you don't call BitBlt to finalize the painting
Re: Can't seem to make double-buffered painting with multiple monitors
Quote:
Originally Posted by
Igor Vartanov
[code]
These highlighted must just match in their top left
They do match. If you check above the left-top of RECT is set to 0,0.
As for setting mapmode, I'm not really sure what you mean. Can you give a code example of what should I add? It might be it, because I don't think there's anything else I can do with this sample. It seems right.
Re: Can't seem to make double-buffered painting with multiple monitors
SetMapMode sets how to translate between logical and device coordinates (SetWindowOrg and SetViewPortOrg might also be good to checkup).
As I said it's been a long time since I did any Windows programming (except console quickies) but I did browse MSDN a bit and it seems like what you're doing should work. As far as I remember though this type of code easily ended up being a pain in the ... as suddenly SetWindowOrg, SetViewportOrg and SetMapMode had to be involved.
Try calling SetMapMode http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx with MM_TEXT before calling BitBlt. Also try removing the text from your output. It might a little red dot somewhere indicating that pixel scaling occur.
Re: Can't seem to make double-buffered painting with multiple monitors
Quote:
Originally Posted by
ahmd
They do match. If you check above the left-top of RECT is set to 0,0.
Oh sorry, you're right, my bad.
Quote:
As for setting mapmode, I'm not really sure what you mean. Can you give a code example of what should I add? It might be it, because I don't think there's anything else I can do with this sample. It seems right.
The map mode by default is MM_TEXT. Considering that BitBlt works fine for your primary monitor, the problem is somewhere else. Need to play with your code a bit. It's a pity you did not provide a compilable project.
Re: Can't seem to make double-buffered painting with multiple monitors
Replace the code for WM_PAINT
Code:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
EnumDisplayMonitors(hdc, NULL, MyPaintEnumProc, 0);
EndPaint(hWnd, &ps);
with
Code:
case WM_PAINT:
hdc = GetDC(NULL);
EnumDisplayMonitors(hdc, NULL, MyPaintEnumProc, 0);
ReleaseDC(NULL, hdc);
And it will work.
Re: Can't seem to make double-buffered painting with multiple monitors
And for full double buffering (with texts too) you need this (though I'm pretty sure you know that):
Code:
void DoDrawing(BOOL bPrimaryMonitor, HDC hDC, RECT* pRcMonitor)
{
//#define DIRECT_PAINT //Comment out for double-buffering
int nMonitorW = abs(pRcMonitor->right - pRcMonitor->left);
int nMonitorH = abs(pRcMonitor->bottom - pRcMonitor->top);
HDC hMemDC = ::CreateCompatibleDC(hDC);
if(hMemDC)
{
HBITMAP hMemBmp = ::CreateCompatibleBitmap(hDC, nMonitorW, nMonitorH);
if(hMemBmp)
{
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hMemBmp);
COLORREF clr, clrBorder;
if(bPrimaryMonitor)
{
clr = RGB(0, 128, 0); //Green
clrBorder = RGB(255, 0, 0);
}
else
{
clr = RGB(128, 0, 0); //Red
clrBorder = RGB(0, 255, 0);
}
RECT rcRect;
#ifndef DIRECT_PAINT
//With double-buffering
rcRect.left = 0;
rcRect.top = 0;
rcRect.right = nMonitorW;
rcRect.bottom = nMonitorH;
#else
rcRect = *pRcMonitor;
#endif
HBRUSH hBrush = ::CreateSolidBrush(clr);
#ifndef DIRECT_PAINT
//With double-buffering
::FillRect(hMemDC, &rcRect, hBrush);
#else
::FillRect(hDC, &rcRect, hBrush);
#endif
//Debugging output
TCHAR _s[256] = {0};
_stprintf_s(
_s,
255,
_T("%s\n")
_T("%s\n")
_T("hDC=0x%X\n")
_T("hMemDC=0x%X\n")
_T("RcMonitor: L=%d, T=%d, R=%d, B=%d")
,
bPrimaryMonitor ? _T("Primary") : _T("Secondary"),
#ifndef DIRECT_PAINT
_T("Double-buffering"),
#else
_T("Direct paint"),
#endif
hDC,
hMemDC,
pRcMonitor->left,
pRcMonitor->top,
pRcMonitor->right,
pRcMonitor->bottom);
#ifndef DIRECT_PAINT
::DrawText(hMemDC, _s, _tcslen(_s), &rcRect, DT_NOCLIP | DT_NOPREFIX);
#else
::DrawText(hDC, _s, _tcslen(_s), &rcRect, DT_NOCLIP | DT_NOPREFIX);
#endif
#ifndef DIRECT_PAINT
//With double-buffering
::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, 0, 0, SRCCOPY);
#endif
SelectObject(hMemDC, hOldBmp);
::DeleteObject(hMemBmp);
}
::DeleteDC(hMemDC);
}
}
Re: Can't seem to make double-buffered painting with multiple monitors
Igor, sorry, I wanted to make a sample project but it seems to be quite a pain in the butt with screensavers.
cilu, much appreciated! There's no way in heck I would think of doing that. Any idea how exactly it makes it work (by doing it completely opposite to what I've been taught to do in WM_PAINT)?
Re: Can't seem to make double-buffered painting with multiple monitors
I guess it's because BeginPaint() clips what's outside the update region, while GetDC(NULL) returns the DC of the entire screen. In your case the update region is not the entire screen.
1 Attachment(s)
Re: Can't seem to make double-buffered painting with multiple monitors
One follow-up question. While trying to find a solution I did the following: I started using the initial virtual screen DC (received from WM_PAINT) to render each monitor. In this case I needed to know the size of the monitor (for which I used mi.rcMonitor) and also the offset from the virtual screen's anchor point, which I was able to obtain by calling GetDCOrgEx(hMonitorDC, &pnt), were hMonitorDC is the DC passed into the MyPaintEnumProc() enum proc. I illustrated it with a diagram attached.
The question I have, even though this approach (with using only one DC for the whole virtual screen) seems to work as well, am I doing something that shouldn't be done?