#include "crGraphicsUniforms.h"

#include "crMath.h"

void crGraphicsUniforms::Init()
{
    glGenBuffers( 1, &_ubo );
    glBindBuffer( GL_UNIFORM_BUFFER, _ubo );
    glBufferData( GL_UNIFORM_BUFFER, sizeof( Data ), nullptr, GL_DYNAMIC_DRAW );
    glBindBuffer( GL_UNIFORM_BUFFER, 0 );

    glBindBufferBase( GL_UNIFORM_BUFFER, BINDING_POINT, _ubo );   // persists — rebinding per frame is unnecessary
}
void crGraphicsUniforms::Cleanup()
{
    glDeleteBuffers( 1, &_ubo );
    _ubo = 0;
}

void crGraphicsUniforms::Update( float4x4 viewProjection, float3 cameraPos, const float4x4* lightVPs, float3 fogColor, float fogStart, float fogEnd, bool fogEnabled, float simTime, float realTime )
{
    Data data;
    data.viewProjection = viewProjection;
    for( int32_t i = 0; i < crShadowMap::CASCADE_COUNT; ++i )
        data.lightVP[ i ] = lightVPs[ i ];
    data.cameraPos      = cameraPos;
    data._pad0          = 0.0f;
    data.lightDir       = crMath::Normalize3( lightDir );
    data._pad1          = 0.0f;
    data.lightColor     = crMath::SrgbToLinear( lightColor );
    data.ambientSky     = crMath::SrgbToLinear( ambientSky );
    data.ambientGround  = crMath::SrgbToLinear( ambientGround );
    data.specShininess  = specShininess;
    data.specStrength   = specStrength;
    data._pad2          = 0.0f;
    data._pad3          = 0.0f;
    data.shadowBias         = shadowBias;
    data.shadowStrength     = shadowStrength;
    data.shadowNormalOffset = shadowNormalOffset;
    data.shadowDarkness     = shadowDarkness;
    // per-cascade shadow constants, precomputed here so the primitive shader doesn't recover them per
    // fragment. the light view part of the combined matrix is rigid (unit rows), so row lengths
    // are the ortho scales regardless of light direction: row 0 = 1 / volumeRadius, row 2 = 2 / depthRange
    for( int32_t i = 0; i < crShadowMap::CASCADE_COUNT; ++i )
    {
        const float* m         = lightVPs[ i ].m;   // column-major: row r = ( m[ r ], m[ r + 4 ], m[ r + 8 ] )
        const float  invRadius = crMath::Sqrt( ( m[ 0 ] * m[ 0 ] ) + ( m[ 4 ] * m[ 4 ] ) + ( m[ 8 ] * m[ 8 ] ) );
        const float  invRange  = crMath::Sqrt( ( m[ 2 ] * m[ 2 ] ) + ( m[ 6 ] * m[ 6 ] ) + ( m[ 10 ] * m[ 10 ] ) ) * 0.5f;
        const float  texelUv   = 1.0f / static_cast<float>( crShadowMap::SHADOW_MAP_SIZE );

        data.shadowCascadeParams[ i ] = color4( 2.0f / ( invRadius * static_cast<float>( crShadowMap::SHADOW_MAP_SIZE ) ), shadowBias * invRange, texelUv, texelUv );
    }
    data.fogColor           = crMath::SrgbToLinear( color4( fogColor.x, fogColor.y, fogColor.z, fogEnabled ? 1.0f : 0.0f ) );
    data.fogStart           = fogStart;
    data.fogEnd             = fogEnd;
    data._pad4              = 0.0f;
    data._pad5              = 0.0f;
    data.simTime            = simTime;
    data.realTime           = realTime;
    data._pad6              = 0.0f;
    data._pad7              = 0.0f;

    glBindBuffer( GL_UNIFORM_BUFFER, _ubo );
    glBufferSubData( GL_UNIFORM_BUFFER, 0, sizeof( Data ), &data );
    glBindBuffer( GL_UNIFORM_BUFFER, 0 );
}
void crGraphicsUniforms::BindTo( GLuint program )
{
    const GLuint index = glGetUniformBlockIndex( program, "GraphicsUniforms" );
    if( index != GL_INVALID_INDEX )
        glUniformBlockBinding( program, index, BINDING_POINT );
}
