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

#pragma once
#ifndef __BUFFER_OBJECTS_INCLUDED
#define __BUFFER_OBJECTS_INCLUDED

/**
 *	@file gl3/BufferObjects.h
 *	@author -tHE SWINe-
 *	@date 2009
 *	@brief wrapper for GL_ARB_vertex_buffer_object / GL_ARB_pixel_buffer_object extensions
 *
 *	@todo Improve object design.
 *	@todo Implement GL_ARB_map_buffer_range, GL_ARB_copy_buffer, maybe GL_ARB_texture_buffer_object.
 *
 *	@date 2012-06-19
 *
 *	Moved multiple inclusion guard before file documentation comment.
 *
 */

#include "OpenGL30.h"

/**
 *	@brief class, wrapping GL_ARB_vertex_buffer_object / GL_ARB_pixel_buffer_object extensions
 *
 *	Object of this class holds a single vertex buffer object, it is created in OpenGL when
 *		one of Bind(), BindAs() or Alloc() is called first time. OpenGL object is destroyed
 *		once destructor is called.
 */
class CGLBufferObject {
protected:
	GLuint m_n_buffer_id;
	unsigned int m_n_size;
	int m_n_usage, m_n_access;
	GLenum m_n_default_target, m_n_bind_target;
	bool m_b_mapped;

public:
	/**
	 *	@brief default constructor
	 *
	 *	Initializes CGLBufferObject instance, otherwise has no effect. No OpenGL objects
	 *		are created.
	 *
	 *	@param[in] n_target is default buffer object bind target, valid values are GL_ARRAY_BUFFER
	 *		(vertex data), GL_ELEMENT_ARRAY_BUFFER (vertex indices) or GL_PIXEL_PACK_BUFFER,
	 *		GL_PIXEL_UNPACK_BUFFER (pixel data)
	 */
	CGLBufferObject(GLenum n_target = GL_ARRAY_BUFFER);

	/**
	 *	@brief destructor
	 *
	 *	Deletes OpenGL buffer object, in case it was created.
	 */
	~CGLBufferObject();

	/**
	 *	@brief binds the buffer
	 *
	 *	Binds the buffer with target set to target, passed to constructor
	 *		(when called first time, it creates OpenGL object as well).
	 *
	 *	@return Returns true on success, false on failure.
	 */
	bool Bind();

	/**
	 *	@brief binds the buffer
	 *
	 *	Binds the buffer with explicit target n_target (when called first time,
	 *		it creates OpenGL object as well).
	 *
	 *	@param[in] n_target is buffer object target, valid values are GL_ARRAY_BUFFER
	 *		(vertex data), GL_ELEMENT_ARRAY_BUFFER (vertex indices) or GL_PIXEL_PACK_BUFFER,
	 *		GL_PIXEL_UNPACK_BUFFER (pixel data)
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note With GL_PIXEL_UNPACK_BUFFER, it is possible to copy data using glReadPixels()
	 *		or glGetTexImage() with <data> parameter, returned by call to p_OffsetPointer(0).
	 */
	bool BindAs(GLenum n_target = GL_PIXEL_PACK_BUFFER);

	/**
	 *	@brief allocates the buffer
	 *
	 *	Allocates buffer data of size n_size bytes, can be initialized to p_data in case
	 *		non-NULL pointer is supplied. In case buffer was allocated already, it's data
	 *		are freed as soon as there are no OpenGL commands, sourcing data from it
	 *		(it shouldn't block program execution, depends on graphics card vendor and
	 *		driver version) and new buffer is allocated.
	 *
	 *	@param[in] n_size is buffer size in bytes
	 *	@param[in] p_data is pointer to n_size bytes of buffer data, or null pointer
	 *	@param[in] n_usage is optimalization hint only, it is one of:
	 *		GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY,
	 *		GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,
	 *		GL_DYNAMIC_DRAW, GL_DYNAMIC_READ or GL_DYNAMIC_COPY
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This may generate GL_OUT_OF_MEMORY error (call glGetError()).
	 */
	bool Alloc(unsigned int n_size, const void *p_data = 0, GLenum n_usage = GL_STATIC_DRAW);

	/**
	 *	@brief gets server-side pointer to buffer
	 *
	 *	Returns pointer to buffer data for use with glVertexAttribPointer(), glDrawElements(), etc.
	 *		This pointer is only associated with this buffer, in case this buffer is currently bound.
	 *		Do not use this pointer to access data, it doesn't point to client address space.
	 *
	 *	@param[in] n_offset is offset to referenced place in buffer, in bytes
	 *
	 *	@return Returns pointer to buffer data.
	 *
	 *	@note Do not compare returned pointer to null, it is perfectly valid server-side pointer value.
	 */
	inline void *p_OffsetPointer(unsigned int n_offset) { return (void*)((char*)NULL + n_offset); }

	/**
	 *	@brief (re)specifies part (or all) of buffer data
	 *
	 *	@param[in] n_offset is offset in buffer to start of data being specified
	 *	@param[in] n_size is data size in bytes
	 *	@param[in] p_data is pointer to n_size bytes of buffer data
	 *
	 *	@note n_offset must be greater or equal to 0, n_offset + n_size must be less or equal
	 *		to buffer size, otherwise OpenGL error is generated (call glGetError()).
	 */
	void BufferSubData(unsigned int n_offset, unsigned int n_size, const void *p_data);

	/**
	 *	@brief reads part (or all) of buffer data
	 *
	 *	Copies n_size bytes of buffer data, starting at byte n_offset to p_data.
	 *
	 *	@param[in] n_offset is offset in buffer to start of data being specified
	 *	@param[in] n_size is data size in bytes (n_offset + n_size must be less or equal
	 *		to buffer size, otherwise OpenGL error is generated)
	 *	@param[in] p_data is pointer, n_size bytes of buffer data will be copied to
	 */
	void GetBufferSubData(unsigned int n_offset,
		unsigned int n_size, void *p_data);

	/**
	 *	@brief maps buffer contents to client space
	 *
	 *	Maps buffer into client address space (one buffer only can be mapped at a time).
	 *		Mapped buffer should be un-mapped by calling UnMap(). In case buffer was mapped,
	 *		pointer returned by this function can be retrieved again, by calling
	 *		p_BufferPointer().
	 *
	 *	@param[in] n_access is buffer access, it can be one of GL_READ_ONLY, GL_WRITE_ONLY
	 *		or GL_READ_WRITE
	 *
	 *	@return Returns client address space pointer in case of success or NULL on failure.
	 */
	void *p_Map(GLenum n_access);

	/**
	 *	@brief gets client-side pointer to mapped buffer
	 *
	 *	@return In case buffer object has been successfuly mapped, return client-space pointer,
	 *		otherwise return NULL.
	 */
	void *p_BufferPointer();

	/**
	 *	@brief unmaps buffer
	 *
	 *	Ends mapping of buffer data to client address space, previously estabilished
	 *		calling p_Map().
	 *
	 *	@return Returns true on success or in case buffer was not mapped,
	 *		returns false on failure (buffer contents were lost, may be caused by
	 *		screen-mode switch while buffer was bound).
	 */
	bool UnMap();

	/**
	 *	@brief gets size of buffer
	 *
	 *	@return Returns size of buffer, in bytes.
	 */
	inline unsigned int n_Size() { return m_n_size; }

	/**
	 *	@brief gets buffer ussage
	 *
	 *	@return Returns buffer ussage (one of:
	 *		GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY,
	 *		GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,
	 *		GL_DYNAMIC_DRAW, GL_DYNAMIC_READ or GL_DYNAMIC_COPY).
	 */
	inline GLenum n_Usage() { return m_n_usage; }

	/**
	 *	@brief determines wheter buffer is mapped
	 *
	 *	@return Returns true in case buffer contents were mapped into client-space memory.
	 *
	 *	@note It's possible to obtain pointer to mapped buffer data by call to p_BufferPointer().
	 */
	inline bool b_Mapped() { return m_b_mapped; }

	/**
	 *	@brief determines mapped buffer access flags
	 *
	 *	@return Returns access flag to buffer in case it has been mapped (one of GL_READ_ONLY,
	 *		GL_WRITE_ONLY, GL_READ_WRITE), or in case buffer is not mapped, returns 0.
	 */
	inline GLenum n_AccessFlag() { return (m_b_mapped)? m_n_access : 0; }

	/**
	 *	@brief gets default buffer target
	 *
	 *	@return Returns default target of buffer object (ie. target which is given
	 *		in constructor and used in Bind()).
	 */
	inline GLenum n_Default_Target() { return m_n_default_target; }

	/**
	 *	@brief gets buffer target
	 *
	 *	Gets target buffer is currently bound to. This function was added to supress
	 *		confusion about what target should be un-bound after finishing work with
	 *		some buffer object.
	 *
	 *	@return Returns last bind target of buffer object (ie. target which is given
	 *		in constructor and used in Bind()).
	 */
	inline GLenum n_Bind_Target() { return m_n_bind_target; }
};

#endif // !__BUFFER_OBJECTS_INCLUDED
