//============ Copyright (c) Valve Corporation, All rights reserved. ============
//
// cglmbuffer.cpp
//
//===============================================================================

#include "glmgr.h"
#include "glmdisplay.h"
#include "cglmbuffer.h"

#ifdef OSX
// Debugger - 10.8
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif

//    void BindBufferARB(enum target, uint buffer);
//    void DeleteBuffersARB(sizei n, const uint *buffers);
//    void GenBuffersARB(sizei n, uint *buffers);
//    boolean IsBufferARB(uint buffer);
//
//    void BufferDataARB(enum target, sizeiptrARB size, const void *data,
//                       enum usage);
//    void BufferSubDataARB(enum target, intptrARB offset, sizeiptrARB size,
//                          const void *data);
//    void GetBufferSubDataARB(enum target, intptrARB offset,
//                             sizeiptrARB size, void *data);
//
//    void *MapBufferARB(enum target, enum access);
//    boolean UnmapBufferARB(enum target);
//
//    void GetBufferParameterivARB(enum target, enum pname, int *params);
//    void GetBufferPointervARB(enum target, enum pname, void **params);
//
//New Tokens
//
//    Accepted by the <target> parameters of BindBufferARB, BufferDataARB,
//    BufferSubDataARB, MapBufferARB, UnmapBufferARB,
//    GetBufferSubDataARB, GetBufferParameterivARB, and
//    GetBufferPointervARB:
//
//        ARRAY_BUFFER_ARB                             0x8892
//        ELEMENT_ARRAY_BUFFER_ARB                     0x8893
//
//    Accepted by the <pname> parameter of GetBooleanv, GetIntegerv,
//    GetFloatv, and GetDoublev:
//
//        ARRAY_BUFFER_BINDING_ARB                     0x8894
//        ELEMENT_ARRAY_BUFFER_BINDING_ARB             0x8895
//        VERTEX_ARRAY_BUFFER_BINDING_ARB              0x8896
//        NORMAL_ARRAY_BUFFER_BINDING_ARB              0x8897
//        COLOR_ARRAY_BUFFER_BINDING_ARB               0x8898
//        INDEX_ARRAY_BUFFER_BINDING_ARB               0x8899
//        TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB       0x889A
//        EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB           0x889B
//        SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB     0x889C
//        FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB      0x889D
//        WEIGHT_ARRAY_BUFFER_BINDING_ARB              0x889E
//
//    Accepted by the <pname> parameter of GetVertexAttribivARB:
//
//        VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB       0x889F
//
//    Accepted by the <usage> parameter of BufferDataARB:
//
//        STREAM_DRAW_ARB                              0x88E0
//        STREAM_READ_ARB                              0x88E1
//        STREAM_COPY_ARB                              0x88E2
//        STATIC_DRAW_ARB                              0x88E4
//        STATIC_READ_ARB                              0x88E5
//        STATIC_COPY_ARB                              0x88E6
//        DYNAMIC_DRAW_ARB                             0x88E8
//        DYNAMIC_READ_ARB                             0x88E9
//        DYNAMIC_COPY_ARB                             0x88EA
//
//    Accepted by the <access> parameter of MapBufferARB:
//
//        READ_ONLY_ARB                                0x88B8
//        WRITE_ONLY_ARB                               0x88B9
//        READ_WRITE_ARB                               0x88BA
//
//    Accepted by the <pname> parameter of GetBufferParameterivARB:
//
//        BUFFER_SIZE_ARB                              0x8764
//        BUFFER_USAGE_ARB                             0x8765
//        BUFFER_ACCESS_ARB                            0x88BB
//        BUFFER_MAPPED_ARB                            0x88BC
//
//    Accepted by the <pname> parameter of GetBufferPointervARB:
//
//        BUFFER_MAP_POINTER_ARB                       0x88BD

// http://www.opengl.org/registry/specs/ARB/pixel_buffer_object.txt
//    Accepted by the <target> parameters of BindBuffer, BufferData,
//    BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData,
//    GetBufferParameteriv, and GetBufferPointerv:
//        PIXEL_PACK_BUFFER_ARB                        0x88EB
//        PIXEL_UNPACK_BUFFER_ARB                      0x88EC


	// gl_bufmode: zero means we mark all vertex/index buffers static

	// non zero means buffers are initially marked static..
	// ->but can shift to dynamic upon first 'discard' (orphaning)

//ConVar	gl_bufmode( "gl_bufmode", "1" );
int gl_bufmode = 1;

CGLMBuffer::CGLMBuffer( GLMContext *ctx, EGLMBufferType type, uint size, uint options )
{
	m_ctx = ctx;
	m_type = type;
	switch(m_type)
	{
		case	kGLMVertexBuffer:		m_buffGLTarget = GL_ARRAY_BUFFER_ARB; break;
		case	kGLMIndexBuffer:		m_buffGLTarget = GL_ELEMENT_ARRAY_BUFFER_ARB; break;
		case	kGLMUniformBuffer:		m_buffGLTarget = GL_UNIFORM_BUFFER_EXT; break;
		case	kGLMPixelBuffer:		m_buffGLTarget = GL_PIXEL_UNPACK_BUFFER_ARB; break;
		
		default:	//Assert(!"Unknown buffer type" );
		break;
	}
	m_size = size;
	m_bound = false;
	m_mapped = false;
	m_lastMappedAddress = NULL;

	m_enableAsyncMap = false;
	m_enableExplicitFlush = false;
	m_dirtyMinOffset = m_dirtyMaxOffset = 0;								// adjust/grow on lock, clear on unlock

	m_ctx->CheckCurrent();
	m_revision = rand();

	// make a decision about pseudo mode
	// this looked like it didn't help much or was actually slower, so leave it available but only as opt-in.
	// a more clever implementation would be able to select pseudo buf storage for small batches only..
	m_pseudo = false;	// (m_type==kGLMIndexBuffer) && (CommandLine()->FindParm("-gl_enable_pseudobufs"));	
	if (m_pseudo)
	{
		m_name		= 0;		
		m_pseudoBuf	= (char*)malloc( size );
		
		m_ctx->BindBufferToCtx( m_type, NULL );		// exit with no buffer bound
	}
	else
	{
		glGenBuffersARB( 1, &m_name );
		GLMCheckError();

		m_ctx->BindBufferToCtx( m_type, this );	// causes glBindBufferARB

		// buffers start out static, but if they get orphaned and gl_bufmode is non zero,
		// then they will get flipped to dynamic.
		
		GLenum hint = GL_STATIC_DRAW_ARB;
		switch(m_type)
		{
			case	kGLMVertexBuffer:		hint = (options & GLMBufferOptionDynamic) ? GL_DYNAMIC_DRAW_ARB : GL_STATIC_DRAW_ARB; break;
			case	kGLMIndexBuffer:		hint = (options & GLMBufferOptionDynamic) ? GL_DYNAMIC_DRAW_ARB : GL_STATIC_DRAW_ARB; break;
			case	kGLMUniformBuffer:		hint = GL_DYNAMIC_DRAW_ARB; break;	// "fwiw" - shrug
			case	kGLMPixelBuffer:		hint = (options & GLMBufferOptionDynamic) ? GL_DYNAMIC_DRAW_ARB : GL_STATIC_DRAW_ARB; break;
			
			default:	//Assert(!"Unknown buffer type" );
			break;
		}

		glBufferDataARB( m_buffGLTarget, m_size, NULL, hint );	// may ultimately need more hints to set the usage correctly (esp for streaming)

		this->SetModes( false, true, true );

		m_ctx->BindBufferToCtx( m_type, NULL );	// unbind me
	}
}

CGLMBuffer::~CGLMBuffer( )
{
	m_ctx->CheckCurrent();
	
	if (m_pseudo)
	{
		free (m_pseudoBuf);
		m_pseudoBuf = NULL;
	}
	else
	{
		glDeleteBuffersARB( 1, &m_name );
		GLMCheckError();
	}
	
	m_ctx = NULL;
	m_name = 0;
	m_bound = 0;
	
	m_lastMappedAddress = NULL;
}

void	CGLMBuffer::SetModes			( bool asyncMap, bool explicitFlush, bool force )
{
	// assumes buffer is bound. called by constructor and by Lock.

	if (m_pseudo)
	{
		// ignore it...
	}
	else
	{
		if (force || (m_enableAsyncMap != asyncMap) )
		{
			// note the sense of the parameter, it's TRUE if you *want* serialization, so for async you turn it to false.
			glBufferParameteriAPPLE( this->m_buffGLTarget, GL_BUFFER_SERIALIZED_MODIFY_APPLE, asyncMap==false );
			m_enableAsyncMap = asyncMap;
		}

		if (force || (m_enableExplicitFlush != explicitFlush) )
		{
			// note the sense of the parameter, it's TRUE if you *want* auto-flush-on-unmap, so for explicit-flush, you turn it to false.
			glBufferParameteriAPPLE( this->m_buffGLTarget, GL_BUFFER_FLUSHING_UNMAP_APPLE, explicitFlush==false );
			m_enableExplicitFlush = explicitFlush;
		}
	}
}

void	CGLMBuffer::FlushRange			( uint offset, uint size )
{
	if (m_pseudo)
	{
		// nothing to do
	}
	else
	{
		// assumes buffer is bound.
		glFlushMappedBufferRangeAPPLE(this->m_buffGLTarget, (GLintptr)offset, (GLsizeiptr)size);
	}
}
	
//ConVar	gl_buffer_alignment_quantum		( "gl_buffer_alignment_quantum", "32" );		// the alignment we use pre-SLGU
//ConVar	gl_buffer_alignment_quantum_slgu( "gl_buffer_alignment_quantum_slgu", "2" );	// alignment used post-SLGU

int	gl_buffer_alignment_quantum			= 32;
int gl_buffer_alignment_quantum_slgu	= 2;

void	CGLMBuffer::Lock( GLMBuffLockParams *params, char **addressOut )
{
	char	*resultPtr = NULL;
	
	//Assert( !m_mapped);
	
	m_ctx->CheckCurrent();
	GLMCheckError();

	if (params->m_offset >= m_size)
		Debugger();
	
	if (params->m_offset + params->m_size > m_size)
		Debugger();

	// bind (yes, even for pseudo - this binds name 0)
	m_ctx->BindBufferToCtx( this->m_type, this );
	
	if (m_pseudo)
	{
		// discard is a no-op

		// async map modes are a no-op

		// latch last mapped address (silly..)
		m_lastMappedAddress = (float*)m_pseudoBuf;

		// calc lock address
		resultPtr = m_pseudoBuf + params->m_offset;
		
		// dirty range is a no-op
	}
	else
	{
		// perform discard if requested
		if (params->m_discard)
		{
			// observe gl_bufmode on any orphan event.
			// if orphaned and bufmode is nonzero, flip it to dynamic.
			GLenum hint = gl_bufmode /*.GetInt()*/ ? GL_DYNAMIC_DRAW_ARB : GL_STATIC_DRAW_ARB;
			glBufferDataARB( m_buffGLTarget, m_size, NULL, hint );
			
			m_lastMappedAddress = NULL;
			
			m_revision++;	// revision grows on orphan event
		}

		// adjust async map option appropriately, leave explicit flush unchanged
		this->SetModes( params->m_nonblocking, m_enableExplicitFlush );

		// map
		char *mapPtr = (char*)glMapBufferARB( this->m_buffGLTarget, GL_READ_WRITE_ARB );
		
		if (!mapPtr)
		{
			Debugger();
		}
		
		if (m_lastMappedAddress)
		{
			// just check if it moved
			//Assert (m_lastMappedAddress == (float*)mapPtr);
		}

		m_lastMappedAddress = (float*)mapPtr;

		// calculate offset location
		resultPtr = mapPtr + params->m_offset;

		// adjust dirty range
		if (m_dirtyMinOffset != m_dirtyMaxOffset)
		{
			// grow range
			m_dirtyMinOffset = std::min( m_dirtyMinOffset, params->m_offset );
			m_dirtyMaxOffset = std::min( m_dirtyMaxOffset, params->m_offset+params->m_size );
		}
		else
		{
			// set range
			m_dirtyMinOffset = params->m_offset;
			m_dirtyMaxOffset = params->m_offset+params->m_size;
		}

		// pad and clamp dirty range to choice of boundary
		uint quantum = (m_ctx->Caps().m_hasPerfPackage1) ? gl_buffer_alignment_quantum_slgu /*.GetInt()*/ : gl_buffer_alignment_quantum /*.GetInt()*/ ;
		uint quantum_mask = quantum - 1;

		m_dirtyMinOffset = m_dirtyMinOffset & (~quantum_mask);
		m_dirtyMaxOffset = (m_dirtyMaxOffset + quantum_mask) & (~quantum_mask);
		m_dirtyMaxOffset = std::min( m_dirtyMaxOffset, m_size );
	}

	m_mapped = true;
	
	*addressOut = resultPtr;
}

void	CGLMBuffer::Unlock( void )
{
	m_ctx->CheckCurrent();

	//Assert (m_mapped);

	if (m_pseudo)
	{
		// nothing to do actually
	}
	else
	{
		m_ctx->BindBufferToCtx( this->m_type, this );

		// time to do explicit flush
		if (m_enableExplicitFlush)
		{
			this->FlushRange( m_dirtyMinOffset, m_dirtyMaxOffset - m_dirtyMinOffset );
		}
		
		// clear dirty range no matter what
		m_dirtyMinOffset = m_dirtyMaxOffset = 0;								// adjust/grow on lock, clear on unlock

		glUnmapBuffer( this->m_buffGLTarget );
		
	}

	m_mapped = false;
}
