Windows Superfast Screenshots (GL)

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Windows Superfast Screenshots (GL)

Post by Baker »

glReadPixels downloads the entire pixel buffer to clipboard. Slowly.

On Windows: This function captures from the desktop image. Quickly. This function does the output to the clipboard, doesn't write to file. But it works.
Limitations: If another window is over your Quake window, this is a capture of the desktop and then limited the Quake client area.

Mostly based on what I found modification of from http://cboard.cprogramming.com/windows- ... ure-c.html and DirectQ's method of limiting the mouse cursor region. Call the function and give it "mainwindow" or "dibwindow" or whatever you are using for your primary window.

At some point, I may try to speed up Quake video capture by using the results instead of sluggish glReadPixels.

Code: Select all

#if _MSC_VER <=1200 // MSVC6 ONLY
// These are in <winuser.h> except I can't get them to work in MSVC6

typedef struct tagWINDOWINFO {
  DWORD cbSize;
  RECT  rcWindow;
  RECT  rcClient;
  DWORD dwStyle;
  DWORD dwExStyle;
  DWORD dwWindowStatus;
  UINT  cxWindowBorders;
  UINT  cyWindowBorders;
  ATOM  atomWindowType;
  WORD  wCreatorVersion;
} WINDOWINFO, *PWINDOWINFO, *LPWINDOWINFO;

BOOL WINAPI GetWindowInfo(HWND hwnd, PWINDOWINFO pwi);
#endif

void Sys_Window_To_Clipboard (HWND srchwnd)
{
// Baker: A modification of from http://cboard.cprogramming.com/windows-programming/34818-screen-capture-c.html
#define RectWidth(lprc)  ((lprc)->right-(lprc)->left)
#define RectHeight(lprc) ((lprc)->bottom-(lprc)->top)

	HWND	desktophwnd	= GetDesktopWindow ();
	HDC		hdc			= GetWindowDC (desktophwnd);
	HDC		hdcMem		= CreateCompatibleDC(hdc);
	HBITMAP hbm			= NULL;
	HBITMAP	oldbm		= NULL;

	RECT				rcClient, rcWindow, rcCrop, rcCapture;

	SelectObject (hdcMem,oldbm);

//	GetWindowRect (srchwnd, &rcWindow);	// Gets the entire window area in screen coordinates
//	GetClientRect (srchwnd, &rcClient); // This is mostly useless, isn't in screen coordinates.  Aero messes with it (it's wrong)
	{
		// MH of course has the "right" answer in DirectQ so we use that ....
		WINDOWINFO windowinfo;

		windowinfo.cbSize = sizeof (WINDOWINFO);
		GetWindowInfo (srchwnd, &windowinfo); // windowinfo.rcClient
		rcCapture = rcClient = windowinfo.rcClient;
	}

	SetRect (&rcCrop, 0, 0, RectWidth(&rcCapture), RectHeight(&rcCapture));

	hbm = CreateCompatibleBitmap (hdc, RectWidth(&rcCrop), RectHeight (&rcCrop));
	oldbm = SelectObject (hdcMem, hbm);

	BitBlt (hdcMem, 0, 0, RectWidth(&rcCrop), RectHeight(&rcCrop), hdc, rcCapture.left, rcCapture.top, SRCCOPY);
	
	// Clipboard
	OpenClipboard(NULL);
	EmptyClipboard();
	SetClipboardData (CF_BITMAP, hbm);
	CloseClipboard();
	ReleaseDC (desktophwnd, hdc);
	DeleteDC (hdcMem);
	DeleteObject (oldbm);
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Re: Windows Superfast Screenshots (GL)

Post by taniwha »

25ms for 1680x1050, rgb. that's ~200MB/s. It would probably be faster if I used rgba or even bgra (or whatever the native format is). I'm not sure I'd call that sluggish. It's certainly enough for 40fps (neglecting other factors such as rendering, compression, etc).

Actually, I just checked, and using RGBA instead of RGB drops glReadPixels from 25ms to 21ms.

Note, however, that the rgb to bgr swap in SCR_ScreenShot_f takes about 10ms on my system. So that's 35ms for screen shot, not including writing the image.

(G80 [GeForce 8800 GTS], nvidia driver, linux, (R) Core(TM)2 Quad CPU Q8200 @ 2.33GHz)
Leave others their otherness.
http://quakeforge.net/
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: Windows Superfast Screenshots (GL)

Post by Spike »

presumably glReadPixels would have the same format performance characteristics as lightmaps. specifically that you should be grabbing the data as bgra instead of rgb - format GL_BGRA, type GL_UNSIGNED_INT_8_8_8_8_REV. Assuming that's how your backbuffer is configured anyway.

when you time it, what is it that you're timing? the implicit glFinish() call that is part of glReadPixels()? certainly a windows call will bypass that, but you'll end up receiving stale image data. Certainly this is significant if you've a mod sending screenshot stuffcmds to capture the scoreboard.
taniwha
Posts: 401
Joined: Thu Jan 14, 2010 7:11 am
Contact:

Re: Windows Superfast Screenshots (GL)

Post by taniwha »

Actually, I put in an explicit call to glFlush before the call to Sys_DoubleTime, and it didn't make any difference.

I agree that GL_UNSIGNED_INT_8_8_8_8_REV would probably be faster still.
Leave others their otherness.
http://quakeforge.net/
metlslime
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Re: Windows Superfast Screenshots (GL)

Post by metlslime »

I think the slowest part of taking a screenshot is actually finding an available filename; once you have 50-100 existing screenshots the directory scan really bogs down. I'm sure there's an obvious better way but never got around to fixing it.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Windows Superfast Screenshots (GL)

Post by Baker »

Not all ideas work out. I know where to file this one now. :D
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: Windows Superfast Screenshots (GL)

Post by mh »

glReadPixels (and glDrawPixels) have the exact same characteristics as textures, as Spike noted. Grab them in their native format and you get better performance (you still have a pipeline stall which can be up to 3 frames, but it doesn't get much worse). It's probably going to be much faster to compress down to BGR (for TGA output) in-place following that.

Grabbing a free file name is likely the slowest part of the process though (aside from disk I/O which you can do nothing about - that's already optimal). A FindFirstFile/FindNextFile loop is probably going to be better than just trying to fopen each file in turn as all it would need to do is access the filesystem tables (rather than set up all the crap needed for full-on file I/O) but I haven't benchmarked this. On Windows fopen will be just a wrapper around the native CreateFile call, so using CreateFile directly may help by bypassing the wrapper (any perf improvements will be quite marginal though). Failing that, access (or _access on Windows) is something that's supposed to be pretty good.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Post Reply