OpenGL ES - glReadPixels
Asked Answered
M

6

9

I am taking a screenshot with glReadPixels to perform a "cross-over" effect between two images.

On the Marmalade SDK simulator, the screenshot is taken just fine and the "cross-over" effect works a treat: enter image description here

However, this is how it looks on iOS and Android devices - corrupted: enter image description here
(source: eikona.info)

I always read the screen as RGBA 1 byte/channel, as the documentation says it's ALWAYS accepted.

Here is the code used to take the screenshot:

uint8* Gfx::ScreenshotBuffer(int& deviceWidth, int& deviceHeight, int& dataLength) {

    /// width/height
    deviceWidth = IwGxGetDeviceWidth();
    deviceHeight = IwGxGetDeviceHeight();
    int rowLength = deviceWidth * 4; /// data always returned by GL as RGBA, 1 byte/each

    dataLength = rowLength * deviceHeight;

    // set the target framebuffer to read
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    uint8* buffer = new uint8[dataLength];
    glReadPixels(0, 0, deviceWidth, deviceHeight, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

    return buffer;
}

void Gfx::ScreenshotImage(CIwImage* img, uint8*& pbuffer) {

    int deviceWidth, deviceHeight, dataLength;

    pbuffer = ScreenshotBuffer(deviceWidth, deviceHeight, dataLength);
    img->SetFormat(CIwImage::ABGR_8888);
    img->SetWidth(deviceWidth);
    img->SetHeight(deviceHeight);
    img->SetBuffers(pbuffer, dataLength, 0, 0);
}
Mercola answered 21/11, 2011 at 8:51 Comment(7)
If you remove your up-down block, does the corruption go away? Just upside down? Or is it corrupted and upside down?Gowen
The thing is, I don't have a device so I need to send to my client, then wait for him to install and test... all this may take up to a couple of days :-(Mercola
The comments above are now irrelevant after re-editing my message to reflect my new findings.Mercola
Try memsetting the buffer to zeroes after allocating. That way you can tell if anything was actually written by the glReadPixels call.Sergu
In the end, it was lack of memory. The "new uint8[dataLength];" never returned an existent pointer, thus the whole process went corrupted. TomA, your idea of clearing the buffer actually helped me to solve the problem. But how can I give you the +100 points? Please send an answer so that I can award you. Thanks.Mercola
@TomA: Note that your comment put Bill on the right path, and he'd like to give the 100 point bounty to you for the hint -- rush back and amend your answer in the next 18 hours. :)Gowen
Nah, it wasn't really an answer, just a hint. I'm glad that I could help. Cheers!Sergu
M
5

In the end, it was lack of memory. The "new uint8[dataLength];" never returned an existent pointer, thus the whole process went corrupted.

TomA, your idea of clearing the buffer actually helped me to solve the problem. Thanks.

Mercola answered 30/11, 2011 at 11:57 Comment(0)
A
5

That is a driver bug. Simple as that.

The driver got the pitch of the surface in the video memory wrong. You can clearly see this in the upper lines. Also the garbage you see at the lower part of the image is the memory where the driver thinks the image is stored but there is different data there. Textures / Vertex data maybe.

And sorry, I know of no way to fix that. You may have better luck with a different surface-format or by enabling/disabling multisampling.

Airdrop answered 27/11, 2011 at 19:24 Comment(2)
I wish this was true, the corruption is apparent in both iOS and Android, so something else must be wrong on my side...Mercola
Bill, it is most likely the same SGX 3D-chip with the same ARM driver on Android and iOS.Airdrop
M
5

In the end, it was lack of memory. The "new uint8[dataLength];" never returned an existent pointer, thus the whole process went corrupted.

TomA, your idea of clearing the buffer actually helped me to solve the problem. Thanks.

Mercola answered 30/11, 2011 at 11:57 Comment(0)
A
3

I don't know about android or the SDK you're using, but on IOS when I take a screenshot I have to make the buffer the size of the next POT texture, something like this:

int x = NextPot((int)screenSize.x*retina);
int y = NextPot((int)screenSize.y*retina);

void *buffer = malloc( x * y * 4 );

glReadPixels(0,0,x,y,GL_RGBA,GL_UNSIGNED_BYTE,buffer);

The function NextPot just gives me the next POT size, so if the screen size was 320x480, the x,y would be 512x512.

Maybe what your seeing is the wrap around of the buffer because it's expecting a bigger buffer size ?

Also this could be a reason for it to work in the simulator and not on the device, my graphics card doesn't have the POT size limitation and I get similar (weird looking) result.

Altheta answered 28/11, 2011 at 10:34 Comment(0)
D
3

What I assume is happening is that you are trying to use glReadPixels on the window that is covered. If the view area is covered, then the result of glReadPixels is undefined.

See How do I use glDrawPixels() and glReadPixels()? and The Pixel Ownership Problem.

As said here :

The solution is to make an offscreen buffer (FBO) and render to the FBO.

Another option is to make sure the window is not covered when you use glReadPixels.

Dasha answered 30/11, 2011 at 10:31 Comment(0)
E
1

I don't have a solution for fixing glReadPixels. My suggestion is that you change your algorithm to avoid the need to read the data back from the screen.

Take a look at this page. These guys have done a page flip effect all in Flash. It's all in 2D, the illusion is achieved just with shadow gradients.

I think you can use a similar approach, but a little better in 3D. Basically you have to split the effect into three parts: the front facing top page (clouds), the bottom page (the girl) and the back side of the front page. You have to draw each part separately. You can easily draw the front facing top page and the bottom page together in the same screen, you just need to invoke the drawing code for each with a preset clipping region that is aligned with the split line where the top page bends. After you have to top and back page sections drawn, you can draw the gray back facing portion on top, also aligned to the split line.

With this approach the only thing you lose is a little bit of deformation where the clouds image starts to bend up, of course no deformation will occur with my method. Hopefully that will not diminish the effect, I think the shadows are way more important to give the depth effect and will hide this minor inconsistency.

Eros answered 30/11, 2011 at 7:11 Comment(0)
A
1

I am getting screenshoot of my android game without any problems on android device using glReadPixels.

I am not sure yet what's the problem in your case, need more information. So lets start:

  1. I would recommend you not to specify PixelStore format. I am worried about your alignment in 1 byte, do you really "use it"/"know what does it do"? It seems you get exactly what you specify - one extra byte(look at your image, there one extra pixel all the time!) instead of fully packed image. SO try to remove this:

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);

  2. I am not sure in C code, as I was working only in java, but this looks as possible point:

    // width/height deviceWidth = IwGxGetDeviceWidth(); deviceHeight = IwGxGetDeviceHeight();

Are you getting device size? You should use your OpenGL surface size, like this:

public void onSurfaceChanged(GL10 gl, int width, int height) {
int surfaceWidth = width;
int surfaceHeight = height;
}
  1. What are you doing next with captured image? Are you aware that memory block you got from opengl is RGBA, but all not-opengl image operations expect ARGB? For example here in your code you expect alpha to be first bit, not last:

    img->SetFormat(CIwImage::ABGR_8888);

  2. In case if 1, 2 and 3 did not help you might want to save the captured screen to phone sdcard to examine later. I have a program that converts opengl RGBA block to normal bitmap to examine on PC. I may share it with you.

Ataghan answered 30/11, 2011 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.