diff options
Diffstat (limited to 'src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java')
-rw-r--r-- | src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java | 496 |
1 files changed, 415 insertions, 81 deletions
diff --git a/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java b/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java index 03bc26cbc..de45466f3 100644 --- a/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java @@ -1,142 +1,476 @@ 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.GL2GL3; +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.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; /** - * Offscreen GLDrawable implementation using framebuffer object (FBO) - * as it's offscreen rendering mechanism. + * {@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 { - final GLDrawableImpl parent; - final FBObject fbo; - int texUnit; - int samplesTexUnit = 0; - int width=0, height=0, samples=0; - - protected GLFBODrawableImpl(GLDrawableFactoryImpl factory, GLDrawableImpl parent, - NativeSurface surface, int initialWidth, int initialHeight, int textureUnit) { +public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { + protected static final boolean DEBUG = GLDrawableImpl.DEBUG || Debug.debug("FBObject"); + + private final GLDrawableImpl parent; + + private boolean initialized; + private int texUnit; + private int samples; + + private FBObject[] fbos; + private int fboIBack; // points to GL_BACK buffer + private int fboIFront; // points to GL_FRONT buffer + private FBObject pendingFBOReset = null; + private boolean fboBound; + private static final int bufferCount = 2; // number of FBOs for double buffering. TODO: Possible to configure! + + // private DoubleBufferMode doubleBufferMode; // TODO: Add or remove TEXTURE (only) DoubleBufferMode support + + private SwapBufferContext swapBufferContext; + + public static interface SwapBufferContext { + public void swapBuffers(boolean doubleBuffered); + } + + protected GLFBODrawableImpl(GLDrawableFactoryImpl factory, GLDrawableImpl parent, NativeSurface surface, + GLCapabilitiesImmutable fboCaps, int textureUnit) { super(factory, surface, false); + this.initialized = false; + + // Replace the chosen caps of dummy-surface w/ it's clone and copied values of orig FBO caps request. + // The dummy-surface has already been configured, hence value replace is OK + // and due to cloning, the native GLCapability portion is being preserved. + final MutableGraphicsConfiguration msConfig = (MutableGraphicsConfiguration) surface.getGraphicsConfiguration(); + final GLCapabilities fboCapsNative = (GLCapabilities) msConfig.getChosenCapabilities().cloneMutable(); + fboCapsNative.copyFrom(fboCaps); + msConfig.setChosenCapabilities(fboCapsNative); + this.parent = parent; this.texUnit = textureUnit; - final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); - this.width = initialWidth; - this.height = initialHeight; - this.samples = caps.getNumSamples(); - this.fbo = new FBObject(); + this.samples = fboCaps.getNumSamples(); + + // default .. // TODO: Add or remove TEXTURE (only) DoubleBufferMode support + // this.doubleBufferMode = ( samples > 0 || fboCaps.getDoubleBuffered() ) ? DoubleBufferMode.FBO : DoubleBufferMode.NONE ; + + this.swapBufferContext = null; } - @Override - protected void contextRealized(GLContext glc, boolean realized) { - final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); - final GL gl = glc.getGL(); - if(realized) { - fbo.reset(gl, width, height, samples); - samples = fbo.getNumSamples(); // update, maybe capped + private final void initialize(boolean realize, GL gl) { + if(realize) { + final int maxSamples = gl.getMaxRenderbufferSamples(); + samples = samples <= maxSamples ? samples : maxSamples; + + final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); + final int fbosN; if(samples > 0) { - fbo.attachColorbuffer(gl, 0, caps.getAlphaBits()>0); + fbosN = 1; + } else if( caps.getDoubleBuffered() ) { + fbosN = bufferCount; } else { - fbo.attachTexture2D(gl, 0, caps.getAlphaBits()>0); + fbosN = 1; } - if( caps.getStencilBits() > 0 ) { - fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, 24); - } else { - fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); + + 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); + if(fbos[i].getNumSamples() != samples) { + throw new InternalError("Sample number mismatch: "+samples+", fbos["+i+"] "+fbos[i]); + } + if(samples > 0) { + fbos[i].attachColorbuffer(gl, 0, caps.getAlphaBits()>0); + } else { + fbos[i].attachTexture2D(gl, 0, caps.getAlphaBits()>0); + } + if( caps.getStencilBits() > 0 ) { + fbos[i].attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, 24); + } else { + fbos[i].attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); + } } - } else if(null != fbo) { - fbo.destroy(gl); - } - } - - @Override - protected void contextMadeCurrent(GLContext glc, boolean current) { - final GL gl = glc.getGL(); - if(current) { - fbo.bind(gl); + fbos[fboIFront].syncFramebuffer(gl); + fboBound = false; + final GLCapabilities fboCapsNative = (GLCapabilities) surface.getGraphicsConfiguration().getChosenCapabilities(); + fbos[0].formatToGLCapabilities(fboCapsNative); + fboCapsNative.setDoubleBuffered( fboCapsNative.getDoubleBuffered() || samples > 0 ); + + initialized = true; } else { - fbo.unbind(gl); - final TextureAttachment attachment = samples > 0 ? fbo.getSamplingSink() : (TextureAttachment) fbo.getColorbuffer(0) ; - if(null == attachment) { - throw new GLException("Null texture colorbuffer, samples "+samples+", "+fbo.toString()); - } - gl.glActiveTexture(GL.GL_TEXTURE0 + texUnit); - fbo.use(gl, attachment ); - if( samples > 0) { - gl.glBindFramebuffer(GL2GL3.GL_READ_FRAMEBUFFER, fbo.getReadFramebuffer()); + initialized = false; + + for(int i=0; i<fbos.length; i++) { + fbos[i].destroy(gl); } + fbos=null; + fboBound = false; + pendingFBOReset = null; + } + if(DEBUG) { + System.err.println("GLFBODrawableImpl.initialize("+realize+"): "+this); + Thread.dumpStack(); } } - @Override - protected int getDefaultDrawFramebuffer() { return fbo.getWriteFramebuffer(); } - - @Override - protected int getDefaultReadFramebuffer() { return fbo.getReadFramebuffer(); } - - public FBObject getFBObject() { return fbo; } - - public void setSize(GL gl, int newWidth, int newHeight) throws GLException { - width = newWidth; - height = newHeight; - fbo.reset(gl, width, height, samples); - samples = fbo.getNumSamples(); // update, maybe capped + public final void setSwapBufferContext(SwapBufferContext sbc) { + swapBufferContext = sbc; + } + + private static final void reset(GL gl, FBObject fbo, int fboIdx, int width, int height, int samples) { + fbo.reset(gl, width, height, samples); // implicit glClear(..) + if(fbo.getNumSamples() != samples) { + throw new InternalError("Sample number mismatch: "+samples+", fbos["+fboIdx+"] "+fbo); + } } - public void setSamples(GL gl, int newSamples) throws GLException { - samples = newSamples; - fbo.reset(gl, width, height, samples); - samples = fbo.getNumSamples(); // update, maybe capped + 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(); + 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 || 0<samples && 0==newSamples) { + // MSAA on/off switch + if(DEBUG) { + System.err.println("GLFBODrawableImpl.reset(): samples [on/off] reconfig: "+samples+" -> "+newSamples); + } + initialize(false, gl); + samples = newSamples; + initialize(true, gl); + } else { + if(DEBUG) { + System.err.println("GLFBODrawableImpl.reset(): simple reconfig: "+samples+" -> "+newSamples); + } + final int nWidth = getWidth(); + final int nHeight = getHeight(); + samples = newSamples; + pendingFBOReset = ( 1 < fbos.length ) ? fbos[fboIFront] : null; // pending-front reset only w/ double buffering (or zero samples) + for(int i=0; i<fbos.length; i++) { + if(1 == fbos.length || fboIFront != i) { + reset(gl, fbos[i], i, nWidth, nHeight, samples); + } + } + 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 GLContext createContext(GLContext shareWith) { + public final GLContext createContext(GLContext shareWith) { final GLContext ctx = parent.createContext(shareWith); ctx.setGLDrawable(this, false); return ctx; } + // + // GLDrawableImpl + // + @Override - public GLDynamicLookupHelper getGLDynamicLookupHelper() { + public final GLDynamicLookupHelper getGLDynamicLookupHelper() { return parent.getGLDynamicLookupHelper(); } @Override - protected void swapBuffersImpl() { - } + protected final int getDefaultDrawFramebuffer() { return initialized ? fbos[fboIBack].getWriteFramebuffer() : 0; } + + @Override + protected final int getDefaultReadFramebuffer() { return initialized ? fbos[fboIFront].getReadFramebuffer() : 0; } @Override - protected void setRealizedImpl() { + protected final void setRealizedImpl() { parent.setRealized(realized); - if(realized) { - final MutableGraphicsConfiguration msConfig = (MutableGraphicsConfiguration) surface.getGraphicsConfiguration(); - final GLCapabilitiesImmutable chosenCaps = (GLCapabilitiesImmutable) msConfig.getChosenCapabilities(); - final GLCapabilitiesImmutable chosenFBOCaps = GLGraphicsConfigurationUtil.fixOffscreenGLCapabilities(chosenCaps, true /*FBO*/, false /*PBO*/); - msConfig.setChosenCapabilities(chosenFBOCaps); + } + + @Override + protected final void contextRealized(GLContext glc, boolean realized) { + initialize(realized, glc.getGL()); + } + + @Override + protected final void contextMadeCurrent(GLContext glc, boolean current) { + final GL gl = glc.getGL(); + if(current) { + fbos[fboIBack].bind(gl); + fboBound = true; + } else { + if(fboBound) { + swapFBOImpl(glc); + fboBound=false; + if(DEBUG) { + System.err.println("Post FBO swap(@release): done"); + } + } } } - + @Override - public int getWidth() { - return width; + protected void swapBuffersImpl(boolean doubleBuffered) { + final GLContext ctx = GLContext.getCurrent(); + if(null!=ctx && ctx.getGLDrawable()==this) { + if(fboBound) { + swapFBOImpl(ctx); + fboBound=false; + if(DEBUG) { + System.err.println("Post FBO swap(@swap): done"); + } + } + } + if(null != swapBufferContext) { + swapBufferContext.swapBuffers(doubleBuffered); + } + } + + private final void swapFBOImpl(GLContext glc) { + final GL gl = glc.getGL(); + fbos[fboIBack].markUnbound(); // fast path, use(gl,..) is called below + + // Safely reset the previous front FBO + if(null != pendingFBOReset) { + reset(gl, pendingFBOReset, fboIFront, getWidth(), getHeight(), samples); + pendingFBOReset = null; + } + + 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) { + 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 int getHeight() { - return height; + 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); + } + } + + /** // 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 "+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(); + } + } + } } |