com.ardor3d.renderer.lwjgl.LwjglTextureRenderer.java Source code

Java tutorial

Introduction

Here is the source code for com.ardor3d.renderer.lwjgl.LwjglTextureRenderer.java

Source

/**
 * Copyright (c) 2008-2012 Ardor Labs, Inc.
 *
 * This file is part of Ardor3D.
 *
 * Ardor3D is free software: you can redistribute it and/or modify it 
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
 */

package com.ardor3d.renderer.lwjgl;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import org.lwjgl.opengl.ARBDepthTexture;
import org.lwjgl.opengl.ARBDrawBuffers;
import org.lwjgl.opengl.EXTFramebufferBlit;
import org.lwjgl.opengl.EXTFramebufferMultisample;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL11;

import com.ardor3d.framework.Scene;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture.Type;
import com.ardor3d.image.TextureCubeMap;
import com.ardor3d.image.TextureCubeMap.Face;
import com.ardor3d.math.type.ReadOnlyColorRGBA;
import com.ardor3d.renderer.AbstractFBOTextureRenderer;
import com.ardor3d.renderer.ContextCapabilities;
import com.ardor3d.renderer.ContextManager;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.TextureRendererFactory;
import com.ardor3d.renderer.state.RenderState;
import com.ardor3d.renderer.state.record.RendererRecord;
import com.ardor3d.renderer.state.record.TextureRecord;
import com.ardor3d.renderer.state.record.TextureStateRecord;
import com.ardor3d.scene.state.lwjgl.LwjglTextureStateUtil;
import com.ardor3d.scene.state.lwjgl.util.LwjglTextureUtil;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.util.Ardor3dException;
import com.ardor3d.util.TextureKey;
import com.ardor3d.util.geom.BufferUtils;

/**
 * <p>
 * This class is used by Ardor3D's LWJGL implementation to render textures. Users should <b>not</b> create this class
 * directly.
 * </p>
 * 
 * @see TextureRendererFactory
 */
public class LwjglTextureRenderer extends AbstractFBOTextureRenderer {
    private static final Logger logger = Logger.getLogger(LwjglTextureRenderer.class.getName());

    public LwjglTextureRenderer(final int width, final int height, final int depthBits, final int samples,
            final Renderer parentRenderer, final ContextCapabilities caps) {
        super(width, height, depthBits, samples, parentRenderer, caps);

        if (caps.getMaxFBOColorAttachments() > 1) {
            _attachBuffer = BufferUtils.createIntBuffer(caps.getMaxFBOColorAttachments());
            for (int i = 0; i < caps.getMaxFBOColorAttachments(); i++) {
                _attachBuffer.put(EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT + i);
            }
        }
    }

    /**
     * <code>setupTexture</code> initializes a new Texture object for use with TextureRenderer. Generates a valid OpenGL
     * texture id for this texture and initializes the data type for the texture.
     */
    public void setupTexture(final Texture tex) {
        if (tex.getType() != Type.TwoDimensional && tex.getType() != Type.CubeMap) {
            throw new IllegalArgumentException("Texture type not supported: " + tex.getType());
        }

        final RenderContext context = ContextManager.getCurrentContext();
        final TextureStateRecord record = (TextureStateRecord) context
                .getStateRecord(RenderState.StateType.Texture);

        // check if we are already setup... if so, throw error.
        if (tex.getTextureKey() == null) {
            tex.setTextureKey(TextureKey.getRTTKey(tex.getMinificationFilter()));
        } else if (tex.getTextureIdForContext(context.getGlContextRep()) != 0) {
            throw new Ardor3dException("Texture is already setup and has id.");
        }

        // Create the texture
        final IntBuffer ibuf = BufferUtils.createIntBuffer(1);
        GL11.glGenTextures(ibuf);
        final int textureId = ibuf.get(0);
        tex.setTextureIdForContext(context.getGlContextRep(), textureId);

        LwjglTextureStateUtil.doTextureBind(tex, 0, true);

        // Initialize our texture with some default data.
        final int internalFormat = LwjglTextureUtil.getGLInternalFormat(tex.getTextureStoreFormat());
        final int dataFormat = LwjglTextureUtil.getGLPixelFormatFromStoreFormat(tex.getTextureStoreFormat());
        final int pixelDataType = LwjglTextureUtil.getGLPixelDataType(tex.getRenderedTexturePixelDataType());

        if (tex.getType() == Type.TwoDimensional) {
            GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, _width, _height, 0, dataFormat, pixelDataType,
                    (ByteBuffer) null);
        } else {
            for (final Face face : Face.values()) {
                GL11.glTexImage2D(LwjglTextureStateUtil.getGLCubeMapFace(face), 0, internalFormat, _width, _height,
                        0, dataFormat, pixelDataType, (ByteBuffer) null);
            }
        }

        // Initialize mipmapping for this texture, if requested
        if (tex.getMinificationFilter().usesMipMapLevels()) {
            EXTFramebufferObject.glGenerateMipmapEXT(LwjglTextureStateUtil.getGLType(tex.getType()));
        }

        // Setup filtering and wrap
        final TextureRecord texRecord = record.getTextureRecord(textureId, tex.getType());
        LwjglTextureStateUtil.applyFilter(tex, texRecord, 0, record, context.getCapabilities());
        LwjglTextureStateUtil.applyWrap(tex, texRecord, 0, record, context.getCapabilities());

        logger.fine("setup fbo tex with id " + textureId + ": " + _width + "," + _height);
    }

    public void render(final Spatial spat, final List<Texture> texs, final int clear) {
        render(null, spat, null, texs, clear);
    }

    public void render(final List<? extends Spatial> spat, final List<Texture> texs, final int clear) {
        render(spat, null, null, texs, clear);
    }

    @Override
    public void render(final Scene scene, final List<Texture> texs, final int clear) {
        render(null, null, scene, texs, clear);
    }

    private void render(final List<? extends Spatial> toDrawA, final Spatial toDrawB, final Scene toDrawC,
            final List<Texture> texs, final int clear) {

        final int maxDrawBuffers = ContextManager.getCurrentContext().getCapabilities().getMaxFBOColorAttachments();

        // if we only support 1 draw buffer at a time anyway, we'll have to render to each texture individually...
        if (maxDrawBuffers == 1 || texs.size() == 1) {
            try {
                ContextManager.getCurrentContext().pushFBOTextureRenderer(this);

                for (int i = 0; i < texs.size(); i++) {
                    final Texture tex = texs.get(i);

                    setupForSingleTexDraw(tex);

                    if (_samples > 0 && _supportsMultisample) {
                        setMSFBO();
                    }

                    switchCameraIn(clear);
                    if (toDrawA != null) {
                        doDraw(toDrawA);
                    } else if (toDrawB != null) {
                        doDraw(toDrawB);
                    } else {
                        doDraw(toDrawC);
                    }
                    switchCameraOut();

                    if (_samples > 0 && _supportsMultisample) {
                        blitMSFBO();
                    }

                    takedownForSingleTexDraw(tex);
                }
            } finally {
                ContextManager.getCurrentContext().popFBOTextureRenderer();
            }
            return;
        }
        try {
            ContextManager.getCurrentContext().pushFBOTextureRenderer(this);

            // Otherwise, we can streamline this by rendering to multiple textures at once.
            // first determine how many groups we need
            final LinkedList<Texture> depths = new LinkedList<Texture>();
            final LinkedList<Texture> colors = new LinkedList<Texture>();
            for (int i = 0; i < texs.size(); i++) {
                final Texture tex = texs.get(i);
                if (tex.getTextureStoreFormat().isDepthFormat()) {
                    depths.add(tex);
                } else {
                    colors.add(tex);
                }
            }
            // we can only render to 1 depth texture at a time, so # groups is at minimum == numDepth
            final int groups = Math.max(depths.size(), (int) Math.ceil(colors.size() / (float) maxDrawBuffers));

            final RenderContext context = ContextManager.getCurrentContext();
            for (int i = 0; i < groups; i++) {
                // First handle colors
                int colorsAdded = 0;
                while (colorsAdded < maxDrawBuffers && !colors.isEmpty()) {
                    final Texture tex = colors.removeFirst();
                    if (tex.getType() == Type.TwoDimensional) {
                        EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                                EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT + colorsAdded, GL11.GL_TEXTURE_2D,
                                tex.getTextureIdForContext(context.getGlContextRep()), 0);
                    } else if (tex.getType() == Type.CubeMap) {
                        EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                                EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT + colorsAdded,
                                LwjglTextureStateUtil.getGLCubeMapFace(((TextureCubeMap) tex).getCurrentRTTFace()),
                                tex.getTextureIdForContext(context.getGlContextRep()), 0);
                    } else {
                        throw new IllegalArgumentException("Invalid texture type: " + tex.getType());
                    }
                    colorsAdded++;
                }

                // Now take care of depth.
                if (!depths.isEmpty()) {
                    final Texture tex = depths.removeFirst();
                    // Set up our depth texture
                    if (tex.getType() == Type.TwoDimensional) {
                        EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                                EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D,
                                tex.getTextureIdForContext(context.getGlContextRep()), 0);
                    } else if (tex.getType() == Type.CubeMap) {
                        EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                                EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT,
                                LwjglTextureStateUtil.getGLCubeMapFace(((TextureCubeMap) tex).getCurrentRTTFace()),
                                tex.getTextureIdForContext(context.getGlContextRep()), 0);
                    } else {
                        throw new IllegalArgumentException("Invalid texture type: " + tex.getType());
                    }
                    _usingDepthRB = false;
                } else if (!_usingDepthRB) {
                    // setup our default depth render buffer if not already set
                    EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                            EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT,
                            _depthRBID);
                    _usingDepthRB = true;
                }

                setDrawBuffers(colorsAdded);
                setReadBuffer(colorsAdded != 0 ? EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT : GL11.GL_NONE);

                // Check FBO complete
                checkFBOComplete(_fboID);

                switchCameraIn(clear);

                if (toDrawA != null) {
                    doDraw(toDrawA);
                } else {
                    doDraw(toDrawB);
                }

                switchCameraOut();
            }

            // automatically generate mipmaps for our textures.
            for (int x = 0, max = texs.size(); x < max; x++) {
                if (texs.get(x).getMinificationFilter().usesMipMapLevels()) {
                    final Texture tex = texs.get(x);
                    if (tex.getMinificationFilter().usesMipMapLevels()) {
                        LwjglTextureStateUtil.doTextureBind(texs.get(x), 0, true);
                        EXTFramebufferObject.glGenerateMipmapEXT(LwjglTextureStateUtil.getGLType(tex.getType()));
                    }
                }
            }

        } finally {
            ContextManager.getCurrentContext().popFBOTextureRenderer();
        }
    }

    @Override
    protected void setupForSingleTexDraw(final Texture tex) {
        final RenderContext context = ContextManager.getCurrentContext();
        final int textureId = tex.getTextureIdForContext(context.getGlContextRep());

        if (tex.getTextureStoreFormat().isDepthFormat()) {
            // No color buffer
            EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                    EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, 0);

            // Setup depth texture into FBO
            if (tex.getType() == Type.TwoDimensional) {
                EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                        EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, textureId, 0);
            } else if (tex.getType() == Type.CubeMap) {
                EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                        EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT,
                        LwjglTextureStateUtil.getGLCubeMapFace(((TextureCubeMap) tex).getCurrentRTTFace()),
                        textureId, 0);
            } else {
                throw new IllegalArgumentException("Can not render to texture of type: " + tex.getType());
            }

            setDrawBuffer(GL11.GL_NONE);
            setReadBuffer(GL11.GL_NONE);
        } else {
            // Set color texture into FBO
            if (tex.getType() == Type.TwoDimensional) {
                EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                        EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, GL11.GL_TEXTURE_2D, textureId, 0);
            } else if (tex.getType() == Type.CubeMap) {
                EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                        EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT,
                        LwjglTextureStateUtil.getGLCubeMapFace(((TextureCubeMap) tex).getCurrentRTTFace()),
                        textureId, 0);
            } else {
                throw new IllegalArgumentException("Can not render to texture of type: " + tex.getType());
            }

            // setup depth RB
            EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                    EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT,
                    _depthRBID);

            setDrawBuffer(EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT);
            setReadBuffer(EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT);
        }

        // Check FBO complete
        checkFBOComplete(_fboID);
    }

    private void setReadBuffer(final int attachVal) {
        GL11.glReadBuffer(attachVal);
    }

    private void setDrawBuffer(final int attachVal) {
        GL11.glDrawBuffer(attachVal);
    }

    private void setDrawBuffers(final int maxEntry) {
        if (maxEntry <= 1) {
            setDrawBuffer(maxEntry != 0 ? EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT : GL11.GL_NONE);
        } else {
            // We should only get to this point if we support ARBDrawBuffers.
            _attachBuffer.clear();
            _attachBuffer.limit(maxEntry);
            ARBDrawBuffers.glDrawBuffersARB(_attachBuffer);
        }
    }

    @Override
    protected void takedownForSingleTexDraw(final Texture tex) {
        // automatically generate mipmaps for our texture.
        if (tex.getMinificationFilter().usesMipMapLevels()) {
            LwjglTextureStateUtil.doTextureBind(tex, 0, true);
            EXTFramebufferObject.glGenerateMipmapEXT(LwjglTextureStateUtil.getGLType(tex.getType()));
        }
    }

    @Override
    protected void setMSFBO() {
        EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferBlit.GL_DRAW_FRAMEBUFFER_EXT, _msfboID);
    }

    @Override
    protected void blitMSFBO() {
        EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferBlit.GL_READ_FRAMEBUFFER_EXT, _msfboID);
        EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferBlit.GL_DRAW_FRAMEBUFFER_EXT, _fboID);
        EXTFramebufferBlit.glBlitFramebufferEXT(0, 0, _width, _height, 0, 0, _width, _height,
                GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT, GL11.GL_NEAREST);

        EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferBlit.GL_READ_FRAMEBUFFER_EXT, 0);
        EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferBlit.GL_DRAW_FRAMEBUFFER_EXT, 0);
        EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);
    }

    /**
     * Check the currently bound FBO status for completeness. The passed in fboID is for informational purposes only.
     * 
     * @param fboID
     *            an id to use for log messages, particularly if there are any issues.
     */
    public static void checkFBOComplete(final int fboID) {
        final int status = EXTFramebufferObject
                .glCheckFramebufferStatusEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT);
        switch (status) {
        case EXTFramebufferObject.GL_FRAMEBUFFER_COMPLETE_EXT:
            break;
        case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
            throw new IllegalStateException(
                    "FrameBuffer: " + fboID + ", has caused a GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT exception");
        case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
            throw new IllegalStateException("FrameBuffer: " + fboID
                    + ", has caused a GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT exception");
        case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
            throw new IllegalStateException(
                    "FrameBuffer: " + fboID + ", has caused a GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT exception");
        case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
            throw new IllegalStateException(
                    "FrameBuffer: " + fboID + ", has caused a GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT exception");
        case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
            throw new IllegalStateException(
                    "FrameBuffer: " + fboID + ", has caused a GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT exception");
        case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
            throw new IllegalStateException(
                    "FrameBuffer: " + fboID + ", has caused a GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT exception");
        case EXTFramebufferObject.GL_FRAMEBUFFER_UNSUPPORTED_EXT:
            throw new IllegalStateException(
                    "FrameBuffer: " + fboID + ", has caused a GL_FRAMEBUFFER_UNSUPPORTED_EXT exception.");
        case EXTFramebufferMultisample.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
            throw new IllegalStateException("FrameBuffer: " + fboID
                    + ", has caused a GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT exception.");
        default:
            throw new IllegalStateException("Unexpected reply from glCheckFramebufferStatusEXT: " + status);
        }
    }

    public void copyToTexture(final Texture tex, final int x, final int y, final int width, final int height,
            final int xoffset, final int yoffset) {
        LwjglTextureStateUtil.doTextureBind(tex, 0, true);

        if (tex.getType() == Type.TwoDimensional) {
            GL11.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, xoffset, yoffset, x, y, width, height);
        } else if (tex.getType() == Type.CubeMap) {
            GL11.glCopyTexSubImage2D(
                    LwjglTextureStateUtil.getGLCubeMapFace(((TextureCubeMap) tex).getCurrentRTTFace()), 0, xoffset,
                    yoffset, x, y, width, height);
        } else {
            throw new IllegalArgumentException("Invalid texture type: " + tex.getType());
        }
    }

    @Override
    protected void clearBuffers(final int clear) {
        GL11.glDisable(GL11.GL_SCISSOR_TEST);
        _parentRenderer.clearBuffers(clear);
    }

    @Override
    protected void activate() {
        // Lazy init
        if (_fboID == 0) {
            final IntBuffer buffer = BufferUtils.createIntBuffer(1);

            // Create our texture binding FBO
            EXTFramebufferObject.glGenFramebuffersEXT(buffer); // generate id
            _fboID = buffer.get(0);

            // Create a depth renderbuffer to use for RTT use
            EXTFramebufferObject.glGenRenderbuffersEXT(buffer); // generate id
            _depthRBID = buffer.get(0);
            EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, _depthRBID);
            int format = GL11.GL_DEPTH_COMPONENT;
            if (_supportsDepthTexture && _depthBits > 0) {
                switch (_depthBits) {
                case 16:
                    format = ARBDepthTexture.GL_DEPTH_COMPONENT16_ARB;
                    break;
                case 24:
                    format = ARBDepthTexture.GL_DEPTH_COMPONENT24_ARB;
                    break;
                case 32:
                    format = ARBDepthTexture.GL_DEPTH_COMPONENT32_ARB;
                    break;
                default:
                    // stick with the "undefined" GL_DEPTH_COMPONENT
                }
            }
            EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, format, _width,
                    _height);

            // unbind...
            EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, 0);
            EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);

            // If we support it, rustle up a multisample framebuffer + renderbuffers
            if (_samples != 0 && _supportsMultisample) {
                // create ms framebuffer object
                EXTFramebufferObject.glGenFramebuffersEXT(buffer);
                _msfboID = buffer.get(0);

                // create ms renderbuffers
                EXTFramebufferObject.glGenRenderbuffersEXT(buffer); // generate id
                _mscolorRBID = buffer.get(0);
                EXTFramebufferObject.glGenRenderbuffersEXT(buffer); // generate id
                _msdepthRBID = buffer.get(0);

                // set up renderbuffer properties
                EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, _mscolorRBID);
                EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(
                        EXTFramebufferObject.GL_RENDERBUFFER_EXT, _samples, GL11.GL_RGBA, _width, _height);

                EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, _msdepthRBID);
                EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(
                        EXTFramebufferObject.GL_RENDERBUFFER_EXT, _samples, format, _width, _height);

                EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, 0);

                EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, _msfboID);
                EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                        EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT,
                        _mscolorRBID);
                EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
                        EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT,
                        _msdepthRBID);

                // check for errors
                checkFBOComplete(_msfboID);

                // release
                EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);
            }

        }

        if (_active == 0) {

            final RenderContext context = ContextManager.getCurrentContext();
            final RendererRecord record = context.getRendererRecord();

            // needed as FBOs do not share this flag it seems
            record.setClippingTestValid(false);

            // push a delimiter onto the clip stack
            _neededClip = _parentRenderer.isClipTestEnabled();
            if (_neededClip) {
                _parentRenderer.pushEmptyClip();
            }

            GL11.glClearColor(_backgroundColor.getRed(), _backgroundColor.getGreen(), _backgroundColor.getBlue(),
                    _backgroundColor.getAlpha());
            EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, _fboID);
            ContextManager.getCurrentContext().pushEnforcedStates();
            ContextManager.getCurrentContext().clearEnforcedStates();
            ContextManager.getCurrentContext().enforceStates(_enforcedStates);
        }
        _active++;
    }

    @Override
    protected void deactivate() {
        if (_active == 1) {
            final ReadOnlyColorRGBA bgColor = _parentRenderer.getBackgroundColor();
            GL11.glClearColor(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue(), bgColor.getAlpha());
            EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);

            ContextManager.getCurrentContext().popEnforcedStates();

            if (_neededClip) {
                _parentRenderer.popClip();
            }
        }
        _active--;
    }

    public void cleanup() {
        if (_fboID != 0) {
            final IntBuffer id = BufferUtils.createIntBuffer(1);
            id.put(_fboID);
            id.rewind();
            EXTFramebufferObject.glDeleteFramebuffersEXT(id);
        }

        if (_depthRBID != 0) {
            final IntBuffer id = BufferUtils.createIntBuffer(1);
            id.put(_depthRBID);
            id.rewind();
            EXTFramebufferObject.glDeleteRenderbuffersEXT(id);
        }
    }
}