1 /**
2  * Copyright 2010 JogAmp Community. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without modification, are
5  * permitted provided that the following conditions are met:
6  *
7  *    1. Redistributions of source code must retain the above copyright notice, this list of
8  *       conditions and the following disclaimer.
9  *
10  *    2. Redistributions in binary form must reproduce the above copyright notice, this list
11  *       of conditions and the following disclaimer in the documentation and/or other materials
12  *       provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
15  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  * The views and conclusions contained in the software and documentation are those of the
25  * authors and should not be interpreted as representing official policies, either expressed
26  * or implied, of JogAmp Community.
27  */
28 
29 package com.jogamp.opengl.util;
30 
31 import java.io.File;
32 import java.io.IOException;
33 
34 import com.jogamp.nativewindow.util.PixelFormat;
35 import com.jogamp.opengl.GL;
36 import com.jogamp.opengl.GL2ES3;
37 import com.jogamp.opengl.GLAutoDrawable;
38 import com.jogamp.opengl.GLDrawable;
39 import com.jogamp.opengl.GLException;
40 
41 import com.jogamp.common.nio.Buffers;
42 import com.jogamp.opengl.util.texture.Texture;
43 import com.jogamp.opengl.util.texture.TextureData;
44 import com.jogamp.opengl.util.GLPixelBuffer;
45 import com.jogamp.opengl.util.GLPixelBuffer.GLPixelAttributes;
46 import com.jogamp.opengl.util.GLPixelBuffer.GLPixelBufferProvider;
47 import com.jogamp.opengl.util.texture.TextureIO;
48 
49 /**
50  * Utility to read out the current FB to TextureData, optionally writing the data back to a texture object.
51  * <p>May be used directly to write the TextureData to file (screenshot).</p>
52  */
53 public class GLReadBufferUtil {
54     protected final GLPixelBufferProvider pixelBufferProvider;
55     protected final Texture readTexture;
56     protected final GLPixelStorageModes psm;
57 
58     protected boolean hasAlpha;
59     protected GLPixelBuffer readPixelBuffer = null;
60     protected TextureData readTextureData = null;
61 
62     /**
63      * @param alpha true for RGBA readPixels, otherwise RGB readPixels. Disclaimer: Alpha maybe forced on ES platforms!
64      * @param write2Texture true if readPixel's TextureData shall be written to a 2d Texture
65      */
GLReadBufferUtil(final boolean alpha, final boolean write2Texture)66     public GLReadBufferUtil(final boolean alpha, final boolean write2Texture) {
67         this(GLPixelBuffer.defaultProviderNoRowStride, alpha, write2Texture);
68     }
69 
GLReadBufferUtil(final GLPixelBufferProvider pixelBufferProvider, final boolean alpha, final boolean write2Texture)70     public GLReadBufferUtil(final GLPixelBufferProvider pixelBufferProvider, final boolean alpha, final boolean write2Texture) {
71         this.pixelBufferProvider = pixelBufferProvider;
72         this.readTexture = write2Texture ? new Texture(GL.GL_TEXTURE_2D) : null ;
73         this.psm = new GLPixelStorageModes();
74         this.hasAlpha = alpha; // preset
75     }
76 
77     /** Returns the {@link GLPixelBufferProvider} used by this instance. */
getPixelBufferProvider()78     public GLPixelBufferProvider getPixelBufferProvider() { return pixelBufferProvider; }
79 
isValid()80     public boolean isValid() {
81       return null!=readTextureData && null!=readPixelBuffer && readPixelBuffer.isValid();
82     }
83 
hasAlpha()84     public boolean hasAlpha() { return hasAlpha; }
85 
getGLPixelStorageModes()86     public GLPixelStorageModes getGLPixelStorageModes() { return psm; }
87 
88     /**
89      * Returns the {@link GLPixelBuffer}, created and filled by {@link #readPixels(GLAutoDrawable, boolean)}.
90      */
getPixelBuffer()91     public GLPixelBuffer getPixelBuffer() { return readPixelBuffer; }
92 
93     /**
94      * rewind the raw pixel ByteBuffer
95      */
rewindPixelBuffer()96     public void rewindPixelBuffer() { if( null != readPixelBuffer ) { readPixelBuffer.rewind(); } }
97 
98     /**
99      * @return the resulting TextureData, filled by {@link #readPixels(GLAutoDrawable, boolean)}
100      */
getTextureData()101     public TextureData getTextureData() { return readTextureData; }
102 
103     /**
104      * @return the Texture object filled by {@link #readPixels(GLAutoDrawable, boolean)},
105      *         if this instance writes to a 2d Texture, otherwise null.
106      * @see #GLReadBufferUtil(boolean, boolean)
107      */
getTexture()108     public Texture getTexture() { return readTexture; }
109 
110     /**
111      * Write the TextureData filled by {@link #readPixels(GLAutoDrawable, boolean)} to file
112      */
write(final File dest)113     public void write(final File dest) {
114         try {
115             TextureIO.write(readTextureData, dest);
116             rewindPixelBuffer();
117         } catch (final IOException ex) {
118             throw new RuntimeException("can not write to file: " + dest.getAbsolutePath(), ex);
119         }
120     }
121 
122     /**
123      * Read the drawable's pixels to TextureData and Texture, if requested at construction.
124      *
125      * @param gl the current GL context object. It's read drawable is being used as the pixel source.
126      * @param mustFlipVertically indicates whether to flip the data vertically or not.
127      *                           The context's drawable {@link GLDrawable#isGLOriented()} state
128      *                           is taken into account.
129      *                           Vertical flipping is propagated to TextureData
130      *                           and handled in a efficient manner there (TextureCoordinates and TextureIO writer).
131      *
132      * @see #GLReadBufferUtil(boolean, boolean)
133      */
readPixels(final GL gl, final boolean mustFlipVertically)134     public boolean readPixels(final GL gl, final boolean mustFlipVertically) {
135         return readPixels(gl, 0, 0, 0, 0, mustFlipVertically);
136     }
137 
138     /**
139      * Read the drawable's pixels to TextureData and Texture, if requested at construction.
140      *
141      * @param gl the current GL context object. It's read drawable is being used as the pixel source.
142      * @param inX readPixel x offset
143      * @param inY readPixel y offset
144      * @param inWidth optional readPixel width value, used if [1 .. drawable.width], otherwise using drawable.width
145      * @param inHeight optional readPixel height, used if [1 .. drawable.height], otherwise using drawable.height
146      * @param mustFlipVertically indicates whether to flip the data vertically or not.
147      *                           The context's drawable {@link GLDrawable#isGLOriented()} state
148      *                           is taken into account.
149      *                           Vertical flipping is propagated to TextureData
150      *                           and handled in a efficient manner there (TextureCoordinates and TextureIO writer).
151      * @see #GLReadBufferUtil(boolean, boolean)
152      */
readPixels(final GL gl, final int inX, final int inY, final int inWidth, final int inHeight, final boolean mustFlipVertically)153     public boolean readPixels(final GL gl, final int inX, final int inY, final int inWidth, final int inHeight, final boolean mustFlipVertically) {
154         final GLDrawable drawable = gl.getContext().getGLReadDrawable();
155         final int width, height;
156         if( 0 >= inWidth || drawable.getSurfaceWidth() < inWidth ) {
157             width = drawable.getSurfaceWidth();
158         } else {
159             width = inWidth;
160         }
161         if( 0 >= inHeight || drawable.getSurfaceHeight() < inHeight ) {
162             height = drawable.getSurfaceHeight();
163         } else {
164             height= inHeight;
165         }
166         return readPixelsImpl(drawable, gl, inX, inY, width, height, mustFlipVertically);
167     }
168 
readPixelsImpl(final GLDrawable drawable, final GL gl, final int inX, final int inY, final int width, final int height, final boolean mustFlipVertically)169     protected boolean readPixelsImpl(final GLDrawable drawable, final GL gl,
170                                      final int inX, final int inY, final int width, final int height,
171                                      final boolean mustFlipVertically) {
172         final int glerr0 = gl.glGetError();
173         if(GL.GL_NO_ERROR != glerr0) {
174             System.err.println("Info: GLReadBufferUtil.readPixels: pre-exisiting GL error 0x"+Integer.toHexString(glerr0));
175         }
176         final int reqCompCount = hasAlpha ? 4 : 3;
177         final PixelFormat.Composition hostPixelComp = pixelBufferProvider.getHostPixelComp(gl.getGLProfile(), reqCompCount);
178         final GLPixelAttributes pixelAttribs = pixelBufferProvider.getAttributes(gl, reqCompCount, true);
179         final int componentCount = pixelAttribs.pfmt.comp.componentCount();
180         hasAlpha = 0 <= pixelAttribs.pfmt.comp.find(PixelFormat.CType.A);
181         final int alignment = 4 == componentCount ? 4 : 1 ;
182         final int internalFormat = 4 == componentCount ? GL.GL_RGBA : GL.GL_RGB;
183 
184         final boolean flipVertically;
185         if( drawable.isGLOriented() ) {
186             flipVertically = mustFlipVertically;
187         } else {
188             flipVertically = !mustFlipVertically;
189         }
190 
191         final int tmp[] = new int[1];
192         final int readPixelSize = GLBuffers.sizeof(gl, tmp, pixelAttribs.pfmt.comp.bytesPerPixel(), width, height, 1, true);
193 
194         boolean newData = false;
195         if( null == readPixelBuffer || readPixelBuffer.requiresNewBuffer(gl, width, height, readPixelSize) ) {
196             readPixelBuffer = pixelBufferProvider.allocate(gl, hostPixelComp, pixelAttribs, true, width, height, 1, readPixelSize);
197             Buffers.rangeCheckBytes(readPixelBuffer.buffer, readPixelSize);
198             try {
199                 readTextureData = new TextureData(
200                            gl.getGLProfile(),
201                            internalFormat,
202                            width, height,
203                            0,
204                            pixelAttribs,
205                            false, false,
206                            flipVertically,
207                            readPixelBuffer.buffer,
208                            null /* Flusher */);
209                 newData = true;
210             } catch (final Exception e) {
211                 readTextureData = null;
212                 readPixelBuffer = null;
213                 throw new RuntimeException("can not fetch offscreen texture", e);
214             }
215         } else {
216             readTextureData.setInternalFormat(internalFormat);
217             readTextureData.setWidth(width);
218             readTextureData.setHeight(height);
219             readTextureData.setPixelAttributes(pixelAttribs);
220         }
221         boolean res = null!=readPixelBuffer && readPixelBuffer.isValid();
222         if(res) {
223             psm.setPackAlignment(gl, alignment);
224             if(gl.isGL2ES3()) {
225                 final GL2ES3 gl2es3 = gl.getGL2ES3();
226                 psm.setPackRowLength(gl2es3, width);
227                 gl2es3.glReadBuffer(gl2es3.getDefaultReadBuffer());
228             }
229             readPixelBuffer.clear();
230             try {
231                 gl.glReadPixels(inX, inY, width, height, pixelAttribs.format, pixelAttribs.type, readPixelBuffer.buffer);
232             } catch(final GLException gle) { res = false; gle.printStackTrace(); }
233             readPixelBuffer.position( readPixelSize );
234             readPixelBuffer.flip();
235             final int glerr1 = gl.glGetError();
236             if(GL.GL_NO_ERROR != glerr1) {
237                 System.err.println("GLReadBufferUtil.readPixels: readPixels error 0x"+Integer.toHexString(glerr1)+
238                                    " "+width+"x"+height+
239                                    ", "+pixelAttribs+
240                                    ", "+readPixelBuffer+", sz "+readPixelSize);
241                 res = false;
242             }
243             if(res && null != readTexture) {
244                 if(newData) {
245                     readTexture.updateImage(gl, readTextureData);
246                 } else {
247                     readTexture.updateSubImage(gl, readTextureData, 0,
248                                                inX, inY, // dst offset
249                                                0,   0,   // src offset
250                                                width, height);
251                 }
252                 readPixelBuffer.rewind();
253             }
254             psm.restore(gl);
255         }
256         return res;
257     }
258 
dispose(final GL gl)259     public void dispose(final GL gl) {
260         if(null != readTexture) {
261             readTexture.destroy(gl);
262             readTextureData = null;
263         }
264         if(null != readPixelBuffer) {
265             readPixelBuffer.dispose();
266             readPixelBuffer = null;
267         }
268     }
269 
270 }
271