/** * Copyright 2013 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. * * --------------------- * * Based on Brian Paul's tile rendering library, found * at <a href = "http://www.mesa3d.org/brianp/TR.html">http://www.mesa3d.org/brianp/TR.html</a>. * * Copyright (C) 1997-2005 Brian Paul. * Licensed under BSD-compatible terms with permission of the author. * See LICENSE.txt for license information. */ package com.jogamp.opengl.util; import javax.media.nativewindow.util.Dimension; import javax.media.nativewindow.util.DimensionImmutable; import javax.media.opengl.GL; import javax.media.opengl.GL2ES3; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLCapabilitiesImmutable; import javax.media.opengl.GLDrawable; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; import javax.media.opengl.GLFBODrawable; import jogamp.opengl.Debug; /** * A fairly direct port of Brian Paul's tile rendering library, found * at <a href = "http://www.mesa3d.org/brianp/TR.html"> * http://www.mesa3d.org/brianp/TR.html </a> . I've java-fied it, but * the functionality is the same. * <p> * Original code Copyright (C) 1997-2005 Brian Paul. Licensed under * BSD-compatible terms with permission of the author. See LICENSE.txt * for license information. * </p> * <p> * Enhanced for {@link GL} and {@link GL2ES3}, abstracted to suit {@link TileRenderer} and {@link RandomTileRenderer}. * </p> * <a name="pmvmatrix"><h5>PMV Matrix Considerations</h5></a> * <p> * The PMV matrix needs to be reshaped in user code * after calling {@link #beginTile(GL)}, See {@link #beginTile(GL)}. * </p> * <p> * If {@link #attachToAutoDrawable(GLAutoDrawable) attaching to} an {@link GLAutoDrawable}, * the {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)} method * is being called after {@link #beginTile(GL)}. * It's implementation shall reshape the PMV matrix according to {@link #beginTile(GL)}. * </p> * <a name="glprequirement"><h5>GL Profile Requirement</h5></a> * <p> * Note that {@link #setImageBuffer(GLPixelBuffer) image buffer} can only be used * in conjunction w/ a {@link GL} instance ≥ {@link GL2ES3} passed to {@link #beginTile(GL)} and {@link #endTile(GL)}.<br> * This is due to setting up the {@link GL2ES3#GL_PACK_ROW_LENGTH pack row length} * for an {@link #setImageSize(int, int) image width} != tile-width, which usually is the case.<br> * Hence a {@link GLException} is thrown in both methods, * if using an {@link #setImageBuffer(GLPixelBuffer) image buffer} * and passing a {@link GL} instance < {@link GL2ES3}. * </p> * <p> * Further more, reading back of MSAA buffers is only supported since {@link GL2ES3} * since it requires to set the {@link GL2ES3#glReadBuffer(int) read-buffer}. * </p> * * @author ryanm, sgothel */ public abstract class TileRendererBase { /** * The width of the final image. See {@link #getParam(int)}. */ public static final int TR_IMAGE_WIDTH = 1; /** * The height of the final image. See {@link #getParam(int)}. */ public static final int TR_IMAGE_HEIGHT = 2; /** * The x-pos of the current tile. See {@link #getParam(int)}. */ public static final int TR_CURRENT_TILE_X_POS = 3; /** * The y-pos of the current tile. See {@link #getParam(int)}. */ public static final int TR_CURRENT_TILE_Y_POS = 4; /** * The width of the current tile. See {@link #getParam(int)}. */ public static final int TR_CURRENT_TILE_WIDTH = 5; /** * The height of the current tile. See {@link #getParam(int)}. */ public static final int TR_CURRENT_TILE_HEIGHT = 6; /* pp */ static final boolean DEBUG = Debug.debug("TileRenderer"); /** * Notifies {@link GLEventListener} implementing this interface * that the owning {@link GLAutoDrawable} is {@link TileRendererBase#attachToAutoDrawable(GLAutoDrawable) attached} * to a tile renderer or {@link TileRendererBase#detachFromAutoDrawable() detached} from it. */ public static interface TileRendererNotify { /** The owning {@link GLAutoDrawable} is {@link TileRendererBase#attachToAutoDrawable(GLAutoDrawable) attached} to a {@link TileRendererBase}. */ public void addTileRendererNotify(TileRendererBase tr); /** The owning {@link GLAutoDrawable} is {@link TileRendererBase#detachFromAutoDrawable() detached} from a {@link TileRendererBase}. */ public void removeTileRendererNotify(TileRendererBase tr); } protected final Dimension imageSize = new Dimension(0, 0); protected final GLPixelStorageModes psm = new GLPixelStorageModes(); protected GLPixelBuffer imageBuffer; protected GLPixelBuffer tileBuffer; protected boolean beginCalled = false; protected int currentTileXPos; protected int currentTileYPos; protected int currentTileWidth; protected int currentTileHeight; protected GLAutoDrawable glad; protected boolean gladRequiresPreSwap; protected boolean gladAutoSwapBufferMode = true; protected GLEventListener[] listeners; protected boolean[] listenersInit; protected GLEventListener glEventListenerPre = null; protected GLEventListener glEventListenerPost = null; private final String hashStr(Object o) { final int h = null != o ? o.hashCode() : 0; return "0x"+Integer.toHexString(h); } protected StringBuilder tileDetails(StringBuilder sb) { return sb.append("cur "+currentTileXPos+"/"+currentTileYPos+" "+currentTileWidth+"x"+currentTileHeight+", buffer "+hashStr(tileBuffer)); } public StringBuilder toString(StringBuilder sb) { final int gladListenerCount = null != listeners ? listeners.length : 0; sb.append("tile["); tileDetails(sb); sb.append("], image[size "+imageSize+", buffer "+hashStr(imageBuffer)+"], glad["+ gladListenerCount+" listener, pre "+(null!=glEventListenerPre)+", post "+(null!=glEventListenerPost)+", preSwap "+gladRequiresPreSwap+"]"); return sb; } public String toString() { StringBuilder sb = new StringBuilder(); return getClass().getSimpleName()+ "["+toString(sb).toString()+"]"; } protected TileRendererBase() { } /** * Gets the parameters of this TileRenderer object * * @param pname The parameter name that is to be retrieved * @return the value of the parameter * @throws IllegalArgumentException if <code>pname</code> is not handled */ public abstract int getParam(int pname) throws IllegalArgumentException; /** * Specify a buffer the tiles to be copied to. This is not * necessary for the creation of the final image, but useful if you * want to inspect each tile in turn. * * @param buffer The buffer itself. Must be large enough to contain a random tile */ public final void setTileBuffer(GLPixelBuffer buffer) { tileBuffer = buffer; if( DEBUG ) { System.err.println("TileRenderer: tile-buffer "+tileBuffer); } } /** @see #setTileBuffer(GLPixelBuffer) */ public final GLPixelBuffer getTileBuffer() { return tileBuffer; } /** * Sets the desired size of the final image * * @param width The width of the final image * @param height The height of the final image */ public final void setImageSize(int width, int height) { imageSize.setWidth(width); imageSize.setHeight(height); } /** @see #setImageSize(int, int) */ public final DimensionImmutable getImageSize() { return imageSize; } /** * Sets the buffer in which to store the final image * * @param buffer the buffer itself, must be large enough to hold the final image */ public final void setImageBuffer(GLPixelBuffer buffer) { imageBuffer = buffer; if( DEBUG ) { System.err.println("TileRenderer: image-buffer "+imageBuffer); } } /** @see #setImageBuffer(GLPixelBuffer) */ public final GLPixelBuffer getImageBuffer() { return imageBuffer; } /* pp */ final void validateGL(GL gl) throws GLException { if( imageBuffer != null && !gl.isGL2ES3()) { throw new GLException("Using image-buffer w/ inssufficient GL context: "+gl.getContext().getGLVersion()+", "+gl.getGLProfile()); } } /** * Begins rendering a tile. * <p> * This method modifies the viewport, see below. * User shall reset the viewport when finishing all tile rendering, * i.e. after very last call of {@link #endTile(GL)}! * </p> * <p> * The <a href="#pmvmatrix">PMV Matrix</a> * must be reshaped after this call using: * <ul> * <li>Current Viewport * <ul> * <li>x 0</li> * <li>y 0</li> * <li>{@link #TR_CURRENT_TILE_WIDTH tile width}</li> * <li>{@link #TR_CURRENT_TILE_HEIGHT tile height}</li> * </ul></li> * <li>{@link #TR_CURRENT_TILE_X_POS tile x-pos}</li> * <li>{@link #TR_CURRENT_TILE_Y_POS tile y-pos}</li> * <li>{@link #TR_IMAGE_WIDTH image width}</li> * <li>{@link #TR_IMAGE_HEIGHT image height}</li> * </ul> * </p> * <p> * Use shall render the scene afterwards, concluded with a call to * this renderer {@link #endTile(GL)}. * </p> * <p> * User has to comply with the <a href="#glprequirement">GL profile requirement</a>. * </p> * * @param gl The gl context * @throws IllegalStateException if image-size has not been set * @throws GLException if {@link #setImageBuffer(GLPixelBuffer) image buffer} is used but <code>gl</code> instance is < {@link GL2ES3} */ public abstract void beginTile(GL gl) throws IllegalStateException, GLException; /** * Must be called after rendering the scene, * see {@link #beginTile(GL)}. * <p> * Please consider {@link #reqPreSwapBuffers(GLCapabilitiesImmutable)} to determine * whether you need to perform {@link GLDrawable#swapBuffers() swap-buffers} before or after * calling this method! * </p> * <p> * User has to comply with the <a href="#glprequirement">GL profile requirement</a>. * </p> * * @param gl the gl context * @throws IllegalStateException if beginTile(gl) has not been called * @throws GLException if {@link #setImageBuffer(GLPixelBuffer) image buffer} is used but <code>gl</code> instance is < {@link GL2ES3} */ public abstract void endTile( GL gl ) throws IllegalStateException, GLException; /** * Determines whether the chosen {@link GLCapabilitiesImmutable} * requires a <i>pre-{@link GLDrawable#swapBuffers() swap-buffers}</i> * before accessing the results, i.e. before {@link #endTile(GL)}. * <p> * Usually one uses the {@link GL#getDefaultReadBuffer() default-read-buffer}, i.e. * {@link GL#GL_FRONT} for single-buffer and {@link GL#GL_BACK} for double-buffer {@link GLDrawable}s * and {@link GL#GL_COLOR_ATTACHMENT0} for offscreen framebuffer objects.<br> * Here {@link GLDrawable#swapBuffers() swap-buffers} shall happen <b>after</b> calling {@link #endTile(GL)}, the default. * </p> * <p> * However, <i>multisampling</i> offscreen {@link GLFBODrawable}s * utilize {@link GLDrawable#swapBuffers() swap-buffers} to <i>downsample</i> * the multisamples into the readable sampling sink. * In this case, we require a {@link GLDrawable#swapBuffers() swap-buffers} <b>before</b> calling {@link #endTile(GL)}. * </p> * @param chosenCaps the chosen {@link GLCapabilitiesImmutable} * @return chosenCaps.isFBO() && chosenCaps.getSampleBuffers() */ public final boolean reqPreSwapBuffers(GLCapabilitiesImmutable chosenCaps) { return chosenCaps.isFBO() && chosenCaps.getSampleBuffers(); } /** * Attaches this renderer to the {@link GLAutoDrawable}. * <p> * The {@link GLAutoDrawable}'s original {@link GLEventListener} are moved to this tile renderer.<br> * It is <i>highly recommended</i> that the original {@link GLEventListener} implement * {@link TileRendererNotify}, so they get {@link TileRendererNotify#addTileRendererNotify(TileRendererBase) notified} * about this event. * </p> * <p> * The {@link GLAutoDrawable}'s {@link GLAutoDrawable#getAutoSwapBufferMode() auto-swap mode} is cached * and set to <code>false</code>, since {@link GLAutoDrawable#swapBuffers() swapBuffers()} maybe issued before {@link #endTile(GL)}, * see {@link #reqPreSwapBuffers(GLCapabilitiesImmutable)}. * </p> * <p> * This tile renderer's {@link GLEventListener} is then added to handle the tile rendering, * replacing the original {@link GLEventListener}.<br> * This {@link GLEventListener#display(GLAutoDrawable) display} implementations issues: * <ul> * <li>Optional {@link #setGLEventListener(GLEventListener, GLEventListener) pre-glel}.{@link GLEventListener#display(GLAutoDrawable) display(..)}</li> * <li>{@link #beginTile(GL)}</li> * <li>for all original {@link GLEventListener}: * <ul> * <li>{@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(0, 0, tile-width, tile-height)}</li> * <li>{@link GLEventListener#display(GLAutoDrawable) display(autoDrawable)}</li> * </ul></li> * <li>if ( {@link #reqPreSwapBuffers(GLCapabilitiesImmutable) pre-swap} ) { {@link GLAutoDrawable#swapBuffers() swapBuffers()} }</li> * <li>{@link #endTile(GL)}</li> * <li>if ( !{@link #reqPreSwapBuffers(GLCapabilitiesImmutable) pre-swap} ) { {@link GLAutoDrawable#swapBuffers() swapBuffers()} }</li> * <li>Optional {@link #setGLEventListener(GLEventListener, GLEventListener) post-glel}.{@link GLEventListener#display(GLAutoDrawable) display(..)}</li> * </ul> * </p> * <p> * The <a href="#pmvmatrix">PMV Matrix</a> shall be reshaped in the * original {@link GLEventListener}'s {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape} method * according to the tile-position, -size and image-size<br> * The {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape} method is called for each tile * w/ the current viewport of tile-size, where the tile-position and image-size can be retrieved by this tile renderer, * see details in {@link #beginTile(GL)}.<br> * The original {@link GLEventListener} implementing {@link TileRendererNotify} is aware of this * tile renderer instance. * </p> * <p> * Consider using {@link #setGLEventListener(GLEventListener, GLEventListener)} to add pre- and post * hooks to be performed on this renderer {@link GLEventListener}.<br> * The pre-hook is able to allocate memory and setup parameters, since it's called before {@link #beginTile(GL)}.<br> * The post-hook is able to use the rendering result and can even shutdown tile-rendering, * since it's called after {@link #endTile(GL)}. * </p> * <p> * Call {@link #detachFromAutoDrawable()} to remove this renderer from the {@link GLAutoDrawable} * and to restore it's original {@link GLEventListener}. * </p> * @param glad * @throws IllegalStateException if an {@link GLAutoDrawable} is already attached */ public void attachToAutoDrawable(GLAutoDrawable glad) throws IllegalStateException { if( null != this.glad ) { throw new IllegalStateException("GLAutoDrawable already attached"); } this.glad = glad; final int aSz = glad.getGLEventListenerCount(); listeners = new GLEventListener[aSz]; listenersInit = new boolean[aSz]; for(int i=0; i<aSz; i++) { final GLEventListener l = glad.getGLEventListener(0); listenersInit[i] = glad.getGLEventListenerInitState(l); listeners[i] = glad.removeGLEventListener( l ); final boolean trn; if( listeners[i] instanceof TileRendererNotify ) { trn = true; ((TileRendererNotify)listeners[i]).addTileRendererNotify(this); } else { trn = false; } if( DEBUG ) { System.err.println("TileRenderer.attach["+i+"]: isInit "+listenersInit[i]+", isTRN "+trn+", "+listeners[i].getClass().getName()); } } glad.addGLEventListener(tiledGLEL); gladAutoSwapBufferMode = glad.getAutoSwapBufferMode(); gladRequiresPreSwap = this.reqPreSwapBuffers(glad.getChosenGLCapabilities()); glad.setAutoSwapBufferMode(false); if( DEBUG ) { System.err.println("TileRenderer: attached: "+glad); System.err.println("TileRenderer: preSwap "+gladRequiresPreSwap+", "+glad.getChosenGLCapabilities()+", cached "+listeners.length+" listener"); } } /** * Detaches this renderer from the {@link GLAutoDrawable}. * <p> * It is <i>highly recommended</i> that the original {@link GLEventListener} implement * {@link TileRendererNotify}, so they get {@link TileRendererNotify#removeTileRendererNotify(TileRendererBase) notified} * about this event. * </p> * <p> * See {@link #attachToAutoDrawable(GLAutoDrawable)}. * </p> */ public void detachFromAutoDrawable() { if( null != glad ) { glad.removeGLEventListener(tiledGLEL); final int aSz = listenersInit.length; for(int i=0; i<aSz; i++) { final GLEventListener l = listeners[i]; if( l instanceof TileRendererNotify ) { ((TileRendererNotify)l).removeTileRendererNotify(this); } glad.addGLEventListener(l); glad.setGLEventListenerInitState(l, listenersInit[i]); } glad.setAutoSwapBufferMode(gladAutoSwapBufferMode); if( DEBUG ) { System.err.println("TileRenderer: detached: "+glad); System.err.println("TileRenderer: "+glad.getChosenGLCapabilities()); } listeners = null; listenersInit = null; glad = null; } } /** * Set {@link GLEventListener} for pre- and post operations when used w/ * {@link #attachToAutoDrawable(GLAutoDrawable)} * for each {@link GLEventListener} callback. * @param preTile the pre operations * @param postTile the post operations */ public void setGLEventListener(GLEventListener preTile, GLEventListener postTile) { glEventListenerPre = preTile; glEventListenerPost = postTile; } /** * Rendering one tile, by simply calling {@link GLAutoDrawable#display()}. * * @throws IllegalStateException if no {@link GLAutoDrawable} is {@link #attachToAutoDrawable(GLAutoDrawable) attached} * or imageSize is not set */ public void display() throws IllegalStateException { if( null == glad ) { throw new IllegalStateException("No GLAutoDrawable attached"); } glad.display(); } private final GLEventListener tiledGLEL = new GLEventListener() { @Override public void init(GLAutoDrawable drawable) { if( null != glEventListenerPre ) { glEventListenerPre.init(drawable); } final int aSz = listenersInit.length; for(int i=0; i<aSz; i++) { final GLEventListener l = listeners[i]; if( !listenersInit[i] ) { l.init(drawable); listenersInit[i] = true; } } if( null != glEventListenerPost ) { glEventListenerPost.init(drawable); } } @Override public void dispose(GLAutoDrawable drawable) { if( null != glEventListenerPre ) { glEventListenerPre.dispose(drawable); } final int aSz = listenersInit.length; for(int i=0; i<aSz; i++) { listeners[i].dispose(drawable); } if( null != glEventListenerPost ) { glEventListenerPost.dispose(drawable); } } @Override public void display(GLAutoDrawable drawable) { if( null != glEventListenerPre ) { glEventListenerPre.reshape(drawable, 0, 0, currentTileWidth, currentTileHeight); glEventListenerPre.display(drawable); } final GL gl = drawable.getGL(); beginTile(gl); final int aSz = listenersInit.length; for(int i=0; i<aSz; i++) { listeners[i].reshape(drawable, 0, 0, currentTileWidth, currentTileHeight); listeners[i].display(drawable); } if( gladRequiresPreSwap ) { glad.swapBuffers(); endTile(gl); } else { endTile(gl); glad.swapBuffers(); } if( null != glEventListenerPost ) { glEventListenerPost.reshape(drawable, 0, 0, currentTileWidth, currentTileHeight); glEventListenerPost.display(drawable); } } @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { } }; }