#include "crBatchedBillboards.h"

#include "crGraphicsStats.h"

void crBatchedBillboards::Init()
{
    glGenVertexArrays( 1, &_vao );
    glGenBuffers( 1, &_quadVbo );
    glGenBuffers( 1, &_instanceVbo );

    glBindVertexArray( _vao );

    // shared unit quad (TRIANGLE_STRIP): corner offsets + texcoords, centered — the vertex shader
    // expands the corners in the per-particle view-facing basis. v=0 = image top (sprite mapping)
    const float quad[] =
    {
        -0.5f, -0.5f,   0.0f, 1.0f,
         0.5f, -0.5f,   1.0f, 1.0f,
        -0.5f,  0.5f,   0.0f, 0.0f,
         0.5f,  0.5f,   1.0f, 0.0f,
    };
    glBindBuffer( GL_ARRAY_BUFFER, _quadVbo );
    glBufferData( GL_ARRAY_BUFFER, sizeof( quad ), quad, GL_STATIC_DRAW );

    glEnableVertexAttribArray( 0 );
    glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof( float ), reinterpret_cast<void*>( 0 ) );
    glEnableVertexAttribArray( 1 );
    glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof( float ), reinterpret_cast<void*>( 2 * sizeof( float ) ) );

    // per-instance stream — locations 2..6, divisor 1
    glBindBuffer( GL_ARRAY_BUFFER, _instanceVbo );

    const GLsizei stride = static_cast<GLsizei>( sizeof( BillboardInstance ) );
    glEnableVertexAttribArray( 2 );
    glVertexAttribPointer( 2, 3, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 0 ) );
    glEnableVertexAttribArray( 3 );
    glVertexAttribPointer( 3, 1, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 12 ) );
    glEnableVertexAttribArray( 4 );
    glVertexAttribPointer( 4, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 16 ) );
    glEnableVertexAttribArray( 5 );
    glVertexAttribPointer( 5, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 32 ) );   // uvMin + uvMax
    glEnableVertexAttribArray( 6 );
    glVertexAttribPointer( 6, 2, GL_FLOAT, GL_FALSE, stride, reinterpret_cast<void*>( 48 ) );

    glVertexAttribDivisor( 2, 1 );
    glVertexAttribDivisor( 3, 1 );
    glVertexAttribDivisor( 4, 1 );
    glVertexAttribDivisor( 5, 1 );
    glVertexAttribDivisor( 6, 1 );

    glBindVertexArray( 0 );
    glBindBuffer( GL_ARRAY_BUFFER, 0 );
}
void crBatchedBillboards::Cleanup()
{
    glDeleteBuffers( 1, &_instanceVbo );
    glDeleteBuffers( 1, &_quadVbo );
    glDeleteVertexArrays( 1, &_vao );
    _instanceVbo = 0;
    _quadVbo     = 0;
    _vao         = 0;
}

void crBatchedBillboards::Flush( GLuint program, GLuint texture, const BillboardInstance* instances, int32_t count )
{
    if( count <= 0 )
        return;

    glUseProgram( program );
    glUniform1i( glGetUniformLocation( program, "u_tex" ), 0 );
    glActiveTexture( GL_TEXTURE0 );
    glBindTexture( GL_TEXTURE_2D, texture );

    glEnable( GL_DEPTH_TEST );
    glDepthMask( GL_FALSE );        // read depth (occluded by opaque) but never write
    glEnable( GL_BLEND );
    glBlendFunc( GL_ONE, GL_ONE );  // additive — order-independent, HDR accumulates into bloom
    glDisable( GL_CULL_FACE );

    glBindVertexArray( _vao );

    glBindBuffer( GL_ARRAY_BUFFER, _instanceVbo );
    glBufferData( GL_ARRAY_BUFFER, static_cast<GLsizeiptr>( count ) * static_cast<GLsizeiptr>( sizeof( BillboardInstance ) ), instances, GL_STREAM_DRAW );

    glDrawArraysInstanced( GL_TRIANGLE_STRIP, 0, 4, static_cast<GLsizei>( count ) );

    glBindVertexArray( 0 );
    glBindTexture( GL_TEXTURE_2D, 0 );

    glDepthMask( GL_TRUE );   // restore — next frame's depth clears need writes on

    ++crGraphicsStats::drawCalls;
    crGraphicsStats::vertices += ( count * 4 );
}
