// Copyright (C) 2022 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 "DisplaySurfaceGl.h"

#include "OpenGLESDispatch/DispatchTables.h"
#include "OpenGLESDispatch/EGLDispatch.h"
#include "host-common/GfxstreamFatalError.h"
#include "host-common/logging.h"

namespace gfxstream {
namespace gl {
namespace {

using emugl::ABORT_REASON_OTHER;
using emugl::FatalError;

class DisplaySurfaceGlContextHelper : public ContextHelper {
  public:
    DisplaySurfaceGlContextHelper(EGLDisplay display,
                                  EGLSurface surface,
                                  EGLContext context)
        : mDisplay(display),
          mSurface(surface),
          mContext(context) {
        if (mDisplay == EGL_NO_DISPLAY) {
            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
                << "DisplaySurfaceGlContextHelper created with no display?";
        }
        if (mSurface == EGL_NO_SURFACE) {
            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
                << "DisplaySurfaceGlContextHelper created with no surface?";
        }
        if (mContext == EGL_NO_CONTEXT) {
            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
                << "DisplaySurfaceGlContextHelper created with no context?";
        }
    }

    bool setupContext() override {
        EGLContext currentContext = s_egl.eglGetCurrentContext();
        EGLSurface currentDrawSurface = s_egl.eglGetCurrentSurface(EGL_DRAW);
        EGLSurface currentReadSurface = s_egl.eglGetCurrentSurface(EGL_READ);

        if (currentContext != mContext ||
            currentDrawSurface != mSurface ||
            currentReadSurface != mSurface) {
            if (!s_egl.eglMakeCurrent(mDisplay, mSurface, mSurface, mContext)) {
                // b/284523053
                // Legacy swiftshader logspam on exit with this line.
                GL_LOG("Failed to make display surface context current: %d", s_egl.eglGetError());
                return false;
            }
        }

        mPreviousContext = currentContext;
        mPreviousDrawSurface = currentDrawSurface;
        mPreviousReadSurface = currentReadSurface;

        mIsBound = true;

        return mIsBound;
    }

    void teardownContext() override {
        EGLContext currentContext = s_egl.eglGetCurrentContext();
        EGLSurface currentDrawSurface = s_egl.eglGetCurrentSurface(EGL_DRAW);
        EGLSurface currentReadSurface = s_egl.eglGetCurrentSurface(EGL_READ);

        if (currentContext != mPreviousContext ||
            currentDrawSurface != mPreviousDrawSurface ||
            currentReadSurface != mPreviousReadSurface) {
            if (!s_egl.eglMakeCurrent(mDisplay,
                                      mPreviousDrawSurface,
                                      mPreviousReadSurface,
                                      mPreviousContext)) {
                ERR("Failed to make restore previous context: %d", s_egl.eglGetError());
                return;
            }
        }

        mPreviousContext = EGL_NO_CONTEXT;
        mPreviousDrawSurface = EGL_NO_SURFACE;
        mPreviousReadSurface = EGL_NO_SURFACE;
        mIsBound = false;
    }

    bool isBound() const override { return mIsBound; }

  private:
    EGLDisplay mDisplay = EGL_NO_DISPLAY;
    EGLSurface mSurface = EGL_NO_SURFACE;
    EGLContext mContext = EGL_NO_CONTEXT;

    EGLContext mPreviousContext = EGL_NO_CONTEXT;
    EGLSurface mPreviousReadSurface = EGL_NO_SURFACE;
    EGLSurface mPreviousDrawSurface = EGL_NO_SURFACE;

    bool mIsBound = false;
};

}  // namespace

/*static*/
std::unique_ptr<DisplaySurfaceGl> DisplaySurfaceGl::createPbufferSurface(
        EGLDisplay display,
        EGLConfig config,
        EGLContext shareContext,
        const EGLint* contextAttribs,
        EGLint width,
        EGLint height) {
    EGLContext context = s_egl.eglCreateContext(display, config, shareContext, contextAttribs);
    if (context == EGL_NO_CONTEXT) {
        ERR("Failed to create context for DisplaySurfaceGl.");
        return nullptr;
    }

    const EGLint surfaceAttribs[] = {
        EGL_WIDTH, width,   //
        EGL_HEIGHT, height, //
        EGL_NONE,           //
    };
    EGLSurface surface = s_egl.eglCreatePbufferSurface(display, config, surfaceAttribs);
    if (surface == EGL_NO_SURFACE) {
        ERR("Failed to create pbuffer surface for DisplaySurfaceGl.");
        return nullptr;
    }

    return std::unique_ptr<DisplaySurfaceGl>(new DisplaySurfaceGl(display, surface, context));
}

/*static*/
std::unique_ptr<DisplaySurfaceGl> DisplaySurfaceGl::createWindowSurface(
        EGLDisplay display,
        EGLConfig config,
        EGLContext shareContext,
        const GLint* contextAttribs,
        FBNativeWindowType window) {
    EGLContext context = s_egl.eglCreateContext(display, config, shareContext, contextAttribs);
    if (context == EGL_NO_CONTEXT) {
        ERR("Failed to create context for DisplaySurfaceGl.");
        return nullptr;
    }

    EGLSurface surface = s_egl.eglCreateWindowSurface(display, config, window, nullptr);
    if (surface == EGL_NO_SURFACE) {
        ERR("Failed to create window surface for DisplaySurfaceGl.");
        return nullptr;
    }

    return std::unique_ptr<DisplaySurfaceGl>(new DisplaySurfaceGl(display, surface, context));
}

bool DisplaySurfaceGl::bindContext() const {
    if (!s_egl.eglMakeCurrent(mDisplay, mSurface, mSurface, mContext)) {
        ERR("Failed to make display surface context current: %d", s_egl.eglGetError());
        return false;
    }
    return true;
}

DisplaySurfaceGl::DisplaySurfaceGl(EGLDisplay display,
                                   EGLSurface surface,
                                   EGLContext context)
    : mDisplay(display),
      mSurface(surface),
      mContext(context),
      mContextHelper(new DisplaySurfaceGlContextHelper(display, surface, context)) {}

DisplaySurfaceGl::~DisplaySurfaceGl() {
    if (mDisplay != EGL_NO_DISPLAY) {
        if (mSurface) {
            s_egl.eglDestroySurface(mDisplay, mSurface);
        }
        if (mContext) {
            s_egl.eglDestroyContext(mDisplay, mContext);
        }
    }
}

}  // namespace gl
}  // namespace gfxstream