/*
 * Copyright (C) 2018 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 "ImageConsumer.h"
#include <gui/BufferQueue.h>
#include "Properties.h"
#include "SurfaceTexture.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
#include "renderthread/RenderThread.h"
#include "renderthread/VulkanManager.h"
#include "utils/Color.h"
#include <GrAHardwareBufferUtils.h>
#include <GrBackendSurface.h>

// Macro for including the SurfaceTexture name in log messages
#define IMG_LOGE(x, ...) ALOGE("[%s] " x, st.mName.string(), ##__VA_ARGS__)

using namespace android::uirenderer::renderthread;

namespace android {

void ImageConsumer::onFreeBufferLocked(int slotIndex) {
    // This callback may be invoked on any thread.
    mImageSlots[slotIndex].clear();
}

void ImageConsumer::onAcquireBufferLocked(BufferItem* item) {
    // If item->mGraphicBuffer is not null, this buffer has not been acquired
    // before, so any prior SkImage is created with a stale buffer. This resets the stale SkImage.
    if (item->mGraphicBuffer != nullptr) {
        mImageSlots[item->mSlot].clear();
    }
}

void ImageConsumer::onReleaseBufferLocked(int buf) {
    mImageSlots[buf].eglFence() = EGL_NO_SYNC_KHR;
}

/**
 * AutoBackendTextureRelease manages EglImage/VkImage lifetime. It is a ref-counted object
 * that keeps GPU resources alive until the last SKImage object using them is destroyed.
 */
class AutoBackendTextureRelease {
public:
    static void releaseProc(SkImage::ReleaseContext releaseContext);

    AutoBackendTextureRelease(GrContext* context, GraphicBuffer* buffer);

    const GrBackendTexture& getTexture() const { return mBackendTexture; }

    void ref() { mUsageCount++; }

    void unref(bool releaseImage);

    inline sk_sp<SkImage> getImage() { return mImage; }

    void makeImage(sp<GraphicBuffer>& graphicBuffer, android_dataspace dataspace,
                   GrContext* context);

private:
    // The only way to invoke dtor is with unref, when mUsageCount is 0.
    ~AutoBackendTextureRelease() {}

    GrBackendTexture mBackendTexture;
    GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
    GrAHardwareBufferUtils::DeleteImageCtx mDeleteCtx;

    // Starting with refcount 1, because the first ref is held by SurfaceTexture. Additional refs
    // are held by SkImages.
    int mUsageCount = 1;

    // mImage is the SkImage created from mBackendTexture.
    sk_sp<SkImage> mImage;
};

AutoBackendTextureRelease::AutoBackendTextureRelease(GrContext* context, GraphicBuffer* buffer) {
    bool createProtectedImage =
        0 != (buffer->getUsage() & GraphicBuffer::USAGE_PROTECTED);
    GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat(
        context,
        reinterpret_cast<AHardwareBuffer*>(buffer),
        buffer->getPixelFormat(),
        false);
    mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
        context,
        reinterpret_cast<AHardwareBuffer*>(buffer),
        buffer->getWidth(),
        buffer->getHeight(),
        &mDeleteProc,
        &mDeleteCtx,
        createProtectedImage,
        backendFormat,
        false);
}

void AutoBackendTextureRelease::unref(bool releaseImage) {
    if (!RenderThread::isCurrent()) {
        // EGLImage needs to be destroyed on RenderThread to prevent memory leak.
        // ~SkImage dtor for both pipelines needs to be invoked on RenderThread, because it is not
        // thread safe.
        RenderThread::getInstance().queue().post([this, releaseImage]() { unref(releaseImage); });
        return;
    }

    if (releaseImage) {
        mImage.reset();
    }

    mUsageCount--;
    if (mUsageCount <= 0) {
        if (mBackendTexture.isValid()) {
            mDeleteProc(mDeleteCtx);
            mBackendTexture = {};
        }
        delete this;
    }
}

void AutoBackendTextureRelease::releaseProc(SkImage::ReleaseContext releaseContext) {
    AutoBackendTextureRelease* textureRelease =
        reinterpret_cast<AutoBackendTextureRelease*>(releaseContext);
    textureRelease->unref(false);
}

void AutoBackendTextureRelease::makeImage(sp<GraphicBuffer>& graphicBuffer,
                                          android_dataspace dataspace, GrContext* context) {
    SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(
        graphicBuffer->getPixelFormat());
    mImage = SkImage::MakeFromTexture(context,
        mBackendTexture,
        kTopLeft_GrSurfaceOrigin,
        colorType,
        kPremul_SkAlphaType,
        uirenderer::DataSpaceToColorSpace(dataspace),
        releaseProc,
        this);
    if (mImage.get()) {
        // The following ref will be counteracted by releaseProc, when SkImage is discarded.
        ref();
    }
}

void ImageConsumer::ImageSlot::createIfNeeded(sp<GraphicBuffer> graphicBuffer,
                                              android_dataspace dataspace, bool forceCreate,
                                              GrContext* context) {
    if (!mTextureRelease || !mTextureRelease->getImage().get() || dataspace != mDataspace
            || forceCreate) {
        if (!graphicBuffer.get()) {
            clear();
            return;
        }

        if (!mTextureRelease) {
            mTextureRelease = new AutoBackendTextureRelease(context, graphicBuffer.get());
        }

        mDataspace = dataspace;
        mTextureRelease->makeImage(graphicBuffer, dataspace, context);
    }
}

void ImageConsumer::ImageSlot::clear() {
    if (mTextureRelease) {
        // The following unref counteracts the initial mUsageCount of 1, set by default initializer.
        mTextureRelease->unref(true);
        mTextureRelease = nullptr;
    }
}

sk_sp<SkImage> ImageConsumer::ImageSlot::getImage() {
    return mTextureRelease ? mTextureRelease->getImage() : nullptr;
}

sk_sp<SkImage> ImageConsumer::dequeueImage(bool* queueEmpty, SurfaceTexture& st,
                                           uirenderer::RenderState& renderState) {
    BufferItem item;
    status_t err;
    err = st.acquireBufferLocked(&item, 0);
    if (err != OK) {
        if (err != BufferQueue::NO_BUFFER_AVAILABLE) {
            IMG_LOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
        } else {
            int slot = st.mCurrentTexture;
            if (slot != BufferItem::INVALID_BUFFER_SLOT) {
                *queueEmpty = true;
                mImageSlots[slot].createIfNeeded(st.mSlots[slot].mGraphicBuffer,
                        st.mCurrentDataSpace, false, renderState.getRenderThread().getGrContext());
                return mImageSlots[slot].getImage();
            }
        }
        return nullptr;
    }

    int slot = item.mSlot;
    if (item.mFence->isValid()) {
        // Wait on the producer fence for the buffer to be ready.
        if (uirenderer::Properties::getRenderPipelineType() ==
            uirenderer::RenderPipelineType::SkiaGL) {
            err = renderState.getRenderThread().eglManager().fenceWait(item.mFence);
        } else {
            err = renderState.getRenderThread().vulkanManager().fenceWait(
                    item.mFence, renderState.getRenderThread().getGrContext());
        }
        if (err != OK) {
            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
                                   EGL_NO_SYNC_KHR);
            return nullptr;
        }
    }

    // Release old buffer.
    if (st.mCurrentTexture != BufferItem::INVALID_BUFFER_SLOT) {
        // If needed, set the released slot's fence to guard against a producer accessing the
        // buffer before the outstanding accesses have completed.
        sp<Fence> releaseFence;
        EGLDisplay display = EGL_NO_DISPLAY;
        if (uirenderer::Properties::getRenderPipelineType() ==
            uirenderer::RenderPipelineType::SkiaGL) {
            auto& eglManager = renderState.getRenderThread().eglManager();
            display = eglManager.eglDisplay();
            err = eglManager.createReleaseFence(st.mUseFenceSync, &mImageSlots[slot].eglFence(),
                                                releaseFence);
        } else {
            err = renderState.getRenderThread().vulkanManager().createReleaseFence(
                    releaseFence, renderState.getRenderThread().getGrContext());
        }
        if (OK != err) {
            st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
                                   EGL_NO_SYNC_KHR);
            return nullptr;
        }

        if (releaseFence.get()) {
            status_t err = st.addReleaseFenceLocked(
                    st.mCurrentTexture, st.mSlots[st.mCurrentTexture].mGraphicBuffer, releaseFence);
            if (err != OK) {
                IMG_LOGE("dequeueImage: error adding release fence: %s (%d)", strerror(-err), err);
                st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
                                       EGL_NO_SYNC_KHR);
                return nullptr;
            }
        }

        // Finally release the old buffer.
        status_t status = st.releaseBufferLocked(
                st.mCurrentTexture, st.mSlots[st.mCurrentTexture].mGraphicBuffer, display,
                mImageSlots[st.mCurrentTexture].eglFence());
        if (status < NO_ERROR) {
            IMG_LOGE("dequeueImage: failed to release buffer: %s (%d)", strerror(-status), status);
            err = status;
            // Keep going, with error raised.
        }
    }

    // Update the state.
    st.mCurrentTexture = slot;
    st.mCurrentCrop = item.mCrop;
    st.mCurrentTransform = item.mTransform;
    st.mCurrentScalingMode = item.mScalingMode;
    st.mCurrentTimestamp = item.mTimestamp;
    st.mCurrentDataSpace = item.mDataSpace;
    st.mCurrentFence = item.mFence;
    st.mCurrentFenceTime = item.mFenceTime;
    st.mCurrentFrameNumber = item.mFrameNumber;
    st.computeCurrentTransformMatrixLocked();

    *queueEmpty = false;
    mImageSlots[slot].createIfNeeded(st.mSlots[slot].mGraphicBuffer, item.mDataSpace, true,
        renderState.getRenderThread().getGrContext());
    return mImageSlots[slot].getImage();
}

} /* namespace android */