
// StrokingBufferState.cpp - GPU buffers for arc length for paths

#include <assert.h>
#include <stdio.h>

#include "countof.h"
#include "verbose.h"

#include "StrokingBufferState.hpp"

StrokingBufferState::StrokingBufferState()
    : global_ubo(ubo[0])
    , link_ubo(ubo[1])
    , segment_distance_ubo(ubo[2])
    , segment_add_ops_ubo(ubo[3])
    , next_distance_offset(0)
{
    for (size_t i=0; i<countof(ubo); i++) {
        ubo[i] = 0;
    }
}

void StrokingBufferState::resetNextDistanceOffset()
{
    // Reset next_distance_offset to zero.
    next_distance_offset = 0;
    glNamedBufferData(global_ubo, sizeof(next_distance_offset), &next_distance_offset, GL_DYNAMIC_COPY);
}

void StrokingBufferState::resetBuffers()
{
    resetNextDistanceOffset();

    glNamedBufferData(link_ubo, max_links*sizeof(GLuint), NULL, GL_DYNAMIC_COPY);

    glNamedBufferData(segment_distance_ubo, max_segments*sizeof(GLfloat), NULL, GL_DYNAMIC_COPY);
    glNamedBufferData(segment_add_ops_ubo, max_segments*sizeof(GLuint), NULL, GL_DYNAMIC_COPY);

    const GLfloat zero_f = 0;
    glClearNamedBufferData(link_binding_index, GL_R32F, GL_INTENSITY, GL_FLOAT, &zero_f);
    glClearNamedBufferData(segment_distance_ubo, GL_R32F, GL_INTENSITY, GL_FLOAT, &zero_f);
    const GLuint zero_ui = 0;
    glClearNamedBufferData(segment_add_ops_ubo, GL_R32UI, GL_INTENSITY, GL_UNSIGNED_INT, &zero_ui);
}

void StrokingBufferState::init()
{
    glCreateBuffers(NUM_BUFFERS, ubo);
    assert(global_ubo);
    assert(link_ubo);
    assert(segment_distance_ubo);
    assert(segment_add_ops_ubo);

    resetBuffers();
}

void StrokingBufferState::bind()
{
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, global_binding_index, global_ubo);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, link_binding_index, link_ubo);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, segment_distance_binding_index, segment_distance_ubo);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, segment_add_ops_binding_index, segment_add_ops_ubo);
}

void StrokingBufferState::advanceNextDistanceOffset(size_t num_links)
{
    next_distance_offset += GLuint(2*num_links);
}

unsigned int StrokingBufferState::nextDistanceOffset()
{
    if (next_distance_offset >= max_links) {
        static unsigned int wrap_count = 0;
        wrap_count++;
        if (verbose) {
            printf("%u: wrap distance_offset = %d\n", wrap_count, next_distance_offset);
        }
        next_distance_offset = next_distance_offset & (max_links-1);
    }
    return next_distance_offset;
}

void StrokingBufferState::dumpBuffers(size_t links)
{
    typedef unsigned int uint;

    GLuint next_distance_offset = 0;
    glGetNamedBufferSubData(global_ubo, 0, sizeof(GLuint), &next_distance_offset);
    printf("next_distance_offset = %d\n", next_distance_offset);

    GLuint* offsets =  new GLuint[max_links];
    glGetNamedBufferSubData(link_ubo, 0, max_links*sizeof(GLuint), offsets);
    const size_t count = (2*links < max_links) ? 2*links : max_links;
    for (size_t i=0; i<count; i+=2) {
        const uint from(offsets[i]);
        const uint to(offsets[i+1]);
        printf("%u: offset = %u to %u\n", uint(i>>1), from, to);
    }
    delete [] offsets;

    GLfloat* segment_distance = new GLfloat[max_segments];
    glGetNamedBufferSubData(segment_distance_ubo, 0, max_segments*sizeof(GLfloat), segment_distance);
    for (size_t i=0; i<next_distance_offset; i++) {
        printf("%u: distance = %f\n", uint(i), segment_distance[i]);
    }
    delete [] segment_distance;

    GLuint* segment_add_ops = new GLuint[max_segments];
    glGetNamedBufferSubData(segment_add_ops_ubo, 0, max_segments*sizeof(GLuint), segment_add_ops);
    for (size_t i=0; i<next_distance_offset; i++) {
        printf("%u: add ops = %u\n", uint(i), segment_add_ops[i]);
    }
    delete [] segment_add_ops;
}
