reading pixels

Started by
7 comments, last by neneboricua19 18 years, 6 months ago
How would you do this GL operation in D3D? :

float buf[4];
glReadPixels( 10, 10, 1, 1, GL_RGBA, GL_FLOAT, buf);


So it's just reading the RGBA value of a pixel at 10,10 (where the origin is lower left). Thanks.
Advertisement
There is not just one function that does the exact same thing. You have to lock the surface you are rendering to (usually IDirect3DSurface::LockRect) and use the pointer it returns to access the pixels of the image. You have to provide your own offsets into this memory.

If you're trying to acess the backbuffer itself, make sure that when you first created the D3D device, you specify that you want a lockable backbuffer.

Note, that this will all be pretty slow. Reading data back from video memory is a time consuming process. glReadPixels will do the same thing underneath and will also be slow. This operation shouldn't be done in any kind of performance-critical path.

neneboricua
Well luckily the performance doesn't matter as it's just for testing things. Would something like this be about right?

IDirect3DSurface9 *rt = 0;IDirect3DSurface9 *dt = 0; D3DDISPLAYMODE dm; V( DXUTGetD3DDevice()->GetRenderTarget( 0, &rt ) ); V( DXUTGetD3DObject()->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &dm ) );V( DXUTGetD3DDevice()->CreateOffscreenPlainSurface( dm.Width, dm.Height, dm.Format, D3DPOOL_SYSTEMMEM, &dt, 0 ) ); V( DXUTGetD3DDevice()->GetRenderTargetData( rt, dt ) ); D3DLOCKED_RECT lr;          RECT r;             r.left = r.right = 10;   r.top = r.bottom = dm.Height - 10; dt->LockRect( &lr, &r, D3DLOCK_READONLY );                      float *buf = (float*)lr.pBits; //Now buf has my pixelsdt->UnlockRect();       rt->Release(); dt->Release();


So if I wanted to check the RGBA value at buf could I just do this? "if( buf[0] == val || buf[1] == val || etc...)"

In GL that's how i'd look at the RGBA values of buf comming from ReadPixels.


-SirKnight
Yes, the code is exactly correct, except that your buffer should be an "unsigned char" instead of a "float" unless you explicitly created your render target to be of floating point format.

Also, be aware when you read pixels from your buffer that you will need to take into acount the "pitch" of the surface. This is because the graphics card may have allocated more space for your surface than your originally requested for performance reasons.

So acessing pixel (row,col) of your surface would go like this:

buf[row*lr.Pitch+col*4 + 0] // Blue
buf[row*lr.Pitch+col*4 + 1] // Green
buf[row*lr.Pitch+col*4 + 2] // Red
buf[row*lr.Pitch+col*4 + 3] // Alpha

The pitch is the number of bytes in one row of the surface. We multiply the column by 4 because I'm assuming you're using a 32-bit surface where each component is 8bits (ARGB, XRGB, etc...).

Hope this helps,
neneboricua

[Edited by - neneboricua19 on October 7, 2005 1:14:58 AM]
Oh right forgot about the pitch. :) Ok well I think everything is good now. Thanks a lot!


-SirKnight
I spoke to soon. :) I made this op work when I ceated a new render target, made it lockable, and rendered to that. I could then lock and read back the pixel I wanted. If I use the code I posted above (I also got rid of the GetAdapter stuff) then I always get back 0 for the colors (even though in my sample I should get blue).

Here is the code:
int myReadPixel2( int x, int y, mycolor *data ){    IDirect3DSurface9 *rt = 0;    IDirect3DSurface9 *dt = 0;         g_pd3dDevice->GetRenderTarget( 0, &rt );     g_pd3dDevice->CreateOffscreenPlainSurface( 256, 256, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &dt, 0 );     g_pd3dDevice->GetRenderTargetData( rt, dt );     D3DLOCKED_RECT lr;      HRESULT hr = dt->LockRect( &lr, 0, D3DLOCK_READONLY );        mycolor *cbuf = ((mycolor*)lr.pBits) + x * 4 + y * lr.Pitch;   if( !cbuf )        return 0;    data->alpha = cbuf->alpha;    data->red   = cbuf->red;    data->green = cbuf->green;    data->blue  = cbuf->blue;    dt->UnlockRect();           rt->Release();     dt->Release();}


Oh and the fields in my color are unsigned chars.

Here is what I did get working:
int mySetRenderTarget( IDirect3DSurface9 **surf, int width, int height, D3DFORMAT fmt ){    HRESULT hr;    IDirect3DSurface9 *s = 0;    hr = g_pd3dDevice->CreateRenderTarget( width, height, fmt, D3DMULTISAMPLE_NONE, 0, TRUE, &s, 0 );    if( FAILED( hr ) )        return 0;    *surf = s;    hr = g_pd3dDevice->SetRenderTarget( 0, s );    if( FAILED( hr ) )         return 0;    return 1;}int myReadPixel( IDirect3DSurface9 *surf, int x, int y, mycolor *data ){    D3DLOCKED_RECT lr;    HRESULT hr = surf->LockRect( &lr, 0, D3DLOCK_READONLY );    if( FAILED( hr ) )         return 0;    mycolor *cbuf = ((mycolor*)lr.pBits) + x * 4 + y * lr.Pitch;    if( !cbuf )        return 0;    data->alpha = cbuf->alpha;    data->red   = cbuf->red;    data->green = cbuf->green;    data->blue  = cbuf->blue;    surf->UnlockRect();}


So I'd call mySetRenderTarget before I render then call myReadPixel after (and before I present). I'd rather have just one function I call after I render to get the data back from the backbuffer.


-SirKnight
Since your "mycolor" structure is already a 32-bit quantity (4 8bit values), you need to replace the "x * 4 + y * lr.Pitch" thing in the offset calculations with just "x + y*lr.Pitch".

Also note that your readPixel function creates a new rendertarget, copies one render target to another, and then locks it to read a single pixel. This will work but if you call this funciton multiple times per frame, you're performance will go down the toilet.

Make sure that you only copy to your system memory surface once per frame and make all readPixel calls access that one copy.

neneboricua
Oh thanks for noticing that * 4 part, I forgot to take that out. :) I wasn't using that struct at first, then I added it and forgot to modify that part.

But for some reason the myReadPixel2 function just keeps giving me all 0s. I tried replacing GetRenderTarget with GetBackBuffer but that made no difference. I'm not sure doing that is valid anyway. The only ways I have found that works is by making my backbuffer lockable or rendering to a new render buffer like what mySetRenderTarget/myReadPixel does. But I'd rather be able to just copy my backbuffer into a lockable surface for reading but it doesn't like me. I could change the codebase I'm working with to have a lockable backbuffer but I'd still like to find a way where I don't have to.

It's nice to know how the performance is affected with all of this but for my current project, it doesn't matter at all. I'm needing this functionality to test the results of a handfull of operations to see if they pass or fail. It's actually quite simple but very needed. :)
Quote:Original post by SirKnight
But for some reason the myReadPixel2 function just keeps giving me all 0s. I tried replacing GetRenderTarget with GetBackBuffer but that made no difference. I'm not sure doing that is valid anyway.

Make sure you're running the Debug Runtimes. Check the return values of the functions for errors. Make sure there are no errors printed to the debug spew. Whenever a DirectX function fails, a text string explaining why it failed is always printed in the debug spew. If you're not sure what I mean, check the Forum FAQ.

neneboricua

This topic is closed to new replies.

Advertisement