// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qopenglvideobuffer_p.h"

#include <qoffscreensurface.h>
#include <qthread.h>
#include <private/qimagevideobuffer_p.h>

#include <QtOpenGL/private/qopenglcompositor_p.h>
#include <QtOpenGL/private/qopenglframebufferobject_p.h>

QT_BEGIN_NAMESPACE

static QOpenGLContext *createContext(QOpenGLContext *shareContext)
{
    // Create an OpenGL context for the current thread. The lifetime of the context is tied to the
    // lifetime of the current thread.
    auto context = std::make_unique<QOpenGLContext>();
    context->setShareContext(shareContext);

    if (!context->create()) {
        qWarning() << "Couldn't create an OpenGL context for QOpenGLVideoBuffer";
        return nullptr;
    }

    QObject::connect(QThread::currentThread(), &QThread::finished,
                     context.get(), &QOpenGLContext::deleteLater);
    return context.release();
}

static bool setCurrentOpenGLContext()
{
    auto compositorContext = QOpenGLCompositor::instance()->context();

    // A thread-local variable is used to avoid creating a new context if we're called on the same
    // thread. The context lifetime is tied to the current thread lifetime (see createContext()).
    static thread_local QOpenGLContext *context = nullptr;
    static thread_local QOffscreenSurface *surface = nullptr;

    if (!context) {
        context = (compositorContext->thread() == QThread::currentThread())
                ? compositorContext
                : createContext(compositorContext);

        if (!context)
            return false;

        surface = new QOffscreenSurface(nullptr, context);
        surface->setFormat(context->format());
        surface->create();
    }

    return context->makeCurrent(surface);
}

QOpenGLVideoBuffer::QOpenGLVideoBuffer(std::unique_ptr<QOpenGLFramebufferObject> fbo)
    : QAbstractVideoBuffer(QVideoFrame::RhiTextureHandle), m_fbo(std::move(fbo))
{
    Q_ASSERT(m_fbo);
}

QOpenGLVideoBuffer::~QOpenGLVideoBuffer() { }

QVideoFrame::MapMode QOpenGLVideoBuffer::mapMode() const
{
    return m_imageBuffer ? m_imageBuffer->mapMode() : QVideoFrame::NotMapped;
}

QAbstractVideoBuffer::MapData QOpenGLVideoBuffer::map(QVideoFrame::MapMode mode)
{
    return ensureImageBuffer().map(mode);
}

void QOpenGLVideoBuffer::unmap()
{
    if (m_imageBuffer)
        m_imageBuffer->unmap();
}

quint64 QOpenGLVideoBuffer::textureHandle(int plane) const
{
    Q_UNUSED(plane);
    return m_fbo->texture();
}

QImageVideoBuffer &QOpenGLVideoBuffer::ensureImageBuffer()
{
    // Create image buffer if not yet created.
    // This is protected by mapMutex in QVideoFrame::map.
    if (!m_imageBuffer) {
        if (!setCurrentOpenGLContext())
            qWarning() << "Failed to set current OpenGL context";

        m_imageBuffer = std::make_unique<QImageVideoBuffer>(m_fbo->toImage(false));
    }

    return *m_imageBuffer;
}

QT_END_NAMESPACE
