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 ? 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 "+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();
}
}
}
}