GLES2FBOManager::GLES2FBOManager() : mMaxFSAASamples(0)
    {
        detectFBOFormats();
        
        OGRE_CHECK_GL_ERROR(glGenFramebuffers(1, &mTempFBO));

        // Check multisampling if supported
        if(getGLES2RenderSystem()->hasMinGLVersion(3, 0))
        {
            // Check samples supported
            OGRE_CHECK_GL_ERROR(glGetIntegerv(GL_MAX_SAMPLES_APPLE, &mMaxFSAASamples));
        }
    }
//-----------------------------------------------------------------------------
    GLES2FrameBufferObject::GLES2FrameBufferObject(GLES2FBOManager *manager, uint fsaa):
        GLFrameBufferObjectCommon(fsaa), mManager(manager)
    {
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS
        GLint oldfb = 0;
        OGRE_CHECK_GL_ERROR(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldfb));
#endif
        GLES2RenderSystem* rs = getGLES2RenderSystem();
        mContext = rs->_getCurrentContext();
        
        // Generate framebuffer object
        OGRE_CHECK_GL_ERROR(glGenFramebuffers(1, &mFB));

	   if(rs->getCapabilities()->hasCapability(RSC_DEBUG))
       {
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS
           OGRE_CHECK_GL_ERROR(glBindFramebuffer(GL_FRAMEBUFFER, mFB)); // to avoid GL_INVALID_OPERATION in glLabelObjectEXT(GL_FRAMEBUFFER,...) on iOS 
#endif
           OGRE_CHECK_GL_ERROR(glLabelObjectEXT(GL_FRAMEBUFFER, mFB, 0, ("FBO #" + StringConverter::toString(mFB)).c_str()));
       }

        mNumSamples = std::min(mNumSamples, manager->getMaxFSAASamples());

        // Will we need a second FBO to do multisampling?
        if (mNumSamples)
        {
            OGRE_CHECK_GL_ERROR(glGenFramebuffers(1, &mMultisampleFB));
            if(rs->getCapabilities()->hasCapability(RSC_DEBUG))
            {
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS
                OGRE_CHECK_GL_ERROR(glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFB)); // to avoid GL_INVALID_OPERATION in glLabelObjectEXT(GL_FRAMEBUFFER,...) on iOS 
#endif
                OGRE_CHECK_GL_ERROR(glLabelObjectEXT(GL_FRAMEBUFFER, mMultisampleFB, 0, ("MSAA FBO #" + StringConverter::toString(mMultisampleFB)).c_str()));
            }
        }
        else
        {
            mMultisampleFB = 0;
        }

#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS
        OGRE_CHECK_GL_ERROR(glBindFramebuffer(GL_FRAMEBUFFER, oldfb));
#endif
    }
    void GLES2FrameBufferObject::initialise()
    {
        GLES2RenderSystem* rs = getGLES2RenderSystem();
        assert(mContext == rs->_getCurrentContext());
        
        // Release depth and stencil, if they were bound
        mManager->releaseRenderBuffer(mDepth);
        mManager->releaseRenderBuffer(mStencil);
        mManager->releaseRenderBuffer(mMultisampleColourBuffer);
        // First buffer must be bound
        if(!mColour[0].buffer)
        {
            OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, 
                "Attachment 0 must have surface attached",
                "GLES2FrameBufferObject::initialise");
        }

        // If we're doing multisampling, then we need another FBO which contains a
        // renderbuffer which is set up to multisample, and we'll blit it to the final 
        // FBO afterwards to perform the multisample resolve. In that case, the 
        // mMultisampleFB is bound during rendering and is the one with a depth/stencil

        // Store basic stats
        uint32 width = mColour[0].buffer->getWidth();
        uint32 height = mColour[0].buffer->getHeight();
        GLuint format = mColour[0].buffer->getGLFormat();
        ushort maxSupportedMRTs = rs->getCapabilities()->getNumMultiRenderTargets();

        // Bind simple buffer to add colour attachments
        OGRE_CHECK_GL_ERROR(glBindFramebuffer(GL_FRAMEBUFFER, mFB));

        bool isDepth = PixelUtil::isDepth(getFormat());

        // Bind all attachment points to frame buffer
        for(unsigned int x = 0; x < maxSupportedMRTs; ++x)
        {
            if(mColour[x].buffer)
            {
                if(mColour[x].buffer->getWidth() != width || mColour[x].buffer->getHeight() != height)
                {
                    StringStream ss;
                    ss << "Attachment " << x << " has incompatible size ";
                    ss << mColour[x].buffer->getWidth() << "x" << mColour[x].buffer->getHeight();
                    ss << ". It must be of the same as the size of surface 0, ";
                    ss << width << "x" << height;
                    ss << ".";
                    OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, ss.str(), "GLES2FrameBufferObject::initialise");
                }
                if(mColour[x].buffer->getGLFormat() != format)
                {
                    StringStream ss;
                    ss << "Attachment " << x << " has incompatible format.";
                    OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, ss.str(), "GLES2FrameBufferObject::initialise");
                }
                mColour[x].buffer->bindToFramebuffer(
                    isDepth ? GL_DEPTH_ATTACHMENT : (GL_COLOR_ATTACHMENT0 + x), mColour[x].zoffset);
            }
            else
            {
                // Detach
                OGRE_CHECK_GL_ERROR(glFramebufferRenderbuffer(GL_FRAMEBUFFER, static_cast<GLenum>(GL_COLOR_ATTACHMENT0+x), GL_RENDERBUFFER, 0));
            }
        }

        // Now deal with depth / stencil
        if (mMultisampleFB)
        {
            // Bind multisample buffer
            OGRE_CHECK_GL_ERROR(glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFB));

            // Create AA render buffer (colour)
            // note, this can be shared too because we blit it to the final FBO
            // right after the render is finished
            mMultisampleColourBuffer = mManager->requestRenderBuffer(format, width, height, mNumSamples);

            // Attach it, because we won't be attaching below and non-multisample has
            // actually been attached to other FBO
            mMultisampleColourBuffer.buffer->bindToFramebuffer(GL_COLOR_ATTACHMENT0, 
                mMultisampleColourBuffer.zoffset);

            // depth & stencil will be dealt with below
        }

        // Depth buffer is not handled here anymore.
        // See GLES2FrameBufferObject::attachDepthBuffer() & RenderSystem::setDepthBufferFor()

        if(rs->hasMinGLVersion(3, 0) && OGRE_PLATFORM != OGRE_PLATFORM_EMSCRIPTEN) // ED on Emscripten
        {
            GLenum bufs[OGRE_MAX_MULTIPLE_RENDER_TARGETS];
            GLsizei n=0;
            for(unsigned int x=0; x<maxSupportedMRTs; ++x)
            {
                // Fill attached colour buffers
                if(mColour[x].buffer)
                {
                    bufs[x] = isDepth ? GL_DEPTH_ATTACHMENT : (GL_COLOR_ATTACHMENT0 + x);
                    // Keep highest used buffer + 1
                    n = x+1;
                }
                else
                {
                    bufs[x] = GL_NONE;
                }
            }

            // Drawbuffer extension supported, use it
            if(!isDepth)
                OGRE_CHECK_GL_ERROR(glDrawBuffers(n, bufs));

            if (mMultisampleFB)
            {
                // we need a read buffer because we'll be blitting to mFB
                OGRE_CHECK_GL_ERROR(glReadBuffer(bufs[0]));
            }
            else
            {
                // No read buffer, by default, if we want to read anyway we must not forget to set this.
                OGRE_CHECK_GL_ERROR(glReadBuffer(GL_NONE));
            }
        }
        // Check status
        GLuint status;
        OGRE_CHECK_GL_ERROR(status = glCheckFramebufferStatus(GL_FRAMEBUFFER));

        // Bind main buffer
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS
        // The screen buffer is 1 on iOS
        OGRE_CHECK_GL_ERROR(glBindFramebuffer(GL_FRAMEBUFFER, 1));
#else
        OGRE_CHECK_GL_ERROR(glBindFramebuffer(GL_FRAMEBUFFER, 0));
#endif

        switch(status)
        {
        case GL_FRAMEBUFFER_COMPLETE:
            // All is good
            break;
        case GL_FRAMEBUFFER_UNSUPPORTED:
            OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, 
                "All framebuffer formats with this texture internal format unsupported",
                "GLES2FrameBufferObject::initialise");
        default:
            OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, 
                "Framebuffer incomplete or other FBO status error",
                "GLES2FrameBufferObject::initialise");
        }
        
    }
    /** Detect which internal formats are allowed as RTT
        Also detect what combinations of stencil and depth are allowed with this internal
        format.
    */
    void GLES2FBOManager::detectFBOFormats()
    {
#if OGRE_PLATFORM == OGRE_PLATFORM_EMSCRIPTEN
        memset(mProps, 0, sizeof(mProps));

        // TODO: Fix that probing all formats slows down startup not just on the web also on Android / iOS
        mProps[PF_A8B8G8R8].valid = true;
        FormatProperties::Mode mode = {1, 0};
        mProps[PF_A8B8G8R8].modes.push_back(mode);
        LogManager::getSingleton().logMessage("[GLES2] : detectFBOFormats is disabled on this platform (due performance reasons)");
#else
        // Try all formats, and report which ones work as target
        GLES2RenderSystem* rs = getGLES2RenderSystem();
        GLuint fb = 0, tid = 0;

        bool hasGLES3 = rs->hasMinGLVersion(3, 0);

        const size_t depthCount = hasGLES3 ? DEPTHFORMAT_COUNT : DEPTHFORMAT_COUNT - 1; // 32_8 is not available on GLES2
        const size_t stencilStep = hasGLES3 ? 3 : 1; // 1 and 4 bit not available on GLES3

        for(size_t x = 0; x < PF_COUNT; ++x)
        {
            mProps[x].valid = false;

            // Fetch GL format token
            GLint internalFormat = GLES2PixelUtil::getGLInternalFormat((PixelFormat)x);
            GLenum fmt = GLES2PixelUtil::getGLOriginFormat((PixelFormat)x);
            GLenum type = GLES2PixelUtil::getGLOriginDataType((PixelFormat)x);

            // Note: letting PF_UNKNOWN pass here is for pure depth/ stencil formats
            // however there are reports that this crashes some unspecified android devices
            if((internalFormat == GL_NONE || fmt == GL_NONE || type == GL_NONE) && (x != 0))
                continue;

            // not color-renderable in GLES
            if(fmt == GL_BGRA_EXT)
                continue;

            // No test for compressed formats
            if(PixelUtil::isCompressed((PixelFormat)x))
                continue;

            // Create and attach framebuffer
            _createTempFramebuffer((PixelFormat)x, internalFormat, fmt, type, fb, tid);

            // Ignore status in case of fmt==GL_NONE, because no implementation will accept
            // a buffer without *any* attachment. Buffers with only stencil and depth attachment
            // might still be supported, so we must continue probing.
            if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
            {
                mProps[x].valid = true;
                StringStream str;
                str << "FBO " << PixelUtil::getFormatName((PixelFormat)x) 
                    << " depth/stencil support: ";

                // For each depth/stencil formats
                for (size_t depth = 0; depth < depthCount; ++depth)
                {
                    if (depthFormats[depth] != GL_DEPTH24_STENCIL8 && depthFormats[depth] != GL_DEPTH32F_STENCIL8)
                    {
                        // General depth/stencil combination

                        for (size_t stencil = 0; stencil < STENCILFORMAT_COUNT; stencil += stencilStep)
                        {
//                            StringStream l;
//                            l << "Trying " << PixelUtil::getFormatName((PixelFormat)x) 
//                              << " D" << depthBits[depth] 
//                              << "S" << stencilBits[stencil];
//                            LogManager::getSingleton().logMessage(l.str());

                            if (_tryFormat(depthFormats[depth], stencilFormats[stencil]))
                            {
                                // Add mode to allowed modes
                                str << "D" << depthBits[depth] << "S" << stencilBits[stencil] << " ";
                                FormatProperties::Mode mode;
                                mode.depth = depth;
                                mode.stencil = stencil;
                                mProps[x].modes.push_back(mode);
                            }
                            else
                            {
                                // There is a small edge case that FBO is trashed during the test
                                // on some drivers resulting in undefined behavior
                                glBindFramebuffer(GL_FRAMEBUFFER, 0);
                                glDeleteFramebuffers(1, &fb);

                                _createTempFramebuffer((PixelFormat)x, internalFormat, fmt, type, fb, tid);
                            }
                        }
                    }
                    else if(hasGLES3 || rs->checkExtension("GL_OES_packed_depth_stencil") )
                    {
                        // Packed depth/stencil format
                        if (_tryPackedFormat(depthFormats[depth]))
                        {
                            // Add mode to allowed modes
                            str << "Packed-D" << depthBits[depth] << "S" << 8 << " ";
                            FormatProperties::Mode mode;
                            mode.depth = depth;
                            mode.stencil = 0;   // unuse
                            mProps[x].modes.push_back(mode);
                        }
                        else
                        {
                            // There is a small edge case that FBO is trashed during the test
                            // on some drivers resulting in undefined behavior
                            glBindFramebuffer(GL_FRAMEBUFFER, 0);
                            glDeleteFramebuffers(1, &fb);

                            _createTempFramebuffer((PixelFormat)x, internalFormat, fmt, type, fb, tid);
                        }
                    }
                }
                LogManager::getSingleton().logMessage(str.str());
            }

            // Delete texture and framebuffer
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            glDeleteFramebuffers(1, &fb);
            
            if (internalFormat != GL_NONE)
            {
                glDeleteTextures(1, &tid);
                tid = 0;
            }
        }

        // Clear any errors
        glGetError();
#endif
        String fmtstring;
        for(size_t x = 0; x < PF_COUNT; ++x)
        {
            if(mProps[x].valid)
                fmtstring += PixelUtil::getFormatName((PixelFormat)x)+" ";
        }
        LogManager::getSingleton().logMessage("[GLES2] : Valid FBO targets " + fmtstring);

    }