Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I know that in OpenGL I can do something like this

glReadBuffer( GL_FRONT );
glReadPixels( 0, 0, _width, _height, GL_RGB, GL_UNSIGNED_BYTE, _buffer ); 

And its pretty fast, I get the raw bitmap in _buffer. When I try to do this in DirectX. Assuming that I have a D3DDevice object I can do something like this

if (SUCCEEDED(D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackbuffer))) {
   HResult hr = D3DXSaveSurfaceToFileA(filename, D3DXIFF_BMP, pBackbuffer, NULL, NULL); 

But D3DXSaveSurfaceToFile is pretty slow, and I don't need to write the capture to disk anyway, so I was wondering if there was a faster way to do this.

EDIT: I am targeting only DirectX applications. Actually DX9 apps. I am doing this through a hook to Present. (I am using Detours if that is relevant), and I am doing this for every frame. I don't need (or want) a particular bitmap format like BMP PNG JPEG etc, and a colorspace of RGB24 is OK. Getting it as YUV480p would be better but definitely not necessary. Thanks everyone for the very helpful answers

share|improve this question
2  
What's do you mean by "bitmap"? A file in a specific format or an array of pixel colors? If it's a file, try this: msdn.microsoft.com/en-us/library/windows/desktop/… If array, I'd like to know the answer too. –  snake5 Nov 1 '12 at 9:10
    
yeah, I meant an array of bytes, 3 bytes per pixel of RGB information. Just the same thing that glReadPixels does in openGL. I meant a RAW bitmap. I will update the question to make it clearer –  cloudraven Nov 1 '12 at 9:30
    
Actually, that function you mention saves it to memory, I can just read the d3xbuffer it gets. I should try it, if it is fast enough I may just accept it as the solution. (Still if someone knows a faster way to capture the screen, it is very welcome) –  cloudraven Nov 1 '12 at 9:35
add comment

3 Answers

up vote 4 down vote accepted

The way I do this is as follows.

IDirect3DDevice9::GetBackBuffer: Get access to the IDirect3DSurface9 representing the back buffer, same as you've currently got. Don't forget to Release this surface when done as this call will increment the reference count!

IDirect3DSurface::GetDesc: Get the description of the back buffer surface, which will give you it's width, height and format.

IDirect3DDevice9::CreateOffscreenPlainSurface: Create a new surface object in D3DPOOL_SCRATCH; you commonly want to use the same width, height and format (but you don't actually have to with this method). Again, Release when done. If you're doing this operation every frame (in which case you're better off looking at alternatives such as a shader-based approach to what you're trying to do) you could just create the offscreen plain surface once at startup and reuse it, instead of creating it every frame.

D3DXLoadSurfaceFromSurface: Copy from the back buffer surface to the offsceen plain surface. This will do a resize and format conversion automatically for you. Alternatively, if you don't want to or need to resize or change the format you could use IDirect3DDevice9::GetRenderTargetData, but if so then create the offscreen plain surface in D3DPOOL_SYSTEMMEM instead.

IDirect3DSurface9::LockRect: Get access to the data in the offscreen plain surface and have your own evil way with it; UnlockRect when done.

This looks like a lot more code but you'll find that it's just as fast as glReadPixels, and can even be faster if you don't need to do a format conversion (which glReadPixels using GL_RGB almost certainly does).

Edit to add: some (rought 'n' ready) helper functions I also have which may be useful for using this method for screenshots:

// assumes pitch is measured in 32-bit texels, not bytes; use locked_rect.Pitch >> 2
void CollapseRowPitch (unsigned *data, int width, int height, int pitch)
{
    if (width != pitch)
    {
        unsigned *out = data;

        // as a minor optimization we can skip the first row
        // since out and data point to the same this is OK
        out += width;
        data += pitch;

        for (int h = 1; h < height; h++)
        {
            for (int w = 0; w < width; w++)
                out[w] = data[w];

            out += width;
            data += pitch;
        }
    }
}


void Compress32To24 (byte *data, int width, int height)
{
    byte *out = data;

    for (int h = 0; h < height; h++)
    {
        for (int w = 0; w < width; w++, data += 4, out += 3)
        {
            out[0] = data[0];
            out[1] = data[1];
            out[2] = data[2];
        }
    }
}

// bpp is bits, not bytes
void WriteDataToTGA (char *name, void *data, int width, int height, int bpp)
{
    if ((bpp == 24 || bpp == 8) && name && data && width > 0 && height > 0)
    {
        FILE *f = fopen (name, "wb");

        if (f)
        {
            byte header[18];

            memset (header, 0, 18);

            header[2] = 2;
            header[12] = width & 255;
            header[13] = width >> 8;
            header[14] = height & 255;
            header[15] = height >> 8;
            header[16] = bpp;
            header[17] = 0x20;

            fwrite (header, 18, 1, f);
            fwrite (data, (width * height * bpp) >> 3, 1, f);

            fclose (f);
        }
    }
}
share|improve this answer
    
I am actually doing it every frame. I am hooking the Present function and then getting the frame captured within my hook. Could you please elaborate on using a shader for doing this, that may be what I am looking for. Thanks a lot for your thorough answer. –  cloudraven Nov 2 '12 at 0:24
1  
A shader-based approach is only appropriate if you are doing subsequent rendering with the captured image. From your description it sounds more like you're doing something like video capture, yes? Can you confirm this? –  Jimmy Shelter Nov 2 '12 at 0:58
    
You guessed right, but I am not exactly doing video capture, it is very close. I am not encoding all the frames (but enough to require a good performance), and I am applying a couple of 2D filters to the captured image before encoding them. –  cloudraven Nov 2 '12 at 2:13
    
I changed my code to use BGRA. It did get better. Thanks for the insight –  cloudraven Nov 28 '12 at 7:38
    
By the way, I posted a follow up question about the shader approach. I would be thankful if you take a look at it. gamedev.stackexchange.com/questions/44587/… –  cloudraven Nov 28 '12 at 7:44
show 1 more comment

I believe you need to LockRectangle on the pBackBuffer and then read the pixels from D3DLOCKED_RECT.pBits . This should be pretty fast. If you need to read only a few pixels, consider copying whole backbuffer to smaller buffer via something like DX11 CopySubresourceRegion (I'm sure there is alternative in DX9 as well), which is done on GPU and then stream this small buffer from GPU to CPU via LockRectangle - you save the transfer of big backbuffer on bus.

share|improve this answer
1  
The answer is missing the most important part: conversion. pBits will point to data in the surface format. It will most probably not be equal to an array of byte triplets (RGB24). –  snake5 Nov 1 '12 at 10:55
1  
Aside from the format conversion, this requires CreateDevice with the lockable backbuffer flag (which the documentation warns is a performance hit) and locking the backbuffer will always incur a pipeline stall (which is going to be a much greater problem than data transfer). –  Jimmy Shelter Nov 1 '12 at 14:03
1  
that's why I adviced to copy a portion of backbuffer to different buffer. If you can read raw data from surface, conversion to "bitmap" format is up to you... –  GPUquant Nov 1 '12 at 14:19
    
I will actually need to convert it to Yuv420p at some part during the process. So is DirectX using some internal format that is not RGB? –  cloudraven Nov 2 '12 at 0:29
2  
There's no such thing as an RGB internal format - internal formats are 32bpp - even in OpenGL GL_RGB just means that the spare 8 bits are unused. See opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_reads - so both OpenGL and D3D are actually going to be BGRA internally which is why using GL_RGB is slow - it needs to both compress and swizzle the data to RGB, which is a slow software process. –  Jimmy Shelter Nov 2 '12 at 1:03
add comment

You cannot capture screen with GetBackbuffer, this function only get a pointer of the back buffer, not the data.

The correct way I can think is as below

  1. Create an off-screen surface by calling CreateOffscreenPlainSurface.
  2. Call GetFrontBufferData to get the screen image to the off-screen surface
  3. Call LockRect of the surface to retrieve the data in the surface, an off-screen surface was always lockable regardless of their pool types.
share|improve this answer
1  
IDirect3DDevice9::GetFrontBufferData - "This function is very slow, by design, and should not be used in any performance-critical path" - msdn.microsoft.com/en-us/library/windows/desktop/… –  Jimmy Shelter Nov 1 '12 at 14:31
1  
Yes, it's slow, but AFAIK, it's the only way to get the screen image by DirectX, or you can do some hooking before calling the EndScene function. –  zdd Nov 1 '12 at 14:50
    
Thanks! I am actually doing it as a hook, but not to EndScene but to Present, and I am hooking specifically directX applications. Given that, is there something smarter that I can do about it. –  cloudraven Nov 2 '12 at 0:26
    
@cloudraven, I am not familiar with hooking, but you can take a look at this page for reference. stackoverflow.com/questions/1994676/… –  zdd Nov 2 '12 at 1:21
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.