2

I want to save a video of what I am showing with openGL using JOGL. To do this, I am writing my frames to pictures as follows and then, once I have saved all frames I'll use ffmpeg. I know that this is not the best approach but I still don't have much clear how to accelerate with tex2dimage and PBOs. Any help in that direction would be very useful.

Anyway, my problem is that if I run the opengl class it works but, if I call this class from another class, then I see that the glReadPixels is trhowing me an error. It always returns more data to buffer than memory has been allocated to my buffer "pixelsRGB". Does anyone know why?

As an example: width = 1042; height=998. Allocated=3.119.748 glPixels returned=3.121.742

public void display(GLAutoDrawable drawable) {
       //Draw things.....

       //bla bla bla
       t++; //This is a time variable for the animation (it says to me the frame).

       //Save frame        
       int width = drawable.getSurfaceWidth();
       int height = drawable.getSurfaceHeight();
       ByteBuffer pixelsRGB = Buffers.newDirectByteBuffer(width * height * 3);
       gl.glReadPixels(0, 0, width,height, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, pixelsRGB);
       BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

       int[] pixels = new int[width * height];

        int firstByte = width * height * 3;
        int sourceIndex;
        int targetIndex = 0;
        int rowBytesNumber = width * 3;

        for (int row = 0; row < height; row++) {
            firstByte -= rowBytesNumber;
            sourceIndex = firstByte;
            for (int col = 0; col < width; col++) {
                int iR = pixelsRGB.get(sourceIndex++);
                int iG = pixelsRGB.get(sourceIndex++);
                int iB = pixelsRGB.get(sourceIndex++);

                pixels[targetIndex++] = 0xFF000000
                    | ((iR & 0x000000FF) << 16)
                    | ((iG & 0x000000FF) << 8)
                    | (iB & 0x000000FF);
            }

        }

        bufferedImage.setRGB(0, 0, width, height, pixels, 0, width);


        File a = new File(t+".png");
        ImageIO.write(bufferedImage, "PNG", a);
 }

NOTE: With pleluron's answer now it works. The good code is:

public void display(GLAutoDrawable drawable) {
       //Draw things.....

       //bla bla bla
       t++; //This is a time variable for the animation (it says to me the frame).

       //Save frame        
       int width = drawable.getSurfaceWidth();
       int height = drawable.getSurfaceHeight();
       ByteBuffer pixelsRGB = Buffers.newDirectByteBuffer(width * height * 4);
       gl.glReadPixels(0, 0, width,height, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, pixelsRGB);
       BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

       int[] pixels = new int[width * height];

        int firstByte = width * height * 4;
        int sourceIndex;
        int targetIndex = 0;
        int rowBytesNumber = width * 4;

        for (int row = 0; row < height; row++) {
            firstByte -= rowBytesNumber;
            sourceIndex = firstByte;
            for (int col = 0; col < width; col++) {
                int iR = pixelsRGB.get(sourceIndex++);
                int iG = pixelsRGB.get(sourceIndex++);
                int iB = pixelsRGB.get(sourceIndex++);

                sourceIndex++;

                pixels[targetIndex++] = 0xFF000000
                    | ((iR & 0x000000FF) << 16)
                    | ((iG & 0x000000FF) << 8)
                    | (iB & 0x000000FF);
            }

        }

        bufferedImage.setRGB(0, 0, width, height, pixels, 0, width);


        File a = new File(t+".png");
        ImageIO.write(bufferedImage, "PNG", a);
 }
Learning from masters
  • 2,032
  • 3
  • 29
  • 42
  • Rather use com.jogamp.opengl.util.GLReadBufferUtil with com.jogamp.opengl.util.texture.TextureIO. If you use it correctly, you can keep using the same buffer (inside a TextureData object) for all images, you get rid of AWT, the JOGL PNG encoder (based on PNGJ) is faster and has a lower memory footprint than the AWT/Swing equivalent. – gouessej Dec 31 '16 at 15:50
  • By the way, FFMPEG and LibAV are already used under the hood in JOGL inside the media player. Maybe you can look at the source code to see how to expose the required methods to write, it would avoid you to use numerous PNG files. – gouessej Dec 31 '16 at 15:53

1 Answers1

4

The default value of GL_PACK_ALIGNMENT set with glPixelStore is 4. It means that each row of pixelsRGB should start at an address that is a multiple of 4, and the width of your buffer (1042) times the number of bytes in a pixel (3) isn't a multiple of 4. Adding a little padding so the next row starts at a multiple of 4 will make the total byte size of your buffer larger than what you expected.

To fix it, set GL_PACK_ALIGNMENT to 1. You could also read the pixels with GL_RGBA and use a larger buffer, since the data is most likely to be stored that way both on the GPU and in BufferedImage.

Edit: BufferedImage doesn't have a convenient 'setRGBA', too bad.

pleluron
  • 733
  • 4
  • 12