/*
* Copyright (C) 2016 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 "EmulatedEglFenceSync.h"

#include <unordered_set>

#include "OpenGLESDispatch/DispatchTables.h"
#include "OpenGLESDispatch/EGLDispatch.h"
#include "RenderThreadInfoGl.h"
#include "StalePtrRegistry.h"
#include "aemu/base/containers/Lookup.h"
#include "aemu/base/containers/StaticMap.h"
#include "aemu/base/files/StreamSerializing.h"
#include "aemu/base/synchronization/Lock.h"

namespace gfxstream {
namespace gl {
namespace {

using android::base::AutoLock;
using android::base::Lock;
using android::base::StaticMap;

// Timeline class is meant to delete native fences after the
// sync device has incremented the timeline.  We assume a
// maximum number of outstanding timelines in the guest (16) in
// order to derive when a native fence is definitely safe to
// delete. After at least that many timeline increments have
// happened, we sweep away the remaining native fences.
// The function that performs the deleting,
// incrementTimelineAndDeleteOldFences(), happens on the SyncThread.

class Timeline {
  public:
    Timeline() = default;

    static constexpr int kMaxGuestTimelines = 16;
    void addFence(EmulatedEglFenceSync* fence) {
        mFences.set(fence, mTime.load() + kMaxGuestTimelines);
    }

    void incrementTimelineAndDeleteOldFences() {
        ++mTime;
        sweep();
    }

    void sweep() {
        mFences.eraseIf([time = mTime.load()](EmulatedEglFenceSync* fence, int fenceTime) {
            EmulatedEglFenceSync* actual = EmulatedEglFenceSync::getFromHandle((uint64_t)(uintptr_t)fence);
            if (!actual) return true;

            bool shouldErase = fenceTime <= time;
            if (shouldErase) {
                if (!actual->decRef() &&
                    actual->shouldDestroyWhenSignaled()) {
                    actual->decRef();
                }
            }
            return shouldErase;
        });
    }

  private:
    std::atomic<int> mTime {0};
    StaticMap<EmulatedEglFenceSync*, int> mFences;
};

static Timeline* sTimeline() {
    static Timeline* t = new Timeline;
    return t;
}

}  // namespace

// static
void EmulatedEglFenceSync::incrementTimelineAndDeleteOldFences() {
    sTimeline()->incrementTimelineAndDeleteOldFences();
}

// static
std::unique_ptr<EmulatedEglFenceSync> EmulatedEglFenceSync::create(
        EGLDisplay display,
        bool hasNativeFence,
        bool destroyWhenSignaled) {
    auto sync = s_egl.eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
    if (sync == EGL_NO_SYNC_KHR) {
        ERR("Failed to create EGL fence sync: %d", s_egl.eglGetError());
        return nullptr;
    }

    // This MUST be present, or we get a deadlock effect.
    s_gles2.glFlush();

    return std::unique_ptr<EmulatedEglFenceSync>(
        new EmulatedEglFenceSync(display,
                                 sync,
                                 hasNativeFence,
                                 destroyWhenSignaled));
}

EmulatedEglFenceSync::EmulatedEglFenceSync(EGLDisplay display,
                                           EGLSyncKHR sync,
                                           bool hasNativeFence,
                                           bool destroyWhenSignaled)
    : mDestroyWhenSignaled(destroyWhenSignaled),
      mDisplay(display),
      mSync(sync) {

    addToRegistry();

    assert(mCount == 1);
    if (hasNativeFence) {
        incRef();
        sTimeline()->addFence(this);
    }

    // Assumes that there is a valid + current OpenGL context
    assert(RenderThreadInfoGl::get());
}

EmulatedEglFenceSync::~EmulatedEglFenceSync() {
    removeFromRegistry();
}

EGLint EmulatedEglFenceSync::wait(uint64_t timeout) {
    incRef();
    EGLint wait_res =
        s_egl.eglClientWaitSyncKHR(mDisplay, mSync,
                                   EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
                                   timeout);
    decRef();
    return wait_res;
}

void EmulatedEglFenceSync::waitAsync() {
    s_egl.eglWaitSyncKHR(mDisplay, mSync, 0);
}

bool EmulatedEglFenceSync::isSignaled() {
    EGLint val;
    if (EGL_TRUE ==
            s_egl.eglGetSyncAttribKHR(
                mDisplay, mSync, EGL_SYNC_STATUS_KHR, &val))
        return val == EGL_SIGNALED_KHR;

    return true; // if invalid, treat as signaled
}

void EmulatedEglFenceSync::destroy() {
    s_egl.eglDestroySyncKHR(mDisplay, mSync);
}

// Snapshots for EmulatedEglFenceSync//////////////////////////////////////////////////////
// It's possible, though it does not happen often, that a fence
// can be created but not yet waited on by the guest, which
// needs careful handling:
//
// 1. Avoid manipulating garbage memory on snapshot restore;
// rcCreateSyncKHR *creates new fence in valid memory*
// --snapshot--
// rcClientWaitSyncKHR *refers to uninitialized memory*
// rcDestroySyncKHR *refers to uninitialized memory*
// 2. Make rcCreateSyncKHR/rcDestroySyncKHR implementations return
// the "signaled" status if referring to previous snapshot fences. It's
// assumed that the GPU is long done with them.
// 3. Avoid name collisions where a new EmulatedEglFenceSync object is created
// that has the same uint64_t casting as a EmulatedEglFenceSync object from a previous
// snapshot.

// Maintain a StalePtrRegistry<EmulatedEglFenceSync>:
static StalePtrRegistry<EmulatedEglFenceSync>* sFenceRegistry() {
    static StalePtrRegistry<EmulatedEglFenceSync>* s = new StalePtrRegistry<EmulatedEglFenceSync>;
    return s;
}

// static
void EmulatedEglFenceSync::addToRegistry() {
    sFenceRegistry()->addPtr(this);
}

// static
void EmulatedEglFenceSync::removeFromRegistry() {
    sFenceRegistry()->removePtr(this);
}

// static
void EmulatedEglFenceSync::onSave(android::base::Stream* stream) {
    sFenceRegistry()->makeCurrentPtrsStale();
    sFenceRegistry()->onSave(stream);
}

// static
void EmulatedEglFenceSync::onLoad(android::base::Stream* stream) {
    sFenceRegistry()->onLoad(stream);
}

// static
EmulatedEglFenceSync* EmulatedEglFenceSync::getFromHandle(uint64_t handle) {
    return sFenceRegistry()->getPtr(handle);
}

}  // namespace gl
}  // namespace gfxstream
