/*
								+-----------------------------------+
								|                                   |
								|     ***  VBO / PBO class  ***     |
								|                                   |
								|   Copyright  © -tHE SWINe- 2009   |
								|                                   |
								|         BufferObjects.cpp         |
								|                                   |
								+-----------------------------------+
*/

/**
 *	@file gl4/BufferObjects.cpp
 *	@author -tHE SWINe-
 *	@date 2009
 *	@brief wrapper for GL_ARB_vertex_buffer_object / GL_ARB_pixel_buffer_object extensions
 */

#include "../NewFix.h"
#include "../CallStack.h"
#include "BufferObjects.h"

/*
 *								=== CGLBufferObject ===
 */

CGLBufferObject::CGLBufferObject()
	:
#ifdef __GL4BUFFER_OBJECT_HOLD_SIZE
	m_n_size(0),
#endif // __GL4BUFFER_OBJECT_HOLD_SIZE
#ifdef __GL4BUFFER_OBJECT_HOLD_USAGE
	m_n_usage(0/*GL_STATIC_DRAW*/),
#endif // __GL4BUFFER_OBJECT_HOLD_USAGE
#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	//m_n_access(0), // no need to set this
	m_b_mapped(false),
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	m_n_buffer_id(0)
{
	glGenBuffers(1, &m_n_buffer_id);
}

CGLBufferObject::~CGLBufferObject()
{
	if(m_n_buffer_id) {
#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
		_ASSERTE(!m_b_mapped);
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
		glDeleteBuffers(1, &m_n_buffer_id);
	}
}

void CGLBufferObject::Bind(GLenum n_target) const
{
	_ASSERTE(m_n_buffer_id);
	glBindBuffer(n_target, m_n_buffer_id);
}

bool CGLBufferObject::BufferData(GLenum n_target, GLuint n_size,
	const void *p_data, GLenum n_usage)
{
	_ASSERTE(m_n_buffer_id);
	_ASSERTE(b_IsBound(n_target)); // must be bound
	glBufferData(n_target, n_size, p_data, n_usage);

#ifdef __GL4BUFFER_OBJECT_HOLD_SIZE
	m_n_size = n_size;
#endif // __GL4BUFFER_OBJECT_HOLD_SIZE

#ifdef __GL4BUFFER_OBJECT_HOLD_USAGE
	m_n_usage = n_usage;
#endif // __GL4BUFFER_OBJECT_HOLD_USAGE

	return true;
}

void CGLBufferObject::BufferSubData(GLenum n_target, GLuint n_offset,
	GLuint n_size, const void *p_data)
{
#ifdef __GL4BUFFER_OBJECT_HOLD_SIZE
	_ASSERTE(n_offset + n_size <= m_n_size);
#endif // __GL4BUFFER_OBJECT_HOLD_SIZE
	_ASSERTE(p_data);
	_ASSERTE(m_n_buffer_id);
	_ASSERTE(b_IsBound(n_target)); // must be bound

	glBufferSubData(n_target, n_offset, n_size, p_data);
}

void CGLBufferObject::GetBufferSubData(GLenum n_target, GLuint n_offset,
	GLuint n_size, void *p_data) const
{
#ifdef __GL4BUFFER_OBJECT_HOLD_SIZE
	_ASSERTE(n_offset + n_size <= m_n_size);
#endif // __GL4BUFFER_OBJECT_HOLD_SIZE
	_ASSERTE(p_data);
	_ASSERTE(m_n_buffer_id);
	_ASSERTE(b_IsBound(n_target)); // must be bound

	glGetBufferSubData(n_target, n_offset, n_size, p_data);
}

void *CGLBufferObject::p_Map(GLenum n_target, GLenum n_access)
{
#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	//_ASSERTE(!m_b_mapped); // can be remapped
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	_ASSERTE(m_n_buffer_id);
	_ASSERTE(b_IsBound(n_target)); // must be bound

	void *p_result = glMapBuffer(n_target, n_access);

#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	if(p_result) {
		m_n_access = n_access;
		m_b_mapped = true;
	}
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO

	return p_result;
}

void *CGLBufferObject::p_MapRange(GLenum n_target,
	GLintptr n_offset, GLsizeiptr n_length, GLbitfield n_access)
{
#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	//_ASSERTE(!m_b_mapped); // can be remapped
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	_ASSERTE(m_n_buffer_id);
	_ASSERTE(b_IsBound(n_target)); // must be bound

	void *p_result = glMapBufferRange(n_target, n_offset, n_length, n_access);

#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	if(p_result) {
		m_n_access = n_access;
		m_b_mapped = true;
	}
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO

	return p_result;
}

void CGLBufferObject::FlushMappedRange(GLenum n_target, GLintptr n_offset, GLsizeiptr n_length)
{
#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	_ASSERTE(m_b_mapped);
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	_ASSERTE(m_n_buffer_id);
	_ASSERTE(b_IsBound(n_target)); // must be bound

	glFlushMappedBufferRange(n_target, n_offset, n_length);
}

void *CGLBufferObject::p_Get_BufferPointer(GLenum n_target) const
{
#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	if(!m_b_mapped)
		return 0;
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO

	_ASSERTE(m_n_buffer_id);
	_ASSERTE(b_IsBound(n_target)); // must be bound

	void *p_ptr;
	glGetBufferPointerv(n_target, GL_BUFFER_MAP_POINTER, &p_ptr);
	return p_ptr;
}

bool CGLBufferObject::Unmap(GLenum n_target)
{
#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	if(!m_b_mapped)
		return false;
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO

	_ASSERTE(m_n_buffer_id);
	_ASSERTE(b_IsBound(n_target)); // must be bound

#ifdef __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO
	m_b_mapped = false;
	//m_n_access = 0; // no need to set this ...
#endif // __GL4BUFFER_OBJECT_HOLD_MAPPING_INFO

	return glUnmapBuffer(n_target) == GL_TRUE;
}

inline bool CGLBufferObject::b_IsBound(GLenum n_target) const
{
	GLenum n_binding_target;
	switch(n_target) {
	case GL_ARRAY_BUFFER: n_binding_target = GL_ARRAY_BUFFER_BINDING; break;
	//case GL_COPY_READ_BUFFER: n_binding_target = GL_COPY_READ_BUFFER_BINDING; break; // no binding for this target
	//case GL_COPY_WRITE_BUFFER: n_binding_target = GL_COPY_WRITE_BUFFER_BINDING; break; // no binding for this target
	case GL_DRAW_INDIRECT_BUFFER: n_binding_target = GL_DRAW_INDIRECT_BUFFER_BINDING; break;
	case GL_ELEMENT_ARRAY_BUFFER: n_binding_target = GL_ELEMENT_ARRAY_BUFFER_BINDING; break;
	case GL_PIXEL_PACK_BUFFER: n_binding_target = GL_PIXEL_PACK_BUFFER_BINDING; break;
	case GL_PIXEL_UNPACK_BUFFER: n_binding_target = GL_PIXEL_UNPACK_BUFFER_BINDING; break;
	//case GL_TEXTURE_BUFFER: n_binding_target = GL_TEXTURE_BUFFER_BINDING; break; // no binding for this target
	case GL_TRANSFORM_FEEDBACK_BUFFER: n_binding_target = GL_TRANSFORM_FEEDBACK_BUFFER_BINDING; break;
	case GL_UNIFORM_BUFFER: n_binding_target = GL_UNIFORM_BUFFER_BINDING; break;
	default:
		return true; // unknown target; silently assume it's bound
	}
	// translate target to binding target

	int n_bound_buffer;
	glGetIntegerv(n_binding_target, &n_bound_buffer);
	// get bound buffer name

	return GLuint(n_bound_buffer) == m_n_buffer_id;
	// see if this buffer is bound
}

/*
 *								=== ~CGLBufferObject ===
 */

/*
 *								=== CGLVertexArrayObject ===
 */

CGLVertexArrayObject::CGLVertexArrayObject()
{
	glGenVertexArrays(1, &m_n_name);
}

CGLVertexArrayObject::~CGLVertexArrayObject()
{
	if(m_n_name)
		glDeleteVertexArrays(1, &m_n_name);
}

bool CGLVertexArrayObject::b_Status() const
{
	return m_n_name != 0;
}

/*
 *								=== ~CGLVertexArrayObject ===
 */

/*
 *								=== CGLArraySetup ===
 */

CGLArraySetup::CGLArraySetup(const void *p_vertex_list, size_t n_vertices_size_bytes,
	int n_vertex_stride_bytes, GLenum n_position_data_type, int n_position_component_num,
	int n_position_offset_bytes, GLenum n_texcoord_data_type, int n_texcoord_component_num,
	int n_texcoord_offset_bytes, GLenum n_draw_mode)
	:m_n_draw_mode(n_draw_mode)
{
	if(!vertex_buffer.b_Status() ||
	   !vertex_array_object.b_Status())
		return;

	if(!n_vertex_stride_bytes) {
		n_vertex_stride_bytes = n_GL_TypeSize(n_position_data_type) *
			n_position_component_num + ((n_texcoord_component_num)?
			n_GL_TypeSize(n_texcoord_data_type) * n_texcoord_component_num : 0); // t_odo - fix this
	}
	// calculate stride

	m_n_vertex_num = n_vertices_size_bytes / n_vertex_stride_bytes;
	// calculate number of vertices

	vertex_array_object.Bind();
	// create and bind terrain VAO to remember the buffer configuration below

	vertex_buffer.Bind();
	_ASSERTE(n_vertices_size_bytes <= UINT_MAX);
	vertex_buffer.BufferData(GLuint(n_vertices_size_bytes), p_vertex_list);
	// bind and fill buffer with vertices

	if(n_texcoord_component_num) {
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, n_texcoord_component_num,
			n_texcoord_data_type, false, n_vertex_stride_bytes,
			vertex_buffer.p_OffsetPointer(n_texcoord_offset_bytes));
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1, n_position_component_num,
			n_position_data_type, false, n_vertex_stride_bytes,
			vertex_buffer.p_OffsetPointer(n_position_offset_bytes));
	} else {
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, n_position_component_num,
			n_position_data_type, false, n_vertex_stride_bytes,
			vertex_buffer.p_OffsetPointer(n_position_offset_bytes));
	}
	// set up vertex attribute arrays

	CGLVertexArrayObject::Release();
	// unbind VAO
}

bool CGLArraySetup::b_Status() const
{
	return vertex_buffer.b_Status() && vertex_array_object.b_Status();
}

/*
 *								=== ~CGLArraySetup ===
 */

/*
 *								=== CGLElementArraySetup ===
 */

CGLElementArraySetup::CGLElementArraySetup(const void *p_vertex_list, size_t n_vertices_size_bytes,
	int n_vertex_stride_bytes, GLenum n_position_data_type, int n_position_component_num,
	int n_position_offset_bytes, GLenum n_texcoord_data_type, int n_texcoord_component_num,
	int n_texcoord_offset_bytes, const void *p_index_list, size_t n_index_num,
	GLenum n_index_data_type, GLenum n_draw_mode)
	:m_n_draw_mode(n_draw_mode), m_n_index_data_type(n_index_data_type),
	m_n_index_num(n_index_num)
{
	if(!vertex_buffer.b_Status() ||
	   !index_buffer.b_Status() ||
	   !vertex_array_object.b_Status())
		return;

	if(!n_vertex_stride_bytes) {
		n_vertex_stride_bytes = n_GL_TypeSize(n_position_data_type) *
			n_position_component_num + ((n_texcoord_component_num)?
			n_GL_TypeSize(n_texcoord_data_type) * n_texcoord_component_num : 0); // t_odo - fix this
	}

	vertex_array_object.Bind();
	// create and bind terrain VAO to remember the buffer configuration below

	index_buffer.Bind();
	index_buffer.BufferData(GLuint(n_index_num * n_GL_TypeSize(n_index_data_type)), p_index_list);
	// bind and fill buffer with indices

	vertex_buffer.Bind();
	_ASSERTE(n_vertices_size_bytes <= UINT_MAX);
	vertex_buffer.BufferData(GLuint(n_vertices_size_bytes), p_vertex_list);
	// bind and fill buffer with vertices

	if(n_texcoord_component_num) {
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, n_texcoord_component_num,
			n_texcoord_data_type, false, n_vertex_stride_bytes,
			vertex_buffer.p_OffsetPointer(n_texcoord_offset_bytes));
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1, n_position_component_num,
			n_position_data_type, false, n_vertex_stride_bytes,
			vertex_buffer.p_OffsetPointer(n_position_offset_bytes));
	} else {
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, n_position_component_num,
			n_position_data_type, false, n_vertex_stride_bytes,
			vertex_buffer.p_OffsetPointer(n_position_offset_bytes));
	}
	// set up vertex attribute arrays

	CGLVertexArrayObject::Release();
	// unbind VAO
}

bool CGLElementArraySetup::b_Status() const
{
	return vertex_buffer.b_Status() && index_buffer.b_Status() &&
		vertex_array_object.b_Status();
}

/*
 *								=== ~CGLElementArraySetup ===
 */
