//
// Copyright 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// global_state.h : Defines functions for querying the thread-local GL and EGL state.

#ifndef LIBGLESV2_GLOBALSTATE_H_
#define LIBGLESV2_GLOBALSTATE_H_

#include "libANGLE/Context.h"
#include "libANGLE/Debug.h"
#include "libANGLE/Display.h"
#include "libANGLE/GlobalMutex.h"
#include "libANGLE/Thread.h"
#include "libANGLE/features.h"
#include "libANGLE/validationEGL.h"

#if defined(ANGLE_PLATFORM_APPLE) || (ANGLE_PLATFORM_ANDROID)
#    include "common/tls.h"
#endif

#include <mutex>

namespace egl
{
class Debug;
class Thread;

#if defined(ANGLE_PLATFORM_APPLE) || defined(ANGLE_USE_STATIC_THREAD_LOCAL_VARIABLES)
extern Thread *GetCurrentThreadTLS();
extern void SetCurrentThreadTLS(Thread *thread);
#else
extern thread_local Thread *gCurrentThread;
#endif

gl::Context *GetGlobalLastContext();
void SetGlobalLastContext(gl::Context *context);
Thread *GetCurrentThread();
Debug *GetDebug();

// Sync the current context from Thread to global state.
class [[nodiscard]] ScopedSyncCurrentContextFromThread
{
  public:
    ScopedSyncCurrentContextFromThread(egl::Thread *thread);
    ~ScopedSyncCurrentContextFromThread();

  private:
    egl::Thread *const mThread;
};

// Tries to lock "ContextMutex" of the Context current to the "thread".
ANGLE_INLINE ScopedContextMutexLock TryLockCurrentContext(Thread *thread)
{
    ASSERT(kIsContextMutexEnabled);
    gl::Context *context = thread->getContext();
    return context != nullptr ? ScopedContextMutexLock(context->getContextMutex())
                              : ScopedContextMutexLock();
}

// Tries to lock "ContextMutex" of the Context with "contextID" if it is valid.
ANGLE_INLINE ScopedContextMutexLock TryLockContext(Display *display, gl::ContextID contextID)
{
    ASSERT(kIsContextMutexEnabled);
    gl::Context *context = GetContextIfValid(display, contextID);
    return context != nullptr ? ScopedContextMutexLock(context->getContextMutex())
                              : ScopedContextMutexLock();
}

// Locks "ContextMutex" of the "context" and then tries to merge it with the "ContextMutex" of the
// Image with "imageID" if it is valid.
ANGLE_INLINE ScopedContextMutexLock LockAndTryMergeContextMutexes(gl::Context *context,
                                                                  ImageID imageID)
{
    ASSERT(kIsContextMutexEnabled);
    ASSERT(context->getDisplay() != nullptr);
    ScopedContextMutexLock lock(context->getContextMutex());
    const Image *image = context->getDisplay()->getImage(imageID);
    if (image != nullptr)
    {
        ContextMutex *imageMutex = image->getContextMutex();
        if (imageMutex != nullptr)
        {
            ContextMutex::Merge(&context->getContextMutex(), imageMutex);
        }
    }
    return lock;
}

#if !defined(ANGLE_ENABLE_CONTEXT_MUTEX)
#    define ANGLE_EGL_SCOPED_CONTEXT_LOCK(EP, THREAD, ...)
#else
#    define ANGLE_EGL_SCOPED_CONTEXT_LOCK(EP, THREAD, ...) \
        egl::ScopedContextMutexLock shareContextLock = GetContextLock_##EP(THREAD, ##__VA_ARGS__)
#endif

}  // namespace egl

#define ANGLE_SCOPED_GLOBAL_LOCK() egl::ScopedGlobalMutexLock globalMutexLock

namespace gl
{
ANGLE_INLINE Context *GetGlobalContext()
{
#if defined(ANGLE_PLATFORM_APPLE) || defined(ANGLE_USE_STATIC_THREAD_LOCAL_VARIABLES)
    egl::Thread *currentThread = egl::GetCurrentThreadTLS();
#else
    egl::Thread *currentThread = egl::gCurrentThread;
#endif
    ASSERT(currentThread);
    return currentThread->getContext();
}

ANGLE_INLINE Context *GetValidGlobalContext()
{
#if defined(ANGLE_USE_ANDROID_TLS_SLOT)
    // TODO: Replace this branch with a compile time flag (http://anglebug.com/4764)
    if (angle::gUseAndroidOpenGLTlsSlot)
    {
        return static_cast<gl::Context *>(ANGLE_ANDROID_GET_GL_TLS()[angle::kAndroidOpenGLTlsSlot]);
    }
#endif

#if defined(ANGLE_PLATFORM_APPLE) || defined(ANGLE_USE_STATIC_THREAD_LOCAL_VARIABLES)
    return GetCurrentValidContextTLS();
#else
    return gCurrentValidContext;
#endif
}

// Generate a context lost error on the context if it is non-null and lost.
void GenerateContextLostErrorOnContext(Context *context);
void GenerateContextLostErrorOnCurrentGlobalContext();

#if defined(ANGLE_FORCE_CONTEXT_CHECK_EVERY_CALL)
// TODO(b/177574181): This should be handled in a backend-specific way.
// if previous context different from current context, dirty all state
static ANGLE_INLINE void DirtyContextIfNeeded(Context *context)
{
    if (context && context != egl::GetGlobalLastContext())
    {
        context->dirtyAllState();
        SetGlobalLastContext(context);
    }
}

#endif

#if !defined(ANGLE_ENABLE_SHARE_CONTEXT_LOCK)
#    define SCOPED_SHARE_CONTEXT_LOCK(context)
#    define SCOPED_EGL_IMAGE_SHARE_CONTEXT_LOCK(context, imageID) ANGLE_SCOPED_GLOBAL_LOCK()
#else
#    if defined(ANGLE_FORCE_CONTEXT_CHECK_EVERY_CALL)
#        define SCOPED_SHARE_CONTEXT_LOCK(context)       \
            egl::ScopedGlobalMutexLock shareContextLock; \
            DirtyContextIfNeeded(context)
#        define SCOPED_EGL_IMAGE_SHARE_CONTEXT_LOCK(context, imageID) \
            SCOPED_SHARE_CONTEXT_LOCK(context)
#    elif !defined(ANGLE_ENABLE_CONTEXT_MUTEX)
#        define SCOPED_SHARE_CONTEXT_LOCK(context) \
            egl::ScopedOptionalGlobalMutexLock shareContextLock(context->isShared())
#        define SCOPED_EGL_IMAGE_SHARE_CONTEXT_LOCK(context, imageID) ANGLE_SCOPED_GLOBAL_LOCK()
#    else
#        define SCOPED_SHARE_CONTEXT_LOCK(context) \
            egl::ScopedContextMutexLock shareContextLock(context->getContextMutex())
#        define SCOPED_EGL_IMAGE_SHARE_CONTEXT_LOCK(context, imageID) \
            ANGLE_SCOPED_GLOBAL_LOCK();                               \
            egl::ScopedContextMutexLock shareContextLock =            \
                egl::LockAndTryMergeContextMutexes(context, imageID)
#    endif
#endif

}  // namespace gl

#endif  // LIBGLESV2_GLOBALSTATE_H_
