/*
    KWin - the KDE window manager
    This file is part of the KDE project.

    SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "drm_virtual_egl_layer.h"
#include "drm_egl_backend.h"
#include "drm_gpu.h"
#include "drm_logging.h"
#include "drm_virtual_output.h"
#include "opengl/eglnativefence.h"
#include "opengl/eglswapchain.h"
#include "opengl/glrendertimequery.h"
#include "scene/surfaceitem_wayland.h"
#include "utils/envvar.h"
#include "wayland/surface.h"

#include <drm_fourcc.h>
#include <errno.h>
#include <gbm.h>
#include <unistd.h>

namespace KWin
{

VirtualEglGbmLayer::VirtualEglGbmLayer(EglGbmBackend *eglBackend, DrmVirtualOutput *output)
    : DrmOutputLayer(output, OutputLayerType::Primary)
    , m_eglBackend(eglBackend)
{
}

VirtualEglGbmLayer::~VirtualEglGbmLayer()
{
    releaseBuffers();
}

std::optional<OutputLayerBeginFrameInfo> VirtualEglGbmLayer::doBeginFrame()
{
    m_scanoutBuffer.reset();

    if (doesGbmSwapchainFit(m_gbmSwapchain.get())) {
        m_oldGbmSwapchain.reset();
        m_oldDamageJournal.clear();
    } else {
        if (doesGbmSwapchainFit(m_oldGbmSwapchain.get())) {
            m_gbmSwapchain = m_oldGbmSwapchain;
            m_damageJournal = m_oldDamageJournal;
        } else {
            if (const auto swapchain = createGbmSwapchain()) {
                m_oldGbmSwapchain = m_gbmSwapchain;
                m_oldDamageJournal = m_damageJournal;
                m_gbmSwapchain = swapchain;
                m_damageJournal = DamageJournal();
            } else {
                return std::nullopt;
            }
        }
    }

    if (!m_eglBackend->openglContext()->makeCurrent()) {
        return std::nullopt;
    }

    auto slot = m_gbmSwapchain->acquire();
    if (!slot) {
        return std::nullopt;
    }

    m_currentSlot = slot;

    m_query = std::make_unique<GLRenderTimeQuery>(m_eglBackend->openglContextRef());
    m_query->begin();

    const Region repair = m_damageJournal.accumulate(slot->age(), Region::infinite());
    return OutputLayerBeginFrameInfo{
        .renderTarget = RenderTarget(slot->framebuffer()),
        .repaint = repair,
    };
}

bool VirtualEglGbmLayer::doEndFrame(const Region &renderedDeviceRegion, const Region &damagedDeviceRegion, OutputFrame *frame)
{
    m_query->end();
    frame->addRenderTimeQuery(std::move(m_query));
    glFlush();
    m_damageJournal.add(damagedDeviceRegion);

    EGLNativeFence releaseFence{m_eglBackend->eglDisplayObject()};
    m_gbmSwapchain->release(m_currentSlot, releaseFence.takeFileDescriptor());
    return true;
}

std::shared_ptr<EglSwapchain> VirtualEglGbmLayer::createGbmSwapchain() const
{
    static bool modifiersEnvSet = false;
    static const bool modifiersEnv = qEnvironmentVariableIntValue("KWIN_DRM_USE_MODIFIERS", &modifiersEnvSet) != 0;
    const bool allowModifiers = !modifiersEnvSet || modifiersEnv;

    const auto tranches = m_eglBackend->tranches();
    for (const auto &tranche : tranches) {
        for (auto it = tranche.formatTable.constBegin(); it != tranche.formatTable.constEnd(); it++) {
            const auto size = m_output->modeSize();
            const auto format = it.key();
            const auto modifiers = it.value();

            if (allowModifiers && !modifiers.empty()) {
                if (auto swapchain = EglSwapchain::create(m_eglBackend->gpu()->drmDevice()->allocator(), m_eglBackend->openglContext(), size, format, modifiers)) {
                    return swapchain;
                }
            }

            static const ModifierList implicitModifier{DRM_FORMAT_MOD_INVALID};
            if (auto swapchain = EglSwapchain::create(m_eglBackend->gpu()->drmDevice()->allocator(), m_eglBackend->openglContext(), size, format, implicitModifier)) {
                return swapchain;
            }
        }
    }
    qCWarning(KWIN_DRM) << "couldn't create a gbm swapchain for a virtual output!";
    return nullptr;
}

bool VirtualEglGbmLayer::doesGbmSwapchainFit(EglSwapchain *swapchain) const
{
    return swapchain && swapchain->size() == m_output->modeSize();
}

bool VirtualEglGbmLayer::earlyScanoutChecks()
{
    return true;
}

bool VirtualEglGbmLayer::importScanoutBuffer(GraphicsBuffer *buffer, const std::shared_ptr<OutputFrame> &frame)
{
    m_scanoutBuffer = buffer;
    return true;
}

void VirtualEglGbmLayer::releaseBuffers()
{
    (void)m_eglBackend->openglContext()->makeCurrent();
    m_gbmSwapchain.reset();
    m_oldGbmSwapchain.reset();
    m_currentSlot.reset();
    m_scanoutBuffer.reset();
}

DrmDevice *VirtualEglGbmLayer::scanoutDevice() const
{
    return m_eglBackend->drmDevice();
}

FormatModifierMap VirtualEglGbmLayer::supportedDrmFormats() const
{
    return m_eglBackend->supportedFormats();
}

}
