/*
 * Copyright © 2014 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

/**
 * @file glamor_vbo.c
 *
 * Helpers for managing streamed vertex buffers used in glamor.
 */
#include <dix-config.h>

#include "glamor_priv.h"

/** Default size of the VBO, in bytes.
 *
 * If a single request is larger than this size, we'll resize the VBO
 * and return an appropriate mapping, but we'll resize back down after
 * that to avoid hogging that memory forever.  We don't anticipate
 * normal usage actually requiring larger VBO sizes.
 */
#define GLAMOR_VBO_SIZE (512 * 1024)

/**
 * Returns a pointer to @size bytes of VBO storage, which should be
 * accessed by the GL using vbo_offset within the VBO.
 */
void *
glamor_get_vbo_space(ScreenPtr screen, unsigned size, char **vbo_offset)
{
    glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
    void *data;

    glamor_make_current(glamor_priv);

    glBindBuffer(GL_ARRAY_BUFFER, glamor_priv->vbo);

    if (glamor_priv->has_buffer_storage) {
        if (glamor_priv->vbo_size < glamor_priv->vbo_offset + size) {
            if (glamor_priv->vbo_size)
                glUnmapBuffer(GL_ARRAY_BUFFER);

            if (size > glamor_priv->vbo_size) {
                glamor_priv->vbo_size = MAX(GLAMOR_VBO_SIZE, size);

                /* We aren't allowed to resize glBufferStorage()
                 * buffers, so we need to gen a new one.
                 */
                glDeleteBuffers(1, &glamor_priv->vbo);
                glGenBuffers(1, &glamor_priv->vbo);
                glBindBuffer(GL_ARRAY_BUFFER, glamor_priv->vbo);

                assert(glGetError() == GL_NO_ERROR);
                glBufferStorage(GL_ARRAY_BUFFER, glamor_priv->vbo_size, NULL,
                                GL_MAP_WRITE_BIT |
                                GL_MAP_PERSISTENT_BIT |
                                GL_MAP_COHERENT_BIT);

                if (glGetError() != GL_NO_ERROR) {
                    /* If the driver failed our coherent mapping, fall
                     * back to the ARB_mbr path.
                     */
                    glamor_priv->has_buffer_storage = false;
                    glamor_priv->vbo_size = 0;

                    return glamor_get_vbo_space(screen, size, vbo_offset);
                }
            }

            glamor_priv->vbo_offset = 0;
            glamor_priv->vb = glMapBufferRange(GL_ARRAY_BUFFER,
                                               0, glamor_priv->vbo_size,
                                               GL_MAP_WRITE_BIT |
                                               GL_MAP_INVALIDATE_BUFFER_BIT |
                                               GL_MAP_PERSISTENT_BIT |
                                               GL_MAP_COHERENT_BIT);
        }
        *vbo_offset = (void *)(uintptr_t)glamor_priv->vbo_offset;
        data = glamor_priv->vb + glamor_priv->vbo_offset;
        glamor_priv->vbo_offset += size;
    } else if (glamor_priv->has_map_buffer_range) {
        /* Avoid GL errors on GL 4.5 / ES 3.0 with mapping size == 0,
         * which callers may sometimes pass us (for example, if
         * clipping leads to zero rectangles left).  Prior to that
         * version, Mesa would sometimes throw errors on unmapping a
         * zero-size mapping.
         */
        if (size == 0)
            return NULL;

        if (glamor_priv->vbo_size < glamor_priv->vbo_offset + size) {
            glamor_priv->vbo_size = MAX(GLAMOR_VBO_SIZE, size);
            glamor_priv->vbo_offset = 0;
            glBufferData(GL_ARRAY_BUFFER,
                         glamor_priv->vbo_size, NULL, GL_STREAM_DRAW);
        }

        data = glMapBufferRange(GL_ARRAY_BUFFER,
                                glamor_priv->vbo_offset,
                                size,
                                GL_MAP_WRITE_BIT |
                                GL_MAP_UNSYNCHRONIZED_BIT |
                                GL_MAP_INVALIDATE_RANGE_BIT);
        *vbo_offset = (char *)(uintptr_t)glamor_priv->vbo_offset;
        glamor_priv->vbo_offset += size;
        glamor_priv->vbo_mapped = TRUE;
    } else {
        /* Return a pointer to the statically allocated non-VBO
         * memory. We'll upload it through glBufferData() later.
         */
        if (glamor_priv->vbo_size < size) {
            glamor_priv->vbo_size = MAX(GLAMOR_VBO_SIZE, size);
            free(glamor_priv->vb);
            glamor_priv->vb = XNFalloc(glamor_priv->vbo_size);
        }
        *vbo_offset = NULL;
        /* We point to the start of glamor_priv->vb every time, and
         * the vbo_offset determines the size of the glBufferData().
         */
        glamor_priv->vbo_offset = size;
        data = glamor_priv->vb;
    }

    return data;
}

void
glamor_put_vbo_space(ScreenPtr screen)
{
    glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);

    glamor_make_current(glamor_priv);

    if (glamor_priv->has_buffer_storage) {
        /* If we're in the ARB_buffer_storage path, we have a
         * persistent mapping, so we can leave it around until we
         * reach the end of the buffer.
         */
    } else if (glamor_priv->has_map_buffer_range) {
        if (glamor_priv->vbo_mapped) {
            glUnmapBuffer(GL_ARRAY_BUFFER);
            glamor_priv->vbo_mapped = FALSE;
        }
    } else {
        glBufferData(GL_ARRAY_BUFFER, glamor_priv->vbo_offset,
                     glamor_priv->vb, GL_DYNAMIC_DRAW);
    }

    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void
glamor_init_vbo(ScreenPtr screen)
{
    glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);

    glamor_make_current(glamor_priv);

    glGenBuffers(1, &glamor_priv->vbo);
    glGenVertexArrays(1, &glamor_priv->vao);
    glBindVertexArray(glamor_priv->vao);
}

void
glamor_fini_vbo(ScreenPtr screen)
{
    glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);

    glamor_make_current(glamor_priv);

    glDeleteVertexArrays(1, &glamor_priv->vao);
    glamor_priv->vao = 0;
    if (!glamor_priv->has_map_buffer_range)
        free(glamor_priv->vb);
}
