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.PropertyAccess;
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;
protected static final boolean DEBUG_SWAP;
static {
Debug.initSingleton();
DEBUG = GLDrawableImpl.DEBUG || Debug.debug("FBObject");
DEBUG_SWAP = DEBUG || PropertyAccess.isPropertyDefined("jogl.debug.FBObject.Swap", true);
}
private final GLDrawableImpl parent;
private GLCapabilitiesImmutable origParentChosenCaps;
private boolean initialized;
private int fboModeBits;
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(final GLDrawableFactoryImpl factory, final GLDrawableImpl parent, final NativeSurface surface,
final GLCapabilitiesImmutable fboCaps, final int textureUnit) {
super(factory, surface, fboCaps, false);
this.initialized = false;
this.fboModeBits = FBOMODE_USE_TEXTURE | FBOMODE_USE_DEPTH;
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(final boolean realize, final 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
final boolean useTexture = 0 != ( FBOMODE_USE_TEXTURE & fboModeBits );
final boolean useDepth = 0 != ( FBOMODE_USE_DEPTH & fboModeBits );
final boolean useStencil = chosenFBOCaps.getStencilBits() > 0;
final boolean useAlpha = chosenFBOCaps.getAlphaBits() > 0;
final int width = getSurfaceWidth();
final int height = getSurfaceHeight();
for(int i=0; i 0 || !useTexture) {
fbos[i].attachColorbuffer(gl, 0, useAlpha);
} else {
fbos[i].attachTexture2D(gl, 0, useAlpha);
}
if( useStencil ) {
if( useDepth ) {
fbos[i].attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, 24);
} else {
fbos[i].attachRenderbuffer(gl, Attachment.Type.STENCIL, 24);
}
} else if( useDepth ) {
fbos[i].attachRenderbuffer(gl, Attachment.Type.DEPTH, 24);
}
if(samples > 0) {
final FBObject ssink = new FBObject();
{
ssink.reset(gl, width, height);
if( !useTexture ) {
ssink.attachColorbuffer(gl, 0, useAlpha);
} else {
ssink.attachTexture2D(gl, 0, useAlpha);
}
if( useStencil ) {
if( useDepth ) {
ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, 24);
} else {
ssink.attachRenderbuffer(gl, Attachment.Type.STENCIL, 24);
}
} else if( useDepth ) {
ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24);
}
}
fbos[i].setSamplingSink(ssink);
fbos[i].resetSamplingSink(gl); // validate
}
}
fbos[0].formatToGLCapabilities(chosenFBOCaps);
chosenFBOCaps.setDoubleBuffered( chosenFBOCaps.getDoubleBuffered() || samples > 0 );
} else {
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(final 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+"/"+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 = getSurfaceWidth();
final int nHeight = getSurfaceHeight();
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; iGLCapabilities, e.g. X11GLCapabilities
chosenFBOCaps.copyFrom(getRequestedGLCapabilities()); // copy user values
msConfig.setChosenCapabilities(chosenFBOCaps);
} else {
msConfig.setChosenCapabilities(origParentChosenCaps);
parent.setRealized(false);
}
}
@Override
protected void associateContext(final GLContext glc, final boolean bound) {
initialize(bound, glc.getGL());
}
@Override
protected final void contextMadeCurrent(final GLContext glc, final 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(final 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(final 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, getSurfaceWidth(), getSurfaceHeight(), samples, caps.getAlphaBits(), caps.getStencilBits());
pendingFBOReset = -1;
}
}
private final void swapFBOImpl(final GLContext glc) {
final GL gl = glc.getGL();
fbos[fboIBack].markUnbound(); // fast path, use(gl,..) is called below
if(DEBUG) {
final 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);
if(null == colorbuffer) {
throw new GLException("Front colorbuffer is null: samples "+samples+", "+this);
}
final TextureAttachment texAttachment;
if( colorbuffer.isTextureAttachment() ) {
texAttachment = colorbuffer.getTextureAttachment();
gl.glActiveTexture(GL.GL_TEXTURE0 + texUnit);
} else {
texAttachment = null;
}
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 setFBOMode(final int modeBits) throws IllegalStateException {
if( isInitialized() ) {
throw new IllegalStateException("Already initialized: "+this);
}
this.fboModeBits = modeBits;
}
@Override
public final int getFBOMode() {
return fboModeBits;
}
@Override
public final void resetSize(final GL gl) throws GLException {
reset(gl, samples);
}
@Override
public final int getTextureUnit() { return texUnit; }
@Override
public final void setTextureUnit(final int u) { texUnit = u; }
@Override
public final int getNumSamples() { return samples; }
@Override
public void setNumSamples(final GL gl, final int newSamples) throws GLException {
if(samples != newSamples) {
reset(gl, newSamples);
}
}
@Override
public final int setNumBuffers(final 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(final 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 Colorbuffer getColorbuffer(final int bufferName) throws IllegalArgumentException {
if(!initialized) {
return null;
}
final Colorbuffer res;
switch(bufferName) {
case GL.GL_FRONT:
if( samples > 0 ) {
res = fbos[0].getSamplingSink();
} else {
res = 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 = 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(final GLDrawableFactoryImpl factory, final GLDrawableImpl parent, final ProxySurface surface,
final GLCapabilitiesImmutable fboCaps, final int textureUnit) {
super(factory, parent, surface, fboCaps, textureUnit);
}
@Override
public final void setSurfaceSize(final GLContext context, final int newWidth, final int newHeight) throws NativeWindowException, GLException {
if(DEBUG) {
System.err.println("GLFBODrawableImpl.ResizeableImpl setSize: ("+getThreadName()+"): "+newWidth+"x"+newHeight+" - surfaceHandle 0x"+Long.toHexString(getNativeSurface().getSurfaceHandle()));
}
final 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).setSurfaceSize(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();
}
}
}
}