/** * 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 http://www.mesa3d.org/brianp/TR.html. * * 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 * http://www.mesa3d.org/brianp/TR.html . I've java-fied it, but * the functionality is the same. *

* Original code Copyright (C) 1997-2005 Brian Paul. Licensed under * BSD-compatible terms with permission of the author. See LICENSE.txt * for license information. *

*

* Enhanced for {@link GL} and {@link GL2ES3}, abstracted to suit {@link TileRenderer} and {@link RandomTileRenderer}. *

*
PMV Matrix Considerations
*

* The PMV matrix needs to be reshaped in user code * after calling {@link #beginTile(GL)}, See {@link #beginTile(GL)}. *

*

* If {@link #attachAutoDrawable(GLAutoDrawable) attaching to} an {@link GLAutoDrawable}, * the {@link TileRendererListener#reshapeTile(TileRendererBase, int, int, int, int, int, int)} method * is being called after {@link #beginTile(GL)} for each rendered tile. * It's implementation shall reshape the PMV matrix according to {@link #beginTile(GL)}. *

*
GL Profile Requirement
*

* 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)}.
* 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.
* 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}. *

*

* 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}. *

* * @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"); /** * Listener for tile renderer events, intended to extend {@link GLEventListener} implementations, * enabling tile rendering via {@link TileRendererBase#attachAutoDrawable(GLAutoDrawable)}. */ public static interface TileRendererListener { /** * The owning {@link GLAutoDrawable} is {@link TileRendererBase#attachAutoDrawable(GLAutoDrawable) attached} * to the given {@link TileRendererBase} instance. *

* The {@link GLContext} of the {@link TileRendererBase}'s {@link TileRendererBase#getAttachedDrawable() attached} {@link GLAutoDrawable} * is not current. *

* @param tr the associated {@link TileRendererBase} * @see TileRendererBase#getAttachedDrawable() */ public void addTileRendererNotify(TileRendererBase tr); /** * The owning {@link GLAutoDrawable} is {@link TileRendererBase#detachAutoDrawable() detached} * from the given {@link TileRendererBase} instance. *

* The {@link GLContext} of the {@link TileRendererBase}'s {@link TileRendererBase#getAttachedDrawable() attached} {@link GLAutoDrawable} * is not current. *

* @param tr the disassociated {@link TileRendererBase} * @see TileRendererBase#getAttachedDrawable() */ public void removeTileRendererNotify(TileRendererBase tr); /** * Called by the {@link TileRendererBase} during tile-rendering via an * {@link TileRendererBase#getAttachedDrawable() attached} {@link GLAutoDrawable}'s * {@link GLAutoDrawable#display()} call for each tile before {@link #display(GLAutoDrawable)}. *

* The PMV Matrix shall be reshaped * according to the given *

* The GL viewport is already set to origin 0/0 and the current tile-size.
* See details in {@link TileRendererBase#beginTile(GL)}.
*

*

* The {@link GLContext} of the {@link TileRendererBase}'s {@link TileRendererBase#getAttachedDrawable() attached} {@link GLAutoDrawable} * is current. *

* @param tr the issuing {@link TileRendererBase} * @param tileX the {@link TileRendererBase#TR_CURRENT_TILE_X_POS current tile's x-pos} * @param tileY the {@link TileRendererBase#TR_CURRENT_TILE_Y_POS current tile's y-pos} * @param tileWidth the {@link TileRendererBase#TR_CURRENT_TILE_WIDTH current tile's width} * @param tileHeight the {@link TileRendererBase#TR_CURRENT_TILE_HEIGHT current tile's height} * @param imageWidth the {@link TileRendererBase#TR_IMAGE_WIDTH final image width} * @param imageHeight the {@link TileRendererBase#TR_IMAGE_HEIGHT final image height} * @see TileRendererBase#getAttachedDrawable() */ public void reshapeTile(TileRendererBase tr, int tileX, int tileY, int tileWidth, int tileHeight, int imageWidth, int imageHeight); /** * Called by the {@link TileRendererBase} during tile-rendering * after {@link TileRendererBase#beginTile(GL)} and before {@link #reshapeTile(TileRendererBase, int, int, int, int, int, int) reshapeTile(..)}. *

* If {@link TileRendererBase} is of type {@link TileRenderer}, * method is called for the first tile of all tiles.
* Otherwise, i.e. {@link RandomTileRenderer}, method is called for each particular tile. *

*

* The {@link GLContext} of the {@link TileRenderer}'s {@link TileRenderer#getAttachedDrawable() attached} {@link GLAutoDrawable} * is current. *

* @param tr the issuing {@link TileRendererBase} */ public void startTileRendering(TileRendererBase tr); /** * Called by the {@link TileRenderer} during tile-rendering * after {@link TileRendererBase#endTile(GL)} and {@link GLAutoDrawable#swapBuffers()}. *

* If {@link TileRendererBase} is of type {@link TileRenderer}, * method is called for the last tile of all tiles.
* Otherwise, i.e. {@link RandomTileRenderer}, method is called for each particular tile. *

*

* The {@link GLContext} of the {@link TileRenderer}'s {@link TileRenderer#getAttachedDrawable() attached} {@link GLAutoDrawable} * is current. *

* @param tr the issuing {@link TileRendererBase} */ public void endTileRendering(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+"]"); sb.append(", isSetup "+isSetup()); return sb; } @Override 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 pname 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 void setImageSize(int width, int height) { imageSize.set(width, 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()); } } /** * Returns true if this instance is setup properly, i.e. {@link #setImageSize(int, int)} .., * and ready for {@link #beginTile(GL)}. * Otherwise returns false. */ public abstract boolean isSetup(); /** * Returns true if end of tiling has been reached, otherwise false. *

* end of tiling criteria is implementation specific and may never be reached. *

*

* User needs to {@link #reset()} tiling after reaching end of tiling * before calling {@link #beginTile(GL)} again. *

*/ public abstract boolean eot(); /** * Method resets implementation's internal state to start of tiling * as required for {@link #beginTile(GL)} if {@link #eot() end of tiling} has been reached. *

* Implementation is a nop where {@link #eot() end of tiling} is never reached. *

*/ public abstract void reset(); /** * Begins rendering a tile. *

* 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)}! *

*

* The PMV Matrix * must be reshaped after this call using: *

*

*

* Use shall render the scene afterwards, concluded with a call to * this renderer {@link #endTile(GL)}. *

*

* User has to comply with the GL profile requirement. *

*

* If {@link #eot() end of tiling} has been reached, * user needs to {@link #reset()} tiling before calling this method. *

* * @param gl The gl context * @throws IllegalStateException if {@link #setImageSize(int, int) image-size} is undefined, * an {@link #isSetup() implementation related setup} has not be performed * or {@ link #eot()} has been reached. See implementing classes. * @throws GLException if {@link #setImageBuffer(GLPixelBuffer) image buffer} is used but gl instance is < {@link GL2ES3} * @see #isSetup() * @see #eot() * @see #reset() */ public abstract void beginTile(GL gl) throws IllegalStateException, GLException; /** * Must be called after rendering the scene, * see {@link #beginTile(GL)}. *

* Please consider {@link #reqPreSwapBuffers(GLCapabilitiesImmutable)} to determine * whether you need to perform {@link GLDrawable#swapBuffers() swap-buffers} before or after * calling this method! *

*

* User has to comply with the GL profile requirement. *

* * @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 gl instance is < {@link GL2ES3} */ public abstract void endTile( GL gl ) throws IllegalStateException, GLException; /** * Determines whether the chosen {@link GLCapabilitiesImmutable} * requires a pre-{@link GLDrawable#swapBuffers() swap-buffers} * before accessing the results, i.e. before {@link #endTile(GL)}. *

* 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.
* Here {@link GLDrawable#swapBuffers() swap-buffers} shall happen after calling {@link #endTile(GL)}, the default. *

*

* However, multisampling offscreen {@link GLFBODrawable}s * utilize {@link GLDrawable#swapBuffers() swap-buffers} to downsample * the multisamples into the readable sampling sink. * In this case, we require a {@link GLDrawable#swapBuffers() swap-buffers} before calling {@link #endTile(GL)}. *

* @param chosenCaps the chosen {@link GLCapabilitiesImmutable} * @return chosenCaps.isFBO() && chosenCaps.getSampleBuffers() */ public final boolean reqPreSwapBuffers(GLCapabilitiesImmutable chosenCaps) { return chosenCaps.isFBO() && chosenCaps.getSampleBuffers(); } /** * Attaches the given {@link GLAutoDrawable} to this tile renderer. *

* The {@link GLAutoDrawable}'s original {@link GLEventListener} are moved to this tile renderer. *

*

* {@link GLEventListeners} not implementing {@link TileRendererListener} are ignored while tile rendering. *

*

* The {@link GLAutoDrawable}'s {@link GLAutoDrawable#getAutoSwapBufferMode() auto-swap mode} is cached * and set to false, since {@link GLAutoDrawable#swapBuffers() swapBuffers()} maybe issued before {@link #endTile(GL)}, * see {@link #reqPreSwapBuffers(GLCapabilitiesImmutable)}. *

*

* This tile renderer's internal {@link GLEventListener} is then added to the attached {@link GLAutoDrawable} * to handle the tile rendering, replacing the original {@link GLEventListener}.
* It's {@link GLEventListener#display(GLAutoDrawable) display} implementations issues: *

*

*

* Consider using {@link #setGLEventListener(GLEventListener, GLEventListener)} to add pre- and post * hooks to be performed on this renderer {@link GLEventListener}.
* The pre-hook is able to allocate memory and setup parameters, since it's called before {@link #beginTile(GL)}.
* The post-hook is able to use the rendering result and can even shutdown tile-rendering, * since it's called after {@link #endTile(GL)}. *

*

* Call {@link #detachAutoDrawable()} to remove the attached {@link GLAutoDrawable} from this tile renderer * and to restore it's original {@link GLEventListener}. *

* @param glad the {@link GLAutoDrawable} to attach. * @throws IllegalStateException if an {@link GLAutoDrawable} is already attached * @see #getAttachedDrawable() * @see #detachAutoDrawable() */ public final void attachAutoDrawable(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; inull if none is attached. *

* If called from {@link TileRendererListener#addTileRendererNotify(TileRendererBase)} * or {@link TileRendererListener#removeTileRendererNotify(TileRendererBase)}, method returns the * just attached or soon to be detached {@link GLAutoDrawable}. *

* @see #attachAutoDrawable(GLAutoDrawable) * @see #detachAutoDrawable() */ public final GLAutoDrawable getAttachedDrawable() { return glad; } /** * Detaches the given {@link GLAutoDrawable} from this tile renderer. * @see #attachAutoDrawable(GLAutoDrawable) * @see #getAttachedDrawable() */ public final void detachAutoDrawable() { if( null != glad ) { glad.removeGLEventListener(tiledGLEL); final int aSz = listenersInit.length; for(int i=0; i