-
Screenshots in DX9
well, im wondering how i could take a screenshot of the DirectX window and save it to a bmp (for simplicity). At the moment, heres the code i use:
Code:
bool TakeScreenShot(IDirect3DDevice9 *Device, char *FileName, int ScreenX, int ScreenY)
{
IDirect3DSurface9 *FrontBuffer;
bFailed = false;
HRESULT Result;
Result = Device->CreateOffscreenPlainSurface(ScreenX, ScreenY, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &FrontBuffer, NULL);
if (Result != D3D_OK)
{
//FrontBuffer->Release(); Thanks Magos
return false;
}
Result = Device->GetFrontBufferData(0, FrontBuffer);
if (Result != D3D_OK)
{
FrontBuffer->Release();
return false;
}
Result = D3DXSaveSurfaceToFile(FileName, D3DXIFF_BMP, FrontBuffer, NULL, NULL);
if (Result != D3D_OK)
{
FrontBuffer->Release();
return false;
}
FrontBuffer->Release();
return true;
}
it works fine, but its EXTREMELY slow, i tried putting it in a thread, and i get a deformed image.
so, i was wondering, would it be faster/better to do this using GDI? ie. bitblt into a memory hdc and go from there? or is this the only way?
Thanks in advance.
-
I'm not sure, but have you noticed when pressing a game specific button to print a screenshot, most games pause slightly. So maybe you are asking too much of your system.
-
yeah they pause.. slightly, if i try, i pause for a few seconds, in some games, the pause is barely noticeable.
-
If CreateOffscreenPlainSurface() fails, FrontBuffer will most likely be NULL (perhaps even undefined). You shouldn't call FrontBuffer->Release(); on it!
-
1. Lock the front buffer.
Code:
DWORD SizeInDWORDS=0;
asm {
cld
mov esi,[Buffer_Ptr]
mov edi,[FileBuffer_Ptr]
mov ecx,[BufferPitch]
mul ecx,[ScreenHeight]
mov SizeInDWORDS,ecx
rep movsd
and ecx,03h
rep movsb
}
int handle=_open("screenshot.dat",_O_BINARY | _O_IREAD,_S_IREAD );
if (handle)
{
write(handle,(DWORD *)FileBuffer_Ptr,(SizeInDWORDS<<2));
close(handle);
}
This shouldn't be too slow as the memcpy is super fast. The disk access cannot be sped up and depends on the condition, type, size, and quality of the hard drive. But C block writes are extremely fast operations. You will notice a small pause but it shouldn't be too long at all. You could pre-compute the buffer size and then use that value instead of computing it inside of the function. The formula is height*BufferPitch+width.
This will write out raw RGB values but you could alter the code to support any format you want.
-
how exactly would i lock the front buffer? i know how its done in DirectX 7 but not in DirectX 9.
and as for the code you provided, its basically just copying the contents of the locked front buffer to a file, right? or am i reading it wrong.
-
Yes that's all it is.
Locking the front buffer is done by gaining access to the swap chain. Look at the IDirect3DDevice9 interface and check the functions. Once you have access to the swap chain then you can lock the front buffer from there because the swap chain is a COM interface with functions as well. You may have to specify that you want a lockable front buffer when you create your application, however. When I used my code to lock the back buffer you had to specify you wanted a lockable back buffer or the call would fail in every case. This is done in the D3DPRESENT_PARAMETERS structure using the flags member. It looks like from the SDK though that you do not have to specify a lockable front buffer, only if you want to lock the back buffer.
-
I just read throught the documentation and this is what i changed:
Code:
IDirect3DSurface9 *FrontBuffer;
HRESULT hResult;
char sBuffer[256];
/*hResult = g_Device->CreateOffscreenPlainSurface(g_DisplayMode.Width, g_DisplayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &FrontBuffer, NULL);
if (hResult != D3D_OK)
{
FrontBuffer->Release();
return false;
}*/
hResult = g_Device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &FrontBuffer);
the reason i commented the first half is i was unsure if you even needed to create a surface, this works, but its even slower now.
-
Have you thought about using the D3DX API D3DXSaveSurfaceToFile ? An example of usage can be found here:
http://www.mvps.org/directx/articles/screengrab.htm
edit: Oops, I kind of skimmed your first post to be honest. Sorry about that. Also, I don't think you can get much faster than that. The nature of what you are doing is painfully slow.
-
i already used it... first post
Code:
Result = D3DXSaveSurfaceToFile(FileName, D3DXIFF_BMP, FrontBuffer, NULL, NULL);
...
-
then how is it that in other games, its extremely fast? or atleast faster then what i have here?
-
I'm not sure this will help any but if you need the surface and you want to support screenshots then create the surface at load time. This way you don't create it on the fly. You may also be able to do something else. Instead of doing a blit, just change the render target to your off-screen surface and write that surface to file. When done, set the render target back to the original surface.
You may want to fire off a thread as well to handle this, but I still think you will get some kind of pause in the game.
-
yeah, thats a good idea. although im not sure how i would change the render target? unless theres a function (ill search the documentation now). Thanks for the idea
-
Device->SetRenderTarget()