/*
 * Copyright (C) 2022 Metrological Group B.V.
 * Copyright (C) 2022 Igalia S.L.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "GBMBufferSwapchain.h"

#if USE(GBM)

#include "DMABufColorSpace.h"
#include "DRMDeviceManager.h"
#include <gbm.h>
#include <wtf/SafeStrerror.h>

namespace WebCore {

GBMBufferSwapchain::GBMBufferSwapchain(BufferSwapchainSize size)
{
    ASSERT(unsigned(size) <= c_maxBuffers);
    m_array.size = unsigned(size);
}

GBMBufferSwapchain::~GBMBufferSwapchain() = default;

static inline bool isBufferFormatSupported(const DMABufFormat& format)
{
    switch (format.fourcc) {
    case DMABufFormat::FourCC::XRGB8888:
    case DMABufFormat::FourCC::XBGR8888:
    case DMABufFormat::FourCC::RGBX8888:
    case DMABufFormat::FourCC::BGRX8888:
    case DMABufFormat::FourCC::ARGB8888:
    case DMABufFormat::FourCC::ABGR8888:
    case DMABufFormat::FourCC::RGBA8888:
    case DMABufFormat::FourCC::BGRA8888:
    case DMABufFormat::FourCC::I420:
    case DMABufFormat::FourCC::YV12:
    case DMABufFormat::FourCC::A420:
    case DMABufFormat::FourCC::NV12:
    case DMABufFormat::FourCC::NV21:
    case DMABufFormat::FourCC::YUY2:
    case DMABufFormat::FourCC::YVYU:
    case DMABufFormat::FourCC::UYVY:
    case DMABufFormat::FourCC::VYUY:
    case DMABufFormat::FourCC::VUYA:
    case DMABufFormat::FourCC::AYUV:
    case DMABufFormat::FourCC::Y444:
    case DMABufFormat::FourCC::Y41B:
    case DMABufFormat::FourCC::Y42B:
    case DMABufFormat::FourCC::P010:
    case DMABufFormat::FourCC::P016:
        return true;
    default:
        return false;
    }
}

RefPtr<GBMBufferSwapchain::Buffer> GBMBufferSwapchain::getBuffer(const BufferDescription& description)
{
    auto* device = DRMDeviceManager::singleton().mainGBMDeviceNode(DRMDeviceManager::NodeType::Render);
    if (!device) {
        WTFLogAlways("Failed to get GBM buffer from swap chain: no GBM device found");
        return nullptr;
    }

    // If the description of the requested buffers has changed, update the description to the new one and wreck the existing buffers.
    // This should handle changes in format or dimension of the buffers.
    if (description.format.fourcc != m_array.description.format.fourcc || description.width != m_array.description.width || description.height != m_array.description.height || description.flags != m_array.description.flags) {
        m_array.description = description;
        m_array.object = { };
    }

    if (!isBufferFormatSupported(description.format)) {
        WTFLogAlways("Failed to get GBM buffer from swap chain: unsupported format");
        return nullptr;
    }

    // Swapchain was asked to provide a buffer. The buffer array is traversed to find one.
    for (unsigned i = 0; i < m_array.size; ++i) {
        if (!m_array.object[i]) {
            // If no buffer was spawned yet at this location, we do that, and return it.
            auto buffer = adoptRef(*new Buffer(m_handleGenerator++, description));

            // Fill out the buffer's description and plane information for known and supported formats.
            buffer->m_description.format.numPlanes = description.format.numPlanes;
            for (unsigned i = 0; i < buffer->m_description.format.numPlanes; ++i) {
                buffer->m_planes[i].fourcc = description.format.planes[i].fourcc;
                buffer->m_planes[i].width = description.format.planeWidth(i, description.width);
                buffer->m_planes[i].height = description.format.planeHeight(i, description.height);
            }

            uint32_t boFlags = GBM_BO_USE_RENDERING;
            if (description.flags & BufferDescription::LinearStorage)
                boFlags |= GBM_BO_USE_LINEAR;

            // For each plane, we spawn a gbm_bo object of the appropriate size and format.
            // TODO: GBM_BO_USE_LINEAR will be needed when transferring memory into the bo (e.g. copying
            // over the software-decoded video data), but might not be required for backing e.g. ANGLE rendering.
            for (unsigned i = 0; i < buffer->m_description.format.numPlanes; ++i) {
                auto& plane = buffer->m_planes[i];
                plane.bo = gbm_bo_create(device, plane.width, plane.height, uint32_t(plane.fourcc), boFlags);
                if (!plane.bo) {
                    WTFLogAlways("Failed to get GBM buffer from swap chain: error creating plane %u of size %dx%d and format %u: %s\n",
                        i, plane.width, plane.height, uint32_t(plane.fourcc), safeStrerror(errno).data());
                    return nullptr;
                }
                plane.stride = gbm_bo_get_stride(plane.bo);
            }

            // Lock the buffer and return it.
            buffer->m_state.locked = true;
            m_array.object[i] = buffer.copyRef();
            return buffer;
        }

        // There is already an existing buffer at this location. If marked as locked, update its state by reading
        // the release flag. If still locked after that, we have to continue over to the next candidate.
        {
            auto& buffer = m_array.object[i];
            if (buffer->m_state.locked)
                buffer->m_state.locked = !buffer->m_state.releaseFlag.released();

            if (buffer->m_state.locked)
                continue;
        }

        // This buffer was unlocked, so it can be used. Lock and return it.
        auto buffer = m_array.object[i].copyRef();
        buffer->m_state.locked = true;

        // Swap out the located buffer to the end of the buffer queue. When the next buffer is requested from this
        // swapchain, the previously-used buffers will be traversed and tested for release first, meaning there's a
        // higher chance we will be able to reuse them, without the penalty of having to traverse to deep into the
        // swapchain array.
        for (++i; i < m_array.size && !!m_array.object[i]; ++i)
            std::swap(m_array.object[i - 1], m_array.object[i]);

        return buffer;
    }

    WTFLogAlways("Failed to get GBM buffer from swap chain: no buffers available");
    return nullptr;
}

GBMBufferSwapchain::Buffer::Buffer(uint32_t handle, const BufferDescription& description)
    : m_handle(handle)
    , m_description(description)
{
    m_state.releaseFlag = DMABufReleaseFlag(DMABufReleaseFlag::Initialize);
}

GBMBufferSwapchain::Buffer::~Buffer() = default;

DMABufObject GBMBufferSwapchain::Buffer::createDMABufObject(uintptr_t handle) const
{
    DMABufObject object(handle);
    object.format = m_description.format;
    object.width = m_description.width;
    object.height = m_description.height;
    object.releaseFlag = m_state.releaseFlag.dup();

    for (unsigned i = 0; i < m_description.format.numPlanes; ++i) {
        object.fd[i] = UnixFileDescriptor { gbm_bo_get_fd(m_planes[i].bo), UnixFileDescriptor::Adopt };
        object.offset[i] = 0;
        object.stride[i] = m_planes[i].stride;
        object.modifierPresent[i] = true;
        object.modifierValue[i] = gbm_bo_get_modifier(m_planes[i].bo);
    }

    return object;
}

GBMBufferSwapchain::Buffer::PlaneData::~PlaneData()
{
    if (bo)
        gbm_bo_destroy(bo);
}

} // namespace WebCore

#endif // USE(GBM)
