package jogamp.opengl; import javax.media.nativewindow.NativeSurface; import javax.media.nativewindow.NativeWindowException; import javax.media.nativewindow.ProxySurface; import javax.media.nativewindow.UpstreamSurfaceHook; import javax.media.opengl.GL; import javax.media.opengl.GLCapabilities; import javax.media.opengl.GLCapabilitiesImmutable; import javax.media.opengl.GLContext; import javax.media.opengl.GLException; import javax.media.opengl.GLFBODrawable; import com.jogamp.common.util.VersionUtil; import com.jogamp.nativewindow.MutableGraphicsConfiguration; import com.jogamp.opengl.FBObject; import com.jogamp.opengl.FBObject.Attachment; import com.jogamp.opengl.FBObject.Colorbuffer; import com.jogamp.opengl.FBObject.TextureAttachment; import com.jogamp.opengl.JoglVersion; /** * {@link FBObject} offscreen GLDrawable implementation, i.e. {@link GLFBODrawable}. *

* It utilizes the context lifecycle hook {@link #contextRealized(GLContext, boolean)} * to initialize the {@link FBObject} instance. *

*

* It utilizes the context current hook {@link #contextMadeCurrent(GLContext, boolean) contextMadeCurrent(context, true)} * to {@link FBObject#bind(GL) bind} the FBO. *

* See {@link GLFBODrawable} for double buffering details. * * @see GLDrawableImpl#contextRealized(GLContext, boolean) * @see GLDrawableImpl#contextMadeCurrent(GLContext, boolean) * @see GLDrawableImpl#getDefaultDrawFramebuffer() * @see GLDrawableImpl#getDefaultReadFramebuffer() */ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { protected static final boolean DEBUG = GLDrawableImpl.DEBUG || Debug.debug("FBObject"); protected static final boolean DEBUG_SWAP = Debug.isPropertyDefined("jogl.debug.FBObject.Swap", true); private final GLDrawableImpl parent; private GLCapabilitiesImmutable origParentChosenCaps; private boolean initialized; private int texUnit; private int samples; private boolean fboResetQuirk; private FBObject[] fbos; private int fboIBack; // points to GL_BACK buffer private int fboIFront; // points to GL_FRONT buffer private int pendingFBOReset = -1; private boolean fboBound; /** dump fboResetQuirk info only once pre ClassLoader and only in DEBUG mode */ private static volatile boolean resetQuirkInfoDumped = false; /** number of FBOs for double buffering. TODO: Possible to configure! */ private static final int bufferCount = 2; // private DoubleBufferMode doubleBufferMode; // TODO: Add or remove TEXTURE (only) DoubleBufferMode support private SwapBufferContext swapBufferContext; public static interface SwapBufferContext { public void swapBuffers(boolean doubleBuffered); } /** * @param factory * @param parent * @param surface * @param fboCaps the requested FBO capabilities * @param textureUnit */ protected GLFBODrawableImpl(GLDrawableFactoryImpl factory, GLDrawableImpl parent, NativeSurface surface, GLCapabilitiesImmutable fboCaps, int textureUnit) { super(factory, surface, fboCaps, false); this.initialized = false; this.parent = parent; this.origParentChosenCaps = (GLCapabilitiesImmutable) getChosenGLCapabilities(); // just to avoid null, will be reset at initialize(..) this.texUnit = textureUnit; this.samples = fboCaps.getNumSamples(); fboResetQuirk = false; // default .. // TODO: Add or remove TEXTURE (only) DoubleBufferMode support // this.doubleBufferMode = ( samples > 0 || fboCaps.getDoubleBuffered() ) ? DoubleBufferMode.FBO : DoubleBufferMode.NONE ; this.swapBufferContext = null; } private final void initialize(boolean realize, GL gl) { if( initialized == realize ) { throw new InternalError("Already set to initialize := "+realize+": "+this); } if(realize) { final GLCapabilities chosenFBOCaps = (GLCapabilities) getChosenGLCapabilities(); // cloned at setRealized(true) final int maxSamples = gl.getMaxRenderbufferSamples(); { final int newSamples = samples <= maxSamples ? samples : maxSamples; if(DEBUG) { System.err.println("GLFBODrawableImpl.initialize(): samples "+samples+" -> "+newSamples+"/"+maxSamples); } samples = newSamples; } final int fbosN; if(samples > 0) { fbosN = 1; } else if( chosenFBOCaps.getDoubleBuffered() ) { fbosN = bufferCount; } else { fbosN = 1; } fbos = new FBObject[fbosN]; fboIBack = 0; // head fboIFront = fbos.length - 1; // tail for(int i=0; i 0) { fbos[i].attachColorbuffer(gl, 0, chosenFBOCaps.getAlphaBits()>0); } else { fbos[i].attachTexture2D(gl, 0, chosenFBOCaps.getAlphaBits()>0); } if( chosenFBOCaps.getStencilBits() > 0 ) { fbos[i].attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, 24); } else { fbos[i].attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); } } fbos[fboIFront].resetSamplingSink(gl); fboBound = false; fbos[0].formatToGLCapabilities(chosenFBOCaps); chosenFBOCaps.setDoubleBuffered( chosenFBOCaps.getDoubleBuffered() || samples > 0 ); initialized = true; } else { initialized = false; for(int i=0; i 0) { fbos[idx].attachColorbuffer(gl, 0, alphaBits>0); } else { fbos[idx].attachTexture2D(gl, 0, alphaBits>0); } if( stencilBits > 0 ) { fbos[idx].attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, 24); } else { fbos[idx].attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); } } private final void reset(GL gl, int newSamples) throws GLException { if(!initialized) { // NOP if not yet initializes return; } final GLContext curContext = GLContext.getCurrent(); final GLContext ourContext = gl.getContext(); final boolean ctxSwitch = null != curContext && curContext != ourContext; if(DEBUG) { System.err.println("GLFBODrawableImpl.reset(newSamples "+newSamples+"): BEGIN - ctxSwitch "+ctxSwitch+", "+this); Thread.dumpStack(); } Throwable tFBO = null; Throwable tGL = null; ourContext.makeCurrent(); gl.glFinish(); // sync GL command stream fboBound = false; // clear bound-flag immediatly, caused by contextMadeCurrent(..) - otherwise we would swap @ release try { final int maxSamples = gl.getMaxRenderbufferSamples(); newSamples = newSamples <= maxSamples ? newSamples : maxSamples; if(0==samples && 0 "+newSamples+"/"+maxSamples); } initialize(false, gl); samples = newSamples; initialize(true, gl); } else { if(DEBUG) { System.err.println("GLFBODrawableImpl.reset(): simple reconfig: "+samples+" -> "+newSamples+"/"+maxSamples); } final int nWidth = getWidth(); final int nHeight = getHeight(); samples = newSamples; pendingFBOReset = ( 1 < fbos.length ) ? fboIFront : -1; // pending-front reset only w/ double buffering (or zero samples) final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); for(int i=0; i 0 ) { res = fbos[0].getSamplingSinkFBO(); } else { res = fbos[fboIFront]; } break; case GL.GL_BACK: res = fbos[fboIBack]; break; default: throw new IllegalArgumentException(illegalBufferName+toHexString(bufferName)); } return res; } @Override public final TextureAttachment getTextureBuffer(int bufferName) throws IllegalArgumentException { if(!initialized) { return null; } final TextureAttachment res; switch(bufferName) { case GL.GL_FRONT: if( samples > 0 ) { res = fbos[0].getSamplingSink(); } else { res = (TextureAttachment) fbos[fboIFront].getColorbuffer(0); } break; case GL.GL_BACK: if( samples > 0 ) { throw new IllegalArgumentException("Cannot access GL_BACK buffer of MSAA FBO: "+this); } else { res = (TextureAttachment) fbos[fboIBack].getColorbuffer(0); } break; default: throw new IllegalArgumentException(illegalBufferName+toHexString(bufferName)); } return res; } private static final String illegalBufferName = "Only GL_FRONT and GL_BACK buffer are allowed, passed "; @Override public String toString() { return getClass().getSimpleName()+"[Initialized "+initialized+", realized "+isRealized()+", texUnit "+texUnit+", samples "+samples+ ",\n\tFactory "+getFactory()+ ",\n\tHandle "+toHexString(getHandle())+ ",\n\tCaps "+surface.getGraphicsConfiguration().getChosenCapabilities()+ ",\n\tfboI back "+fboIBack+", front "+fboIFront+", num "+(initialized ? fbos.length : 0)+ ",\n\tFBO front read "+getDefaultReadFramebuffer()+", "+getFBObject(GL.GL_FRONT)+ ",\n\tFBO back write "+getDefaultDrawFramebuffer()+", "+getFBObject(GL.GL_BACK)+ ",\n\tSurface "+getNativeSurface()+ "]"; } public static class ResizeableImpl extends GLFBODrawableImpl implements GLFBODrawable.Resizeable { protected ResizeableImpl(GLDrawableFactoryImpl factory, GLDrawableImpl parent, ProxySurface surface, GLCapabilitiesImmutable fboCaps, int textureUnit) { super(factory, parent, surface, fboCaps, textureUnit); } @Override public final void setSize(GLContext context, int newWidth, int newHeight) throws NativeWindowException, GLException { if(DEBUG) { System.err.println("GLFBODrawableImpl.ResizeableImpl setSize: ("+Thread.currentThread().getName()+"): "+newWidth+"x"+newHeight+" - surfaceHandle 0x"+Long.toHexString(getNativeSurface().getSurfaceHandle())); } int lockRes = lockSurface(); if (NativeSurface.LOCK_SURFACE_NOT_READY >= lockRes) { throw new NativeWindowException("Could not lock surface: "+this); } try { // propagate new size final ProxySurface ps = (ProxySurface) getNativeSurface(); final UpstreamSurfaceHook ush = ps.getUpstreamSurfaceHook(); if(ush instanceof UpstreamSurfaceHook.MutableSize) { ((UpstreamSurfaceHook.MutableSize)ush).setSize(newWidth, newHeight); } else { throw new InternalError("GLFBODrawableImpl.ResizableImpl's ProxySurface doesn't hold a UpstreamSurfaceHookMutableSize but "+ush.getClass().getName()+", "+ps+", ush"); } if( null != context && context.isCreated() ) { resetSize(context.getGL()); } } finally { unlockSurface(); } } } }