
// GLPathStrokeState.cpp - implementation of OpenGL resources for a baked polar stroked path

#include "GLPathStrokeState.hpp"

GLPathStrokeState::GLPathStrokeState()
    : vbo(0)
    , component_type(GL_NONE)  // bogus
    , vao(0)
    , index_type(GL_NONE)  // bogus
    , draw_index_count(0)
    , components_per_element(0)
    , control_point_count(0)
    , valid(false)
    , empty(true)
{}

GLPathStrokeState::~GLPathStrokeState()
{
    invalidate();
}

void GLPathStrokeState::init()
{
    if (!vbo) {
        glCreateBuffers(1, &vbo);
    }
    if (!vao) {
        glCreateVertexArrays(1, &vao);
    }
    assert(vbo);
    assert(vao);
}

void GLPathStrokeState::configVertexBufferObject(size_t buffer_bytes,
                                                 size_t link_bytes, const void *index_data,
                                                 size_t aligned_link_bytes,
                                                 size_t coord_bytes, const void *coord_data)
{
    assert(vbo);

    // Load vertex buffer object
    void *uninitialized_data = NULL;
    glNamedBufferData(vbo, buffer_bytes, uninitialized_data, GL_STATIC_DRAW);
    // First in buffer: link indices
    glNamedBufferSubData(vbo, 0, link_bytes, index_data);
    // Second in buffer: 2D (x,y) control points
    glNamedBufferSubData(vbo, aligned_link_bytes, coord_bytes, coord_data);
}

void GLPathStrokeState::configVertexArrayObject(size_t component_size, size_t aligned_link_bytes)
{
    assert(vao);

    // Load vertex array object
    const GLuint position_attribute = 0;  // (x,y) position
    glEnableVertexArrayAttrib(vao, position_attribute);

    const GLboolean not_normalized = false;
    const GLint num_components = components_per_element==1 ? 3 : 2;
    glVertexArrayAttribFormat(vao, position_attribute, num_components, component_type, not_normalized, 0);
    const GLsizei stride = GLsizei(components_per_element*component_size);
    glVertexArrayVertexBuffer(vao, position_attribute, vbo, aligned_link_bytes, stride);
    glVertexArrayElementBuffer(vao, vbo);
}

void GLPathStrokeState::load(BakeIndexedStrokedPath &baker)
{
    init();

    const size_t num_coords = baker.numCoords();
    const size_t num_links = baker.numLinks();
    const size_t indices_per_link = baker.indicesPerLink();  // is 6

    if (baker.hasNontrivialConic()) {
        // Odd vertex indices identify rational quadratic interpolating control points.
        components_per_element = 1;
    } else {
        // No conics so no odd vertex indices needed to encode rational control points.
        components_per_element = 2;
    }

    component_type = GL_SHORT;
    size_t component_size = sizeof(GLshort);
    for (size_t i=0; i<num_coords; i++) {
        // Is the coordinate as a signed short different from the floating-point original?
        const float coord = baker.Coord(i);
        if (coord != GLshort(coord)) {
            // Yes, leave along as float data.
            component_type = GL_FLOAT;
            component_size = sizeof(GLfloat);
            break;
        }
    }

    GLshort* coord16bit = NULL;
    // Can we convert the control point array to 16-bit short values?
    if (component_type == GL_SHORT) {
        coord16bit = new GLshort[num_coords];
        if (coord16bit) {
            GLshort *p = coord16bit;
            for (size_t i=0; i<num_coords; i++) {
                const float f = baker.Coord(i);
                const GLshort s16 = GLshort(f);
                p[i] = s16;
            }
        } else {
            // Stick with floats due to memory allocation failure.
            component_type = GL_FLOAT;
            component_size = sizeof(GLfloat);
        }
    }

    const void *coord_data = &baker.Coord(0);
    if (coord16bit) {
        coord_data = coord16bit;
        assert(component_type == GL_SHORT);
        assert(component_size == sizeof(GLshort));
    } else {
        assert(component_type == GL_FLOAT);
        assert(component_size == sizeof(GLfloat));
    }

    GLuint num_elements = GLuint((components_per_element==2) ? num_coords/2 : num_coords);

    const void *index_data = baker.numLinks() > 0 ? &baker.Link(0) : NULL;
    index_type = GL_UNSIGNED_INT;
    int bytes_per_index = 4;
    GLubyte* ndx8bit = NULL;
    GLushort* ndx16bit = NULL;
    // Can we fit the indices into 8-bit or 16-bit indices?
    if (num_elements < 256) {
        // Yes, 8-bit indices work...
        ndx8bit = new GLubyte[indices_per_link*num_links];
        if (ndx8bit) {
            // Memory allocation success.
            index_type = GL_UNSIGNED_BYTE;
            bytes_per_index = 1;
            GLubyte* p = ndx8bit;
            for (size_t i=0; i<num_links; i++) {
                const StrokeLink c = baker.Link(i);
                for (size_t j=0; j<indices_per_link; j++) {
                    if (components_per_element == 2) {
                        assert(c.ndx[j] < 2*256);
                        assert((c.ndx[j] & 1) == 0);  // Should be even
                        p[j] = GLubyte(c.ndx[j] >> 1);
                    } else {
                        assert(c.ndx[j] < 256);
                        p[j] = GLubyte(c.ndx[j]);
                    }
                }
                p += indices_per_link;
            }
            index_data = ndx8bit;
        } else {
            // Memory failure; continue on as unsigned int indices.
        }
    } else if (num_elements < 65536) {
        // Yes, 16-bit indices work...
        ndx16bit = new GLushort[indices_per_link*num_links];
        if (ndx16bit) {
            // Memory allocation success.
            index_type = GL_UNSIGNED_SHORT;
            bytes_per_index = 2;
            GLushort* p = ndx16bit;
            for (size_t i=0; i<num_links; i++) {
                const StrokeLink &c = baker.Link(i);
                for (size_t j=0; j<indices_per_link; j++) {
                    if (components_per_element == 2) {
                        assert(c.ndx[j] < 2*65536);
                        assert((c.ndx[j] & 1) == 0);  // Should be even
                        p[j] = GLushort(c.ndx[j] >> 1);
                    } else {
                        assert(c.ndx[j] < 65536);
                        p[j] = GLushort(c.ndx[j]);
                    }
                }
                p += indices_per_link;
            }
            index_data = ndx16bit;
        } else {
            // Memory failure; continue on as unsigned int indices.
        }
    } else {
        // Requires 32-bit indices...
        assert(index_type == GL_UNSIGNED_INT);
        // Force 1 component vertex array, conics or not.
        components_per_element = 1;
        num_elements = GLuint(num_coords);
    }

    const size_t link_bytes = num_links*indices_per_link*bytes_per_index;
    const size_t aligned_link_bytes = ((link_bytes+3)>>2)<<2;
    const size_t coord_bytes = size_t(num_elements)*components_per_element*component_size;
    const size_t buffer_bytes = aligned_link_bytes + coord_bytes;

    configVertexBufferObject(buffer_bytes, link_bytes, index_data, aligned_link_bytes, coord_bytes, coord_data);

    // Delete allocated vertex and index arrays after loading VBO.
    delete [] ndx8bit;
    delete [] ndx16bit;
    delete [] coord16bit;

    configVertexArrayObject(component_size, aligned_link_bytes);

    // Finally update empty & valid bits.
    if (indices_per_link > 0) {
        draw_index_count = GLuint(indices_per_link*num_links);
        control_point_count = num_elements-1;  // XXX wrong if components_per_element==1
        empty = false;
    } else {
        draw_index_count = 0;
        control_point_count = 0;
        empty = true;
    }
    valid = true;
}

void GLPathStrokeState::draw() const
{
    assert(valid);
    assert(vao);
    if (draw_index_count > 0) {
        glBindVertexArray(vao);
        glDrawElements(GL_PATCHES, draw_index_count, index_type, 0);
    } else {
        assert(draw_index_count == 0);
    }
}

void GLPathStrokeState::invalidate()
{
    if (vbo) {
        glDeleteBuffers(1, &vbo);
        vbo = 0;
    }
    if (vao) {
        glDeleteVertexArrays(1, &vao);
        vao = 0;
    }
    draw_index_count = 0;
    control_point_count = 0;
    valid = false;
}

void GLPathStrokeState::markEmpty()
{
    invalidate();
    empty = true;
    valid = true;
}

// Extended methods of GLPathStrokeStateExt

static size_t bytesPerIndexType(GLenum index_type)
{
    switch (index_type) {
    case GL_UNSIGNED_BYTE:
        return sizeof(GLubyte);
    case GL_UNSIGNED_SHORT:
        return sizeof(GLushort);
    default:
        assert(!"bad type");
    case GL_UNSIGNED_INT:
        return sizeof(GLuint);
    }
}

void GLPathStrokeStateExt::drawPatch(int ndx) const
{
    assert(valid);
    assert(vao);
    assert(ndx < numPatches());

    glBindVertexArray(vao);
    const int verts_per_stroke = 6;

    GLubyte *ptr = 0;
    ptr += size_t(ndx) * verts_per_stroke * bytesPerIndexType(index_type);
    glDrawElements(GL_PATCHES, verts_per_stroke, index_type, ptr);
}

int GLPathStrokeStateExt::numPatches() const
{
    assert(valid);
    return draw_index_count/6;
}

void GLPathStrokeStateExt::drawControlPoints() const
{
    assert(valid);
    assert(components_per_element == 2);  // XXX revisit
    assert(vao);
    glBindVertexArray(vao);
    // Start with index 1 to skip special join indictor vertex index.
    glDrawArrays(GL_POINTS, 1, control_point_count);
}