/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "EglContext.h"
#include "EglDisplay.h"
#include "EglGlobalInfo.h"
#include "EglOsApi.h"
#include "EglPbufferSurface.h"
#include "ThreadInfo.h"

#include <GLcommon/GLEScontext.h>
#include <memory>

unsigned int EglContext::s_nextContextHndl = 0;

extern EglGlobalInfo* g_eglInfo; // defined in EglImp.cpp

bool EglContext::usingSurface(SurfacePtr surface) {
  return surface.get() == m_read.get() || surface.get() == m_draw.get();
}

EglContext::EglContext(EglDisplay *dpy,
                       uint64_t shareGroupId,
                       EglConfig* config,
                       GLEScontext* glesCtx,
                       GLESVersion ver,
                       EGLint profileMask,
                       ObjectNameManager* mngr,
                       android::base::Stream* stream) :
        m_dpy(dpy),
        m_config(config),
        m_glesContext(glesCtx),
        m_version(ver),
        m_mngr(mngr),
        // If we already have set core profile flag
        // (through the first context creation in Framebuffer initialization
        // or what have you),
        // set all follow contexts to use core as well.
        // Otherwise, we can end up testing unreliable driver paths where
        // core and non-core contexts need to interact with each other.
        m_profileMask(isCoreProfile() ?
                      (profileMask | EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR) :
                      profileMask)
{
    // Set the GLES-side core profile flag,
    // and the global EGL flag.
    bool usingCoreProfile =
        m_profileMask & EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
    setCoreProfile(usingCoreProfile);
    glesCtx->setCoreProfile(usingCoreProfile);

    if (stream) {
        EGLint configId = EGLint(stream->getBe32());
        m_config = dpy->getConfig(configId);
        if (!m_config) {
            m_config = dpy->getDefaultConfig();
        }
        assert(m_config);
        shareGroupId = static_cast<uint64_t>(stream->getBe64());
    }

    EglOS::Context* globalSharedContext = dpy->getGlobalSharedContext();
    m_native =
        dpy->nativeType()->createContext(
            m_profileMask,
            m_config->nativeFormat(),
            globalSharedContext);

    if (m_native) {
        // When loading from a snapshot, the first context within a share group
        // will load share group data.
        m_shareGroup = mngr->attachOrCreateShareGroup(
                m_native.get(), shareGroupId, stream,
                [glesCtx](NamedObjectType type,
                          ObjectLocalName localName,
                          android::base::Stream* stream) {
                    return glesCtx->loadObject(type, localName, stream);
                });
        if (stream) {
            glesCtx->setShareGroup(m_shareGroup);
            glesCtx->postLoad();
        }
        m_hndl = ++s_nextContextHndl;
    } else {
        m_hndl = 0;
    }
}

EglContext::~EglContext()
{
    ThreadInfo* thread = getThreadInfo();
    // get the current context
    EglContext* rebindCtx = thread->eglContext.get();
    SurfacePtr rebindRead = nullptr;
    SurfacePtr rebindDraw = nullptr;
    std::unique_ptr<EglPbufferSurface> pbSurface;
    if (rebindCtx == this) {
        // this context is current, no need to rebind.
        rebindCtx = nullptr;
    } else {
        if (rebindCtx && !m_dpy->getContext((EGLContext)SafePointerFromUInt(
                            rebindCtx->getHndl()))) {
            rebindCtx = nullptr;
        }
        rebindRead = rebindCtx ? rebindCtx->read() : nullptr;
        rebindDraw = rebindCtx ? rebindCtx->draw() : nullptr;
        // we need to make the context current before releasing GL resources.
        // create a dummy surface first
        pbSurface.reset(new EglPbufferSurface(m_dpy, m_config));
        pbSurface->setAttrib(EGL_WIDTH, 1);
        pbSurface->setAttrib(EGL_HEIGHT, 1);
        EglOS::PbufferInfo pbInfo;
        pbSurface->getDim(&pbInfo.width, &pbInfo.height, &pbInfo.largest);
        pbSurface->getTexInfo(&pbInfo.target, &pbInfo.format);
        pbInfo.hasMipmap = false;
        EglOS::Surface* pb = m_dpy->nativeType()->createPbufferSurface(
                m_config->nativeFormat(), &pbInfo);
        assert(pb);
        if (pb) {
            const bool res = m_dpy->nativeType()->makeCurrent(pb, pb, m_native.get());
            assert(res);
            (void)res;
            pbSurface->setNativePbuffer(pb);
        }
    }
    //
    // release GL resources. m_shareGroup, m_mngr and m_glesContext hold
    // smart pointers to share groups. We must clean them up when the context
    // is current.
    //
    g_eglInfo->getIface(version())->setShareGroup(m_glesContext, {});
    if (m_mngr) {
        m_mngr->deleteShareGroup(m_native.get());
    }
    m_shareGroup.reset();

    //
    // call the client-api to remove the GLES context
    //
    g_eglInfo->getIface(version())->deleteGLESContext(m_glesContext);
    //
    // restore the previous context
    //
    if (rebindCtx) {
        m_dpy->nativeType()->makeCurrent(rebindRead->native(),
                                         rebindDraw->native(),
                                         rebindCtx->nativeType());
    } else {
        m_dpy->nativeType()->makeCurrent(nullptr, nullptr, nullptr);
    }
}

void EglContext::setSurfaces(SurfacePtr read,SurfacePtr draw)
{
    m_read = read;
    m_draw = draw;
}

bool EglContext::getAttrib(EGLint attrib,EGLint* value) {
    switch(attrib) {
    case EGL_CONFIG_ID:
        *value = m_config->id();
        break;
    default:
        return false;
    }
    return true;
}

void EglContext::onSave(android::base::Stream* stream) {
    // Save gles context first
    assert(m_glesContext);
    m_glesContext->onSave(stream);
    // We save the information that
    // is needed to restore the contexts.
    // That means (1) context configurations (2) shared group IDs.

    // Save the config.
    // The current implementation is pretty hacky. It stores the config id.
    // It almost only works when snapshot saving and loading happens on the
    // same system with the same GPU driver and hardware.
    // TODO: make it more general
    stream->putBe32(getConfig()->id());
    // Save shared group ID
    stream->putBe64(m_shareGroup->getId());
    m_shareGroup->onSave(stream);
}

void EglContext::postSave(android::base::Stream* stream) {
    m_glesContext->postSave(stream);
    m_shareGroup->postSave(stream);
}
