summaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java
blob: 2bc682324a0a4bbc1e47439287a0f2129372a152 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
package jogamp.opengl;

import com.jogamp.nativewindow.NativeSurface;
import com.jogamp.nativewindow.NativeWindowException;
import com.jogamp.nativewindow.ProxySurface;
import com.jogamp.nativewindow.UpstreamSurfaceHook;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLCapabilitiesImmutable;
import com.jogamp.opengl.GLContext;
import com.jogamp.opengl.GLException;
import com.jogamp.opengl.GLFBODrawable;

import com.jogamp.common.ExceptionUtils;
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.GLRendererQuirks;
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 = PropertyAccess.isPropertyDefined("jogl.debug.FBObject.Swap", true);
    }

    private final GLDrawableImpl parent;
    private GLCapabilitiesImmutable origParentChosenCaps;

    private boolean initialized;
    private int maxSamples;
    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;
    /** Indicates whether the FBO is bound. */
    private boolean fboBound;
    /** Indicates 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 FBObject.Attachment.StorageDefinition colorRenderbufferStorageDef;
    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 if valid, i.e. >= 0, signals using a color texturebuffer {@link GLFBODrawable#FBOMODE_USE_TEXTURE}, otherwise a color renderbuffer is used.
     */
    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 = textureUnit>=0 ? FBOMODE_USE_TEXTURE : 0;

        this.parent = parent;
        this.origParentChosenCaps = getChosenGLCapabilities(); // just to avoid null, will be reset at initialize(..)
        this.texUnit = textureUnit>=0 ? textureUnit : 0;
        this.samples = fboCaps.getNumSamples();
        this.fboResetQuirk = false;
        this.colorRenderbufferStorageDef = null;
        this.swapBufferContext = null;
    }

    private final void setupFBO(final GL gl, final int idx, final int width, final int height, final int samples,
                                final boolean useAlpha, final int depthBits, final int stencilBits,
                                final boolean useTexture, final boolean setupViewportScissors, final boolean realUnbind) {
        final FBObject fbo = new FBObject();
        fbos[idx] = fbo;

        final boolean useDepth   = depthBits > 0;
        final boolean useStencil = stencilBits > 0;
        FBObject.ColorAttachment pCA = null;

        fbo.init(gl, width, height, samples);
        if(fbo.getNumSamples() != samples) {
            throw new InternalError("Sample number mismatch: "+samples+", fbos["+idx+"] "+fbo);
        }
        if(samples > 0 || !useTexture) {
            final FBObject.ColorAttachment ca = fbo.createColorAttachment(useAlpha);
            if( null != colorRenderbufferStorageDef ) {
                ca.setStorageDefinition(colorRenderbufferStorageDef);
                pCA = ca;
            }
            fbo.attachColorbuffer(gl, 0, ca);
        } else {
            fbo.attachTexture2D(gl, 0, useAlpha);
        }
        if( useStencil ) {
            if( useDepth ) {
                fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, depthBits);
            } else {
                fbo.attachRenderbuffer(gl, Attachment.Type.STENCIL, stencilBits);
            }
        } else if( useDepth ) {
            fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH, depthBits);
        }
        if(samples > 0) {
            final FBObject ssink = new FBObject();
            {
                ssink.init(gl, width, height, 0);
                if( !useTexture ) {
                    final FBObject.ColorAttachment ca = ssink.createColorAttachment(useAlpha);
                    if( null != colorRenderbufferStorageDef ) {
                        ca.setStorageDefinition(colorRenderbufferStorageDef);
                    }
                    ssink.attachColorbuffer(gl, 0, ca);
                } else {
                    ssink.attachTexture2D(gl, 0, useAlpha);
                }
                if( useStencil ) {
                    if( useDepth ) {
                        ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, depthBits);
                    } else {
                        ssink.attachRenderbuffer(gl, Attachment.Type.STENCIL, stencilBits);
                    }
                } else if( useDepth ) {
                    ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, depthBits);
                }
            }
            fbo.setSamplingSink(ssink);
            fbo.resetSamplingSink(gl); // validate
        }
        // Clear the framebuffer allowing defined state not exposing previous content.
        // Also remedy for Bug 1020, i.e. OSX/Nvidia's FBO needs to be cleared before blitting,
        // otherwise first MSAA frame lacks antialiasing.
        fbo.bind(gl);

        if( null != pCA ) {
            // FIXME: Apple iOS EAGLLayer doesn't show any content if the
            // last bound RENDERBUFFER isn't the color renderbuffer,
            // i.e. a subsequent bound DEPTH buffer has been attached.
            gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, pCA.getName());
        }

        if( setupViewportScissors ) {
            // Surfaceless: Set initial viewport/scissors
            gl.glViewport(0, 0, width, height);
            gl.glScissor(0, 0, width, height);
        }
        if( useDepth ) {
            gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        } else {
            gl.glClear(GL.GL_COLOR_BUFFER_BIT);
        }
        if( realUnbind ) {
            fbo.unbind(gl);
        } else {
            fbo.markUnbound();
        }
    }

    private final void initialize(final boolean realize, final GL gl) {
        if( !initialized && !realize ) {
            if( DEBUG ) {
                System.err.println("GLFBODrawableImpl.initialize(): WARNING - Already unrealized!");
                ExceptionUtils.dumpStack(System.err);
            }
            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)

            maxSamples = gl.getMaxRenderbufferSamples(); // if > 0 implies fullFBOSupport
            {
                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

            if( 0 == ( FBOMODE_USE_TEXTURE & fboModeBits ) &&
                gl.getContext().hasRendererQuirk(GLRendererQuirks.BuggyColorRenderbuffer) ) {
                // GLRendererQuirks.BuggyColorRenderbuffer also disables MSAA, i.e. full FBO support
                fboModeBits |= FBOMODE_USE_TEXTURE;
            }

            final boolean useTexture = 0 != ( FBOMODE_USE_TEXTURE & fboModeBits );
            final boolean useAlpha = chosenFBOCaps.getAlphaBits() > 0;
            final int width = getSurfaceWidth();
            final int height = getSurfaceHeight();

            for(int i=0; i<fbosN; i++) {
                setupFBO(gl, i, width, height, samples, useAlpha,
                         chosenFBOCaps.getDepthBits(), chosenFBOCaps.getStencilBits(), useTexture,
                         0==i && 0 == parent.getHandle() /* setupViewportScissors for surfaceless */,
                         fbosN-1==i /* unbind */);
            }
            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);
            ExceptionUtils.dumpStack(System.err);
        }
    }

    public final void setSwapBufferContext(final SwapBufferContext sbc) {
        swapBufferContext = sbc;
    }

    /**
     * Inject custom {@link FBObject.Attachment.StorageDefinition} specialization for the use-case of:
     * <ul>
     *   <li>Using a special <i>color renderbuffer storage</i> instead of the default {@link FBObject} <i>internal offscreen</i> storage.</li>
     * </ul>
     * @see {@link FBObject.Attachment.StorageDefinition}
     * @see {@link FBObject.Attachment#setStorageDefinition(FBObject.Attachment.StorageDefinition)}
     */
    public final void setColorRenderbufferStorageDef(final FBObject.Attachment.StorageDefinition sd) {
        colorRenderbufferStorageDef = sd;
        if(DEBUG) {
            System.err.println("EAGL.FBODrawable: setColorRenderbufferStorageDef");
        }
    }
    public final boolean hasColorRenderbufferStorageDef(final FBObject.Attachment.StorageDefinition sd) {
        return sd == colorRenderbufferStorageDef;
    }

    private final void reset(final GL gl, final int idx, final int width, final int height, final int samples,
                             final boolean useAlpha, final int depthBits, final int stencilBits) {
        if( !fboResetQuirk ) {
            try {
                fbos[idx].reset(gl, width, height, samples);
                if(fbos[idx].getNumSamples() != samples) {
                    throw new InternalError("Sample number mismatch: "+samples+", fbos["+idx+"] "+fbos[idx]);
                }
                return;
            } catch (final 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();
                        System.err.println(VersionUtil.getPlatformInfo());
                        System.err.println(joglVersion.toString());
                        System.err.println(JoglVersion.getGLInfo(gl, null));
                        e.printStackTrace();
                    }
                }
                // 'fallthrough' intended
            }
        }
        // resetQuirk fallback
        fbos[idx].destroy(gl);
        final boolean useTexture = 0 != ( FBOMODE_USE_TEXTURE & fboModeBits );
        setupFBO(gl, idx, width, height, samples, useAlpha, depthBits, stencilBits, useTexture, false, true);
    }

    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);
            ExceptionUtils.dumpStack(System.err);
        }
        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 {
            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 = 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; i<fbos.length; i++) {
                    if( pendingFBOReset != i ) {
                        reset(gl, i, nWidth, nHeight, samples, caps.getAlphaBits()>0, caps.getDepthBits(), caps.getStencilBits());
                    }
                }
                final GLCapabilities fboCapsNative = (GLCapabilities) surface.getGraphicsConfiguration().getChosenCapabilities();
                fbos[0].formatToGLCapabilities(fboCapsNative);
            }
        } catch (final Throwable t) {
            tFBO = t;
        } finally {
            try {
                ourContext.release();
                if(ctxSwitch) {
                    curContext.makeCurrent();
                }
            } catch (final Throwable t) {
                tGL = t;
            }
        }
        if(null != tFBO) {
            throw GLException.newGLException(tFBO);
        }
        if(null != tGL) {
            throw GLException.newGLException(tGL);
        }
        if(DEBUG) {
            System.err.println("GLFBODrawableImpl.reset(newSamples "+newSamples+"): END "+this);
        }
    }

    //
    // GLDrawable
    //

    @Override
    public final GLContext createContext(final GLContext shareWith) {
        final GLContext ctx = parent.createContext(shareWith);
        ctx.setGLDrawable(this, false);
        return ctx;
    }

    //
    // GLDrawableImpl
    //

    @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 getDefaultDrawBuffer(final GL gl) {
        return initialized ? fbos[fboIBack].getDefaultDrawBuffer() : GL.GL_COLOR_ATTACHMENT0 ;
    }

    @Override
    protected final int getDefaultReadBuffer(final GL gl, final 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(); // incl. <Type>GLCapabilities, 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()>0, caps.getDepthBits(), 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 IllegalStateException, GLException {
        if( isInitialized() ) {
            throw new IllegalStateException("Already initialized: "+this);
        }
        // FIXME: Implement
        return GLFBODrawableImpl.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();
            }
        }
    }
}