diff options
Diffstat (limited to 'src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java')
-rw-r--r-- | src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java b/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java new file mode 100644 index 000000000..6f5fa3eed --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java @@ -0,0 +1,593 @@ +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}. + * <p> + * It utilizes the context lifecycle hook {@link #contextRealized(GLContext, boolean)} + * to initialize the {@link FBObject} instance. + * </p> + * <p> + * It utilizes the context current hook {@link #contextMadeCurrent(GLContext, boolean) contextMadeCurrent(context, true)} + * to {@link FBObject#bind(GL) bind} the FBO. + * </p> + * 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; + protected static final boolean DEBUG_SWAP; + + static { + Debug.initSingleton(); + DEBUG = GLDrawableImpl.DEBUG || Debug.debug("FBObject"); + DEBUG_SWAP = DEBUG || 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; + /** Indicated whether the FBO is bound. */ + private boolean fboBound; + /** Indicated whether the FBO is swapped, resets to false after makeCurrent -> contextMadeCurrent. */ + private boolean fboSwapped; + + /** 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 = 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 ) { + if( DEBUG ) { + System.err.println("GLFBODrawableImpl.initialize(): WARNING - Already unrealized!"); + Thread.dumpStack(); + } + return; // NOP, no exception for de-init twice or no init! + } + if( initialized == realize ) { + throw new IllegalStateException("initialize already in state "+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<fbosN; i++) { + fbos[i] = new FBObject(); + fbos[i].reset(gl, getWidth(), getHeight(), samples, false); + if(fbos[i].getNumSamples() != samples) { + throw new InternalError("Sample number mismatch: "+samples+", fbos["+i+"] "+fbos[i]); + } + if(samples > 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); + + fbos[0].formatToGLCapabilities(chosenFBOCaps); + chosenFBOCaps.setDoubleBuffered( chosenFBOCaps.getDoubleBuffered() || samples > 0 ); + } else { + for(int i=0; i<fbos.length; i++) { + fbos[i].destroy(gl); + } + fbos=null; + } + fboBound = false; + fboSwapped = false; + pendingFBOReset = -1; + initialized = realize; + + if(DEBUG) { + System.err.println("GLFBODrawableImpl.initialize("+realize+"): "+this); + Thread.dumpStack(); + } + } + + public final void setSwapBufferContext(SwapBufferContext sbc) { + swapBufferContext = sbc; + } + + private final void reset(GL gl, int idx, int width, int height, int samples, int alphaBits, int stencilBits) { + if( !fboResetQuirk ) { + try { + fbos[idx].reset(gl, width, height, samples, false); + if(fbos[idx].getNumSamples() != samples) { + throw new InternalError("Sample number mismatch: "+samples+", fbos["+idx+"] "+fbos[idx]); + } + return; + } catch (GLException e) { + fboResetQuirk = true; + if(DEBUG) { + if(!resetQuirkInfoDumped) { + resetQuirkInfoDumped = true; + System.err.println("GLFBODrawable: FBO Reset failed: "+e.getMessage()); + System.err.println("GLFBODrawable: Enabling FBOResetQuirk, due to GL driver bug."); + final JoglVersion joglVersion = JoglVersion.getInstance(); + if(DEBUG) { + System.err.println(VersionUtil.getPlatformInfo()); + System.err.println(joglVersion.toString()); + System.err.println(JoglVersion.getGLInfo(gl, null)); + } else { + System.err.println(joglVersion.getBriefOSGLBuildInfo(gl, null)); + } + e.printStackTrace(); + } + } + // 'fallthrough' intended + } + } + // resetQuirk fallback + fbos[idx].destroy(gl); + fbos[idx] = new FBObject(); + fbos[idx].reset(gl, getWidth(), getHeight(), samples, false); + if(fbos[idx].getNumSamples() != samples) { + throw new InternalError("Sample number mismatch: "+samples+", fbos["+idx+"] "+fbos[idx]); + } + if(samples > 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 + fboSwapped = false; + try { + final int maxSamples = gl.getMaxRenderbufferSamples(); + newSamples = newSamples <= maxSamples ? newSamples : maxSamples; + + if(0==samples && 0<newSamples || 0<samples && 0==newSamples) { + // MSAA on/off switch + if(DEBUG) { + System.err.println("GLFBODrawableImpl.reset(): samples [on/off] reconfig: "+samples+" -> "+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<fbos.length; i++) { + if( pendingFBOReset != i ) { + reset(gl, i, nWidth, nHeight, samples, caps.getAlphaBits(), caps.getStencilBits()); + } + } + final GLCapabilities fboCapsNative = (GLCapabilities) surface.getGraphicsConfiguration().getChosenCapabilities(); + fbos[0].formatToGLCapabilities(fboCapsNative); + } + } catch (Throwable t) { + tFBO = t; + } finally { + try { + ourContext.release(); + if(ctxSwitch) { + curContext.makeCurrent(); + } + } catch (Throwable t) { + tGL = t; + } + } + if(null != tFBO) { + throw new GLException("GLFBODrawableImpl.reset(..) FBObject.reset(..) exception", tFBO); + } + if(null != tGL) { + throw new GLException("GLFBODrawableImpl.reset(..) GLContext.release() exception", tGL); + } + if(DEBUG) { + System.err.println("GLFBODrawableImpl.reset(newSamples "+newSamples+"): END "+this); + } + } + + // + // GLDrawable + // + + @Override + public final GLContext createContext(GLContext shareWith) { + final GLContext ctx = parent.createContext(shareWith); + ctx.setGLDrawable(this, false); + return ctx; + } + + // + // GLDrawableImpl + // + + @Override + public final GLDynamicLookupHelper getGLDynamicLookupHelper() { + return parent.getGLDynamicLookupHelper(); + } + + @Override + protected final int getDefaultDrawFramebuffer() { return initialized ? fbos[fboIBack].getWriteFramebuffer() : 0; } + + @Override + protected final int getDefaultReadFramebuffer() { return initialized ? fbos[fboIFront].getReadFramebuffer() : 0; } + + @Override + protected final int getDefaultReadBuffer(GL gl, boolean hasDedicatedDrawableRead) { + return initialized ? fbos[fboIFront].getDefaultReadBuffer() : GL.GL_COLOR_ATTACHMENT0 ; + } + + @Override + protected final void setRealizedImpl() { + final MutableGraphicsConfiguration msConfig = (MutableGraphicsConfiguration) surface.getGraphicsConfiguration(); + if(realized) { + parent.setRealized(true); + origParentChosenCaps = (GLCapabilitiesImmutable) msConfig.getChosenCapabilities(); + final GLCapabilities chosenFBOCaps = (GLCapabilities) origParentChosenCaps.cloneMutable(); + chosenFBOCaps.copyFrom(getRequestedGLCapabilities()); + msConfig.setChosenCapabilities(chosenFBOCaps); + } else { + msConfig.setChosenCapabilities(origParentChosenCaps); + parent.setRealized(false); + } + } + + @Override + protected void associateContext(GLContext glc, boolean bound) { + initialize(bound, glc.getGL()); + } + + @Override + protected final void contextMadeCurrent(GLContext glc, boolean current) { + final GL gl = glc.getGL(); + if(current) { + if( !initialized ) { + throw new GLException("Not initialized: "+this); + } + fbos[fboIBack].bind(gl); + fboBound = true; + fboSwapped = false; + } else if( fboBound && !fboSwapped ) { + swapFBOImpl(glc); + swapFBOImplPost(glc); + fboBound=false; + fboSwapped=true; + if(DEBUG_SWAP) { + System.err.println("Post FBO swap(@release): done"); + } + } + } + + @Override + protected void swapBuffersImpl(boolean doubleBuffered) { + final GLContext ctx = GLContext.getCurrent(); + boolean doPostSwap; + if( null != ctx && ctx.getGLDrawable() == this && fboBound ) { + swapFBOImpl(ctx); + doPostSwap = true; + fboSwapped = true; + if(DEBUG_SWAP) { + System.err.println("Post FBO swap(@swap): done"); + } + } else { + doPostSwap = false; + } + if( null != swapBufferContext ) { + swapBufferContext.swapBuffers(doubleBuffered); + } + if(doPostSwap) { + swapFBOImplPost(ctx); + } + } + + private final void swapFBOImplPost(GLContext glc) { + // Safely reset the previous front FBO - after completing propagating swap + if(0 <= pendingFBOReset) { + final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); + reset(glc.getGL(), pendingFBOReset, getWidth(), getHeight(), samples, caps.getAlphaBits(), caps.getStencilBits()); + pendingFBOReset = -1; + } + } + + private final void swapFBOImpl(GLContext glc) { + final GL gl = glc.getGL(); + fbos[fboIBack].markUnbound(); // fast path, use(gl,..) is called below + + if(DEBUG) { + int _fboIFront = ( fboIFront + 1 ) % fbos.length; + if(_fboIFront != fboIBack) { throw new InternalError("XXX: "+_fboIFront+"!="+fboIBack); } + } + fboIFront = fboIBack; + fboIBack = ( fboIBack + 1 ) % fbos.length; + + final Colorbuffer colorbuffer = samples > 0 ? fbos[fboIFront].getSamplingSink() : fbos[fboIFront].getColorbuffer(0); + final TextureAttachment texAttachment; + if(colorbuffer instanceof TextureAttachment) { + texAttachment = (TextureAttachment) colorbuffer; + } else { + if(null == colorbuffer) { + throw new GLException("Front colorbuffer is null: samples "+samples+", "+this); + } else { + throw new GLException("Front colorbuffer is not a texture: "+colorbuffer.getClass().getName()+": samples "+samples+", "+colorbuffer+", "+this); + } + } + gl.glActiveTexture(GL.GL_TEXTURE0 + texUnit); + fbos[fboIFront].use(gl, texAttachment); + + /* Included in above use command: + gl.glBindFramebuffer(GL2GL3.GL_DRAW_FRAMEBUFFER, fbos[fboIBack].getDrawFramebuffer()); + gl.glBindFramebuffer(GL2GL3.GL_READ_FRAMEBUFFER, fbos[fboIFront].getReadFramebuffer()); + } */ + + if(DEBUG_SWAP) { + System.err.println("Post FBO swap(X): fboI back "+fboIBack+", front "+fboIFront+", num "+fbos.length); + } + } + + // + // GLFBODrawable + // + + @Override + public final boolean isInitialized() { + return initialized; + } + + @Override + public final void resetSize(GL gl) throws GLException { + reset(gl, samples); + } + + @Override + public final int getTextureUnit() { return texUnit; } + + @Override + public final void setTextureUnit(int u) { texUnit = u; } + + @Override + public final int getNumSamples() { return samples; } + + @Override + public void setNumSamples(GL gl, int newSamples) throws GLException { + if(samples != newSamples) { + reset(gl, newSamples); + } + } + + @Override + public final int setNumBuffers(int bufferCount) throws GLException { + // FIXME: Implement + return bufferCount; + } + + @Override + public final int getNumBuffers() { + return bufferCount; + } + + /** // TODO: Add or remove TEXTURE (only) DoubleBufferMode support + @Override + public final DoubleBufferMode getDoubleBufferMode() { + return doubleBufferMode; + } + + @Override + public final void setDoubleBufferMode(DoubleBufferMode mode) throws GLException { + if(initialized) { + throw new GLException("Not allowed past initialization: "+this); + } + final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); + if(0 == samples && caps.getDoubleBuffered() && DoubleBufferMode.NONE != mode) { + doubleBufferMode = mode; + } + } */ + + @Override + public FBObject getFBObject(int bufferName) throws IllegalArgumentException { + if(!initialized) { + return null; + } + final FBObject res; + switch(bufferName) { + case GL.GL_FRONT: + if( samples > 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 "+surface+ + "]"; + } + + 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: ("+getThreadName()+"): "+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(); + } + } + } +} |