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