#include <memory>

#include "subdivision.h"
#include "triangle.h"

Subdivision::Subdivision(QOpenGLFunctions_4_3_Core *f)
{
    this->f =f;
    debugOutput = false;
}

Subdivision::~Subdivision()
{
    delete edgeShader;
}

void Subdivision::setDebugOutbut(bool debug){
    this->debugOutput = debug;
}

void Subdivision::init() {
    QString source = QLatin1String(":/subdivision-edge.compute");
    edgeShader = initComputeShaderProgram(source);

    source = QLatin1String(":/subdivision-vertex.compute");
    vertexShader = initComputeShaderProgram(source);
}

QOpenGLShaderProgram *Subdivision::initComputeShaderProgram(QString &source){
    qDebug()<<"Compiling compute shader ...";
    QOpenGLShader *computeShader = new QOpenGLShader(QOpenGLShader::Compute);
    if(!computeShader->compileSourceFile(source)){
        qCritical()<<"Compute Shader"<<source<<"failed"<<computeShader->log();
        exit(5);
    }

    qDebug()<<"Adding compute shader ...";
    QOpenGLShaderProgram *shader = new QOpenGLShaderProgram();
    shader->addShader(computeShader);
    qDebug()<<"Linking compute shader ...";
    if(!shader->link()){
        qCritical()<<"Linking compute shader failed:"<<shader->log();
        exit(5);
    }
    qDebug()<<"Linking compute shader done";

    return shader;
}

void Subdivision::subdivide(Mesh *mesh, int level) {
    // For now we only look at the first mesh entry
    Mesh::Node root = mesh->getRootNode();

    int first_mesh_index = -1;
    if (!root.getFirstMeshIndex(first_mesh_index)) {
        qCritical()<<"No mesh found, aborting subdivision";
        return;
    }


    Mesh::MeshEntry *current_mesh = mesh->getMeshEntry(first_mesh_index);
    Input input;

    int currentMax = current_mesh->buffers.size()-1;
    qDebug()<<"Subdiv Level"<<level<<"Requested. Current Max:"<<currentMax;
    if(level <= currentMax)
        return;

    std::shared_ptr<Mesh::SubdivEntry> entry = current_mesh->buffers[currentMax];

    input.vb_handle = entry->VB_handle;
    input.vertex_buffer = entry->vertices;
    input.index_buffer = entry->indices_irregular;

    if (input.index_buffer.isEmpty()) {
        current_mesh->update(input.vb_handle, input.vertex_buffer, input.index_buffer, entry->indices_regular);
    } else {
        Tables tables = precomputeTables(input);
        Result result = runShader(input, tables);

        QVector<unsigned int> regular;
        QVector<unsigned int> irregular;
        findRegular(tables.index_buffer, result.vertex_buffer, regular, irregular);

        qDebug() << "Indices" << tables.index_buffer.length();
        qDebug() << "regular" << regular.length();
        qDebug() << "irregular" << irregular.length();

        QVector<unsigned int> patches = getPatchIndexBuffer(regular);
        qDebug() << "patches" << patches.length();

        current_mesh->update(result.vb_handle, result.vertex_buffer, irregular, patches);
    }
}

void insert_edge(QMap<Triangle, QVector<Triangle::Edge>> &opposite_edges, const Triangle &triangle, const Triangle::Edge &edge_a, const Triangle::Edge &edge_b) {
    QVector<Triangle::Edge> edges = opposite_edges.value(triangle, QVector<Triangle::Edge>());
    edges.resize(3);
    switch (edge_a.name) {
        case Triangle::Edge::Name::uv:
            edges[0] = edge_b;
            break;
        case Triangle::Edge::Name::vw:
            edges[1] = edge_b;
            break;
        case Triangle::Edge::Name::wu:
            edges[2] = edge_b;
            break;
        default:
            qWarning() << "got" << edge_a.name << "as edge!";
            break;
    }
    opposite_edges.insert(triangle, edges);
}

struct Edge {
    unsigned int a;
    unsigned int b;

    friend bool operator<(const Edge &l, const Edge &r) {
        if (l.a < r.a) {
            return true;
        } else if (l.a > r.a) {
            return false;
        } else {
            return l.b < r.b;
        }
    }
};

/**
 * @brief Subdivision::precomputeTables
 * @param mesh
 * @param edgeIndices_base Wird gefüllt mit 4er-Arrays, die die indices in den alten Vertexbuffer enthalten, von denen der jeweilige edge-point abhängt
 * @param vertexIndices Wird gefüllt mit Folgen von ints, die die indices in den alten Vertexbuffer enthalten, von denen die neuen vertex-points abhängen
 * @param vertexIndicesOffsets enthält offsets in vertexIndices
 * @param newIndexBuffer Wird gefüllt mit neuem Indexbuffer. Der Indexbuffer geht davon aus, dass im neuen Vertex-Buffer
 * erst die Vertex-points (in der selben Reihenfolge wie im ursprünglichen Vertex-Buffer) und dann die Edgepoints kommen.
 *
 * Einträge in neuem Vertex-buffer: Verschobene Knoten aus altem Vertex-Buffer :: edge-vertices erstellt aus edgeIndices_base.
 */
Subdivision::Tables Subdivision::precomputeTables(Input input) {
    Tables tables;

    QVector<unsigned int> ib = input.index_buffer;
    if(debugOutput)
        qDebug()<<"Index Buffer: "<<ib;

    QVector<Vertex> vb = input.vertex_buffer;
    if(debugOutput)
        qDebug()<<"Vertex Buffer: "<<vb;

    QVector<Triangle> triangles;
    for (int i = 0; i < ib.length(); i+=3) {
        triangles.push_back(Triangle(vb, ib[i], ib[i+1], ib[i+2]));
    }

    //compute edge table
    //Format: first two entries: edge vertices. last two entries: distant vertices.

    // for each triangle the edges of the neighbor triangles are stored.
    // First the edge shared with the triangles uv edge, then vw and wu.
    QMap<Triangle, QVector<Triangle::Edge>> opposite_edges;

    // Maps edges to the index of the new vertex added on that edge. The keys are the indices of the two vertices defining the edge and must be in the order uv, vw or uw.
    QMap<Edge, unsigned int> edge_indices;

    unsigned int edge_index = vb.length();//offset in new index buffer for edge vertices
    for (int i = 0; i < triangles.length(); i++){
        //schaue alle dreiecke an
        Triangle triangle = triangles[i];

        QVector<unsigned int> edge_indices_buffer;//push all edge indices into this, then check if enough edge indices were found. if yes, copy to Tables and add to new index buffer.

        /*
         * Find edge xy in all other triangles, add to edge_indices
         * Find edge yz
         * Find edge zx
         * - We assume that we have a closed surface, i.e. each triangle has exactly one adjacent triangle at each edge
         * - Each edge point will be computed twice. This allows us to implicitly know the adjacent points.
         */

        Triangle::Edge edge_a, edge_b;
        for (int j = i + 1; j < triangles.length(); j++){
            if (triangle.get_shared_edge(triangles[j], edge_a, edge_b)) {
                insert_edge(opposite_edges, triangle, edge_a, edge_b);
                insert_edge(opposite_edges, triangles[j], edge_b, edge_a);
            }
        }

        QVector<Triangle::Edge> opposite = opposite_edges.value(triangle);

        // indices of the three vertices added to the edges of this triangle
        unsigned int uv, vw, wu;

        Edge edge;

        edge = { triangle.u_idx(), triangle.v_idx() };
        if (edge_indices.contains(edge)) {
            uv = edge_indices.value(edge);
        } else {
            uv = edge_index++;
            edge_indices.insert(edge, uv);

            edge = { opposite[0].a, opposite[0].b };
            edge_indices.insert(edge, uv);

            tables.edge_indices.push_back(triangle.u_idx());
            tables.edge_indices.push_back(triangle.v_idx());
            tables.edge_indices.push_back(triangle.w_idx());
            tables.edge_indices.push_back(opposite[0].c);
        }

        edge = { triangle.v_idx(), triangle.w_idx() };
        if (edge_indices.contains(edge)) {
            vw = edge_indices.value(edge);
        } else {
            vw = edge_index++;
            edge_indices.insert(edge, vw);

            edge = { opposite[1].a, opposite[1].b };
            edge_indices.insert(edge, vw);

            tables.edge_indices.push_back(triangle.v_idx());
            tables.edge_indices.push_back(triangle.w_idx());
            tables.edge_indices.push_back(triangle.u_idx());
            tables.edge_indices.push_back(opposite[1].c);
        }

        edge = { triangle.w_idx(), triangle.u_idx() };
        if (edge_indices.contains(edge)) {
            wu = edge_indices.value(edge);
        } else {
            wu = edge_index++;
            edge_indices.insert(edge, wu);

            edge = { opposite[2].a, opposite[2].b };
            edge_indices.insert(edge, wu);

            tables.edge_indices.push_back(triangle.w_idx());
            tables.edge_indices.push_back(triangle.u_idx());
            tables.edge_indices.push_back(triangle.v_idx());
            tables.edge_indices.push_back(opposite[2].c);
        }

        //add indices to new index buffer
        tables.index_buffer.push_back(triangle.u_idx());
        tables.index_buffer.push_back(uv);
        tables.index_buffer.push_back(wu);

        tables.index_buffer.push_back(triangle.v_idx());
        tables.index_buffer.push_back(vw);
        tables.index_buffer.push_back(uv);

        tables.index_buffer.push_back(triangle.w_idx());
        tables.index_buffer.push_back(wu);
        tables.index_buffer.push_back(vw);

        tables.index_buffer.push_back(uv);
        tables.index_buffer.push_back(vw);
        tables.index_buffer.push_back(wu);
    }

    qDebug()<<"Done with edge table. "<<tables.edge_indices.length();
    if (debugOutput){
        qDebug()<<"Eedges found. Table: "<<tables.edge_indices;
        qDebug()<<"Table (long version):";
        for (int i = 0; i < tables.edge_indices.length(); i++){
            qDebug()<<"blub:"<<tables.edge_indices[i];
        }
    }

    //compute vertex table
    //Format: First entry: original vertex. Other entries: adjacent vertices.
    QVector<QVector<unsigned int> > duplicates;//for debugging
    unsigned int offset = 0;
    for (int i = 0; i < vb.length(); i++){
        //hat evtl viel redundanz

        QVector<Vertex> adj_v;//helfer
        tables.vertex_offsets.push_back(offset);
        tables.vertex_indices.push_back(i);
        offset++;

        Vertex originalVertex = vb[i];

        QVector<unsigned int> d;

        for (int j = 0; j < ib.length(); j++){

            //suche verweise auf vertex im indexbuffer
            if (vb[ib[j]].samePos(originalVertex)){
                d.push_back(j);
                unsigned int i1, i2; //indices for neighbour vertices
                if(j%3==0){
                    i1 = ib[j+1];
                    i2 = ib[j+2];
                } else if(j%3==1){
                    i1 = ib[j-1];
                    i2 = ib[j+1];
                } else{
                    i1 = ib[j-2];
                    i2 = ib[j-1];
                }

                Vertex v1,v2; //neighbour vertices;
                v1 = vb[i1];
                v2 = vb[i2];
                //check if vertices where already used to influence this vertex
                //Note: Can't use contain. Contain uses equal (would have to implement equal, but we're only interested in the position here.
                bool found1 = false;
                bool found2 = false;
                for (int k = 0; k < adj_v.length(); k++){
                    if (adj_v[k].samePos(v1)) found1 = true;
                    if(adj_v[k].samePos(v2)) found2 = true;
                }
                if (!found1){
                    adj_v.push_back(v1);
                    tables.vertex_indices.push_back(i1);
                    offset++;
                }
                if (!found2){
                    adj_v.push_back(v2);
                    tables.vertex_indices.push_back(i2);
                    offset++;
                }
                //if one adjacent vertex is referenced by multiple different indices, only its first index will appear in the adjacent_indices list.1
            }
        }
        if (!duplicates.contains(d)) duplicates.push_back(d);
    }



    tables.vertex_offsets.push_back(offset);

    qDebug()<<"Done with vertex index table. ";
    if(debugOutput){
        qDebug()<<"Duplicates: ";
        for (int i = 0; i < duplicates.length(); i++){
            qDebug()<<duplicates[i];
        }
    }
    qDebug()<<"Precompute Tables Done";
    tables.index_regular = getPatchIndexBuffer(ib);
    return tables;
}

void Subdivision::splitRegular(Mesh *mesh) {
    Mesh::Node root = mesh->getRootNode();

    int first_mesh_index = -1;
    if (!root.getFirstMeshIndex(first_mesh_index)) {
        qCritical()<<"No mesh found, aborting subdivision";
        return;
    }

    Mesh::MeshEntry *current_mesh = mesh->getMeshEntry(first_mesh_index);

    QVector<unsigned int> regular;
    QVector<unsigned int> irregular;
    findRegular(current_mesh->buffers[0]->indices_irregular, current_mesh->buffers[0]->vertices, regular, irregular);
    findRegular(current_mesh->buffers[0]->indices_regular, current_mesh->buffers[0]->vertices, regular, irregular);

    QVector<unsigned int> patches = getPatchIndexBuffer(regular);
    current_mesh->buffers[0]->indices_regular = patches;
    current_mesh->buffers[0]->indices_irregular = irregular;
    current_mesh->buffers[0]->updateIndices();

    qDebug() << "regular: " << regular.length();
    qDebug() << "irregular: " << irregular.length();
}

/**
 * @brief Subdivision::getPatchIndexBuffer
 * Computes an index buffer for the patches. patches are in order from figure 6(a) from the paper:
 *        0     1
 *
 *     2     3     4
 *
 *  5     6     7     8
 *
 *     9     10    11
 *
 * with 367 being the original triangle.
 * Assumes that each vertex \exists only once in the vertex buffer, i.e. with regular patches, each index in the index buffer \exists 6 times.
 * @param ib original index buffer
 * @return patch index buffer with 4 times as many indices. ! if something goes wrong, the current buffer is returned and might be incomplete or empty.
 */
QVector<unsigned int> Subdivision::getPatchIndexBuffer(QVector<unsigned int> ib){
    QVector<unsigned int> pib;
    for (int i = 0; i < ib.length() - 2; i+=3){

        unsigned int i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11;

        //alles gegen den uzs
        i3 = ib[i];
        i6 = ib[i+1];
        i7 = ib[i+2];

        int count3 = 0; //counts other triangles with this vertex
        int count6 = 0;
        int count7 = 0;

        for (int j = 0; j < ib.length(); j++){
            if (j != i && j != i+1 && j != i+2){
                //for debugging // checking if patch is regular.
                if (ib[j] == i3) count3++;
                if (ib[j] == i6) count6++;
                if (ib[j] == i7) count7++;
            }
        }
        if (count3 != 5 || count6 != 5 || count7 != 5){
            qWarning()<<"Counts wrong! 3: "<< count3 <<", 6: "<<count6<<", 7: "<<count7;
        }


        //find triangles with shared edge. Fills i2, i4, i10.
        {
            bool found36 = false;
            bool found67 = false;
            bool found73 = false;
            for (int j = 0; j < ib.length() - 2; j+=3){
                unsigned int j0 = ib[j];
                unsigned int j1 = ib[j+1];
                unsigned int j2 = ib[j+2];

                //Dreieck angrenzend an Kante i3 i6
                found36 |= matchAndCompleteTriangle(j0,j1,j2,i6,i3,i2);
                //Dreieck angrenzend an Kante i6 i7
                found67 |= matchAndCompleteTriangle(j0,j1,j2,i7,i6,i10);
                //Dreieck angrenzend an Kante i7 i3
                found73 |= matchAndCompleteTriangle(j0,j1,j2,i3,i7,i4);
            }

            if (!(found36 && found67 && found73)){
                qWarning()<<"Didnt find neighbour. duplicate vertex? Abort.";
                return pib;
            }
        }


        //find last missing triangles.
        {
            bool found0 = false;
            bool found1 = false;
            bool found5 = false;
            bool found9 = false;
            bool found8 = false;
            bool found11 = false;
            for (int j = 0; j < ib.length() - 2; j+=3){
                unsigned int j0 = ib[j];
                unsigned int j1 = ib[j+1];
                unsigned int j2 = ib[j+2];

                //find i0. //TODO assert triangle 031 \exists somewhere

                found0 |= matchAndCompleteTriangle(j0,j1,j2,i2,i3,i0);
                found1 |= matchAndCompleteTriangle(j0,j1,j2,i3,i4,i1);
                //TODO maybe assert that triangle i0 i3 i1 actually \exists.

                found5 |= matchAndCompleteTriangle(j0,j1,j2,i6,i2,i5);
                found9 |= matchAndCompleteTriangle(j0,j1,j2,i10,i6,i9);

                //todo assert that triangle i5 i9 i6

                found8 |= matchAndCompleteTriangle(j0,j1,j2,i4,i7,i8);
                found11 |= matchAndCompleteTriangle(j0,j1,j2,i7,i10,i11);
            }
            if (!(found0 && found1 && found5 && found9 && found8 && found11)){
                qWarning()<<"Couldnt find some neighbour at i= "<<i;
                qWarning()<<found0<<found1<<found5<<found9<<found8<<found11;
                qWarning()<<"Abort computing patch index buffer.";
                return pib;
            }
            //else qWarning()<<"found all neighbours for patch.";
            pib.push_back(i0);
            pib.push_back(i1);
            pib.push_back(i2);
            pib.push_back(i3);
            pib.push_back(i4);
            pib.push_back(i5);
            pib.push_back(i6);
            pib.push_back(i7);
            pib.push_back(i8);
            pib.push_back(i9);
            pib.push_back(i10);
            pib.push_back(i11);
         }
    }

    return pib;
}

/**
 * Generates index buffers containing all regular and all irregular triangles in the input index buffer.
 *
 * @param index_buffer Index buffer describing triangles.
 * @param vertex_buffer Vertices used by index_buffer.
 * @param regular All regular triangles are appended to this index buffer.
 * @param irregular All irregular triangles are appended to this index buffer.
 */
void Subdivision::findRegular(QVector<unsigned int> index_buffer, QVector<Vertex> vertex_buffer, QVector<unsigned int> &regular, QVector<unsigned int> &irregular) {
    for (int i = 0; i < index_buffer.length()-2; i += 3){
        Vertex vx = vertex_buffer[index_buffer[i]];
        Vertex vy = vertex_buffer[index_buffer[i+1]];
        Vertex vz = vertex_buffer[index_buffer[i+2]];

        int countx = 0;
        int county = 0;
        int countz = 0;
        for (int j = 0; j < index_buffer.length(); j++){
            if (j != i && j != i+1 && j != i+2){
                if (vx.samePos(vertex_buffer[index_buffer[j]])){
                    countx++;
                }
                if (vy.samePos(vertex_buffer[index_buffer[j]])){
                    county++;
                }
                if (vz.samePos(vertex_buffer[index_buffer[j]])){
                    countz++;
                }
            }
        }
        if (countx == 5 && county == 5 && countz == 5){
            regular.push_back(index_buffer[i]);
            regular.push_back(index_buffer[i+1]);
            regular.push_back(index_buffer[i+2]);
        } else {
            irregular.push_back(index_buffer[i]);
            irregular.push_back(index_buffer[i+1]);
            irregular.push_back(index_buffer[i+2]);
        }
    }
}

bool Subdivision::matchAndCompleteTriangle(unsigned int sx, unsigned int sy, unsigned int sz, unsigned int tx, unsigned int ty, unsigned int &tz){
    if (sx == tx && sy == ty){
        tz = sz;
        return true;
    }
    if (sy == tx && sz == ty){
        tz = sx;
        return true;
    }
    if (sz == tx && sx == ty){
        tz = sy;
        return true;
    }
    return false;
}

Subdivision::Result Subdivision::runShader(Input input, Tables &tables) {
    qDebug()<<"Running compute shader";

    Result result;

    // Create an input buffer for the vertex indices
    GLuint vertex_indices_handle;
    f->glGenBuffers(1, &vertex_indices_handle);
    f->glBindBuffer(GL_SHADER_STORAGE_BUFFER, vertex_indices_handle);
    f->glBufferData(GL_SHADER_STORAGE_BUFFER, tables.vertex_indices.size() * sizeof(GLuint), tables.vertex_indices.data(), GL_DYNAMIC_DRAW);

    // Create an input buffer for the vertex offsets
    GLuint vertex_offsets_handle;
    f->glGenBuffers(1, &vertex_offsets_handle);
    f->glBindBuffer(GL_SHADER_STORAGE_BUFFER, vertex_offsets_handle);
    f->glBufferData(GL_SHADER_STORAGE_BUFFER, tables.vertex_offsets.size() * sizeof(GLuint), tables.vertex_offsets.data(), GL_DYNAMIC_DRAW);

    // Create an input buffer for the edge indices
    GLuint edge_indices_handle;
    f->glGenBuffers(1, &edge_indices_handle);
    f->glBindBuffer(GL_SHADER_STORAGE_BUFFER, edge_indices_handle);
    f->glBufferData(GL_SHADER_STORAGE_BUFFER, tables.edge_indices.size() * sizeof(GLuint), tables.edge_indices.data(), GL_DYNAMIC_DRAW);

    // Create an output buffer
    GLuint output_handle;
    f->glGenBuffers(1, &output_handle);
    f->glBindBuffer(GL_SHADER_STORAGE_BUFFER, output_handle);
    f->glBufferData(GL_SHADER_STORAGE_BUFFER, (input.vertex_buffer.size() + tables.edge_indices.size() / 4) * sizeof(Vertex), NULL, GL_DYNAMIC_DRAW);
    result.vb_handle = output_handle;

    f->glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    runVertexShader(input.vertex_buffer.size(), input.vb_handle, vertex_indices_handle, vertex_offsets_handle, output_handle);
    int edgeOffset = input.vertex_buffer.size();
    runEdgeShader(tables.edge_indices.size() / 4, input.vb_handle, edge_indices_handle, output_handle, edgeOffset);

    // Map the output buffer so we can read the results
    f->glBindBuffer(GL_SHADER_STORAGE_BUFFER, output_handle);
    Vertex *ptr;
    ptr = (Vertex *) f->glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_WRITE_ONLY);

    if(debugOutput)
        qDebug() << "New vertices:";

    for (int i = 0; i < input.vertex_buffer.size(); i++) {
        if(debugOutput)
            qDebug() << ptr[i].pos;
        result.vertex_buffer.push_back(ptr[i]);
    }
    if(debugOutput)
        qDebug() << "New edge points:";
    for (int i = 0; i < tables.edge_indices.size() / 4; i++) {
        if(debugOutput)
            qDebug() << ptr[edgeOffset + i].pos;
        result.vertex_buffer.push_back(ptr[edgeOffset + i]);
    }


    f->glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);

    // Delete the buffers the free the resources
    f->glDeleteBuffers(1, &vertex_indices_handle);
    f->glDeleteBuffers(1, &vertex_offsets_handle);
    f->glDeleteBuffers(1, &edge_indices_handle);

    return result;
}

void Subdivision::runVertexShader(GLuint size, GLuint vb_handle, GLuint vertex_indices_handle, GLuint vertex_offsets_handle, GLuint output_handle) {
    vertexShader->bind();

    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, vb_handle);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, vertex_indices_handle);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, vertex_offsets_handle);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, output_handle);

    // Run the shader
    f->glDispatchCompute(size, 1, 1);

    // Wait for the shader to complete and the data to be written back to the global memory
    f->glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

    // Unbind the buffers from the shader
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0);

    vertexShader->release();
}

void Subdivision::runEdgeShader(GLuint size, GLuint vb_handle, GLuint edge_indices_handle, GLuint output_handle, GLuint offset) {
    edgeShader->bind();

    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, vb_handle);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, edge_indices_handle);
    f->glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 2, output_handle, offset * sizeof(Vertex), size * sizeof(Vertex));

    // Run the shader
    f->glDispatchCompute(size, 1, 1);

    // Wait for the shader to complete and the data to be written back to the global memory
    f->glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

    // Unbind the buffers from the shader
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
    f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);

    edgeShader->release();
}
