/*
								+---------------------------------+
								|                                 |
								|      ***   OpenGL ES   ***      |
								|                                 |
								|  Copyright  © -tHE SWINe- 2009  |
								|                                 |
								|         FrameBuffer.cpp         |
								|                                 |
								+---------------------------------+
*/

/**
 *	@file gles2/FrameBuffer.cpp
 *	@author -tHE SWINe-
 *	@date 2009
 *	@brief OpenGL ES frame-buffer object
 */

#include "../NewFix.h"
#include "../CallStack.h"
#include "FrameBuffer.h"
#include "../MinMax.h"

/*
 *								=== CGLFrameBufferObject ===
 */

CGLFrameBufferObject::CGLFrameBufferObject(int n_width, int n_height,
	int n_color_buffer_num, const bool *p_color_texture_target,
	const GLenum *p_color_internal_format, const int *p_color_multisample_num,
	bool b_depth_buffer, bool b_depth_texture_target,
	GLenum n_depth_internal_format, int n_depth_multisample_num,
	bool b_depth_stencil_packed, bool b_stencil_buffer, bool b_stencil_texture_target,
	GLenum n_stencil_internal_format, int n_stencil_multisample_num)
	:m_n_framebuffer(0), m_n_depth_rb(0), m_n_stencil_rb(0)
{
	memset(m_p_color_rb, 0, max_DrawBuffer_Num * sizeof(GLuint));
	// initialize buffers

	TFrameBufferConfig t_config(n_width, n_height, n_color_buffer_num, p_color_texture_target,
		p_color_internal_format, p_color_multisample_num, b_depth_buffer, b_depth_texture_target,
		n_depth_internal_format, n_depth_multisample_num, b_depth_stencil_packed, b_stencil_buffer,
		b_stencil_texture_target, n_stencil_internal_format, n_stencil_multisample_num);

	if(!Create(n_width, n_height, t_config))
		Delete(); // deletes any objects that may succeeded in creation
	// create a new FBO

	glBindFramebuffer(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/, 0);
	// might remain active on error
}

CGLFrameBufferObject::CGLFrameBufferObject(int n_width, int n_height,
	int n_color_buffer_num, const bool *p_color_texture_target,
	const GLenum *p_color_internal_format, const int *p_color_multisample_num,
	bool b_depth_stencil_buffer, bool b_depth_stencil_texture_target,
	GLenum n_depth_stencil_internal_format, int n_depth_stencil_multisample_num)
	:m_n_framebuffer(0), m_n_depth_rb(0), m_n_stencil_rb(0)
{
	memset(m_p_color_rb, 0, max_DrawBuffer_Num * sizeof(GLuint));
	// initialize buffers

	TFrameBufferConfig t_config(n_width, n_height, n_color_buffer_num, p_color_texture_target,
		p_color_internal_format, p_color_multisample_num, b_depth_stencil_buffer,
		b_depth_stencil_texture_target, n_depth_stencil_internal_format,
		n_depth_stencil_multisample_num, true, false, false, 0, 0);

	if(!Create(n_width, n_height, t_config))
		Delete(); // deletes any objects that may succeeded in creation
	// create a new FBO

	glBindFramebuffer(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/, 0);
	// might remain active on error
}

CGLFrameBufferObject::CGLFrameBufferObject(int n_width, int n_height,
	int n_color_buffer_num, const bool *p_color_texture_target,
	const GLenum *p_color_internal_format, const int *p_color_multisample_num,
	bool b_depth_buffer, bool b_depth_texture_target,
	GLenum n_depth_internal_format, int n_depth_multisample_num,
	bool b_stencil_buffer, bool b_stencil_texture_target,
	GLenum n_stencil_internal_format, int n_stencil_multisample_num)
	:m_n_framebuffer(0), m_n_depth_rb(0), m_n_stencil_rb(0)
{
	memset(m_p_color_rb, 0, max_DrawBuffer_Num * sizeof(GLuint));
	// initialize buffers

	TFrameBufferConfig t_config(n_width, n_height, n_color_buffer_num, p_color_texture_target,
		p_color_internal_format, p_color_multisample_num, b_depth_buffer, b_depth_texture_target,
		n_depth_internal_format, n_depth_multisample_num, false, b_stencil_buffer,
		b_stencil_texture_target, n_stencil_internal_format, n_stencil_multisample_num);

	if(!Create(n_width, n_height, t_config))
		Delete(); // deletes any objects that may succeeded in creation
	// create a new FBO

	glBindFramebuffer(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/, 0);
	// might remain active on error
}

CGLFrameBufferObject::CGLFrameBufferObject(int n_width, int n_height,
	int n_color_buffer_num, bool b_color_texture_target,
	GLenum n_color_internal_format, int n_color_multisample_num,
	bool b_depth_stencil_buffer, bool b_depth_stencil_texture_target,
	GLenum n_depth_stencil_internal_format, int n_depth_stencil_multisample_num)
	:m_n_framebuffer(0), m_n_depth_rb(0), m_n_stencil_rb(0)
{
	memset(m_p_color_rb, 0, max_DrawBuffer_Num * sizeof(GLuint));
	// initialize buffers

	bool p_color_texture_target[max_DrawBuffer_Num];
	GLenum p_color_internal_format[max_DrawBuffer_Num];
	int p_color_multisample_num[max_DrawBuffer_Num];
	_ASSERTE(n_color_buffer_num <= max_DrawBuffer_Num);
	for(int i = 0; i < n_color_buffer_num; ++ i) {
		p_color_texture_target[i] = b_color_texture_target;
		p_color_internal_format[i] = n_color_internal_format;
		p_color_multisample_num[i] = n_color_multisample_num;
	}
	// expand scalar parameters to array

	TFrameBufferConfig t_config(n_width, n_height, n_color_buffer_num, p_color_texture_target,
		p_color_internal_format, p_color_multisample_num, b_depth_stencil_buffer,
		b_depth_stencil_texture_target, n_depth_stencil_internal_format,
		n_depth_stencil_multisample_num, true, false, false, 0, 0);

	if(!Create(n_width, n_height, t_config))
		Delete(); // deletes any objects that may succeeded in creation
	// create a new FBO

	glBindFramebuffer(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/, 0);
	// might remain active on error
}

CGLFrameBufferObject::CGLFrameBufferObject(int n_width, int n_height,
	int n_color_buffer_num, bool b_color_texture_target,
	GLenum n_color_internal_format, int n_color_multisample_num,
	bool b_depth_buffer, bool b_depth_texture_target,
	GLenum n_depth_internal_format, int n_depth_multisample_num,
	bool b_stencil_buffer, bool b_stencil_texture_target,
	GLenum n_stencil_internal_format, int n_stencil_multisample_num)
	:m_n_framebuffer(0), m_n_depth_rb(0), m_n_stencil_rb(0)
{
	memset(m_p_color_rb, 0, max_DrawBuffer_Num * sizeof(GLuint));
	// initialize buffers

	bool p_color_texture_target[max_DrawBuffer_Num];
	GLenum p_color_internal_format[max_DrawBuffer_Num];
	int p_color_multisample_num[max_DrawBuffer_Num];
	_ASSERTE(n_color_buffer_num <= max_DrawBuffer_Num);
	for(int i = 0; i < n_color_buffer_num; ++ i) {
		p_color_texture_target[i] = b_color_texture_target;
		p_color_internal_format[i] = n_color_internal_format;
		p_color_multisample_num[i] = n_color_multisample_num;
	}
	// expand constant parameters to array

	TFrameBufferConfig t_config(n_width, n_height, n_color_buffer_num, p_color_texture_target,
		p_color_internal_format, p_color_multisample_num, b_depth_buffer, b_depth_texture_target,
		n_depth_internal_format, n_depth_multisample_num, false, b_stencil_buffer,
		b_stencil_texture_target, n_stencil_internal_format, n_stencil_multisample_num);

	if(!Create(n_width, n_height, t_config))
		Delete(); // deletes any objects that may succeeded in creation
	// create a new FBO

	glBindFramebuffer(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/, 0);
	// might remain active on error
}

bool CGLFrameBufferObject::Create(int n_width, int n_height, const TFrameBufferConfig &r_t_config)
{
#ifdef __GL_FRAMEBUFFER_STORE_CONFIG
	m_t_config = r_t_config;
	// copy the configuration
#endif // __GL_FRAMEBUFFER_STORE_CONFIG

	m_n_width = n_width;
	m_n_height = n_height;
	// store width / height of the framebuffer

	if(max(n_width, n_height) > n_Get_MaxSize())
		return false;
	if(r_t_config.n_color_buffer_num > min(max_DrawBuffer_Num,
	   min(n_Get_MaxColorAttachment_Num(), n_Get_MaxDrawBuffer_Num())))
		return false;
	// can't bind as many draw buffers (but maybe can create as many color attachments)

	glGenFramebuffers(1, &m_n_framebuffer);
	if(!m_n_framebuffer)
		return false;
	// create a new framebuffer object

	glBindFramebuffer(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/, m_n_framebuffer);
	// bind it

	for(int i = 0, n = r_t_config.n_color_buffer_num; i < n; ++ i) {
		if(r_t_config.p_color_buffer[i].b_texture_target)
			continue;
		if(!(m_p_color_rb[i] = r_t_config.p_color_buffer[i].Create_RenderBuffer(n_width,
		   n_height, n_Draw_Buffer(i))))
			return false;
	}
	// create color buffer(s) (only in case texture is not going to be bound)

	if(r_t_config.n_color_buffer_num > 1) { // uses an NV-specific extension
		static const GLenum p_draw_buffers[] = {
			GL_COLOR_ATTACHMENT0_NV, GL_COLOR_ATTACHMENT0_NV + 1,
			GL_COLOR_ATTACHMENT0_NV + 2, GL_COLOR_ATTACHMENT0_NV + 3,
			GL_COLOR_ATTACHMENT0_NV + 4, GL_COLOR_ATTACHMENT0_NV + 5,
			GL_COLOR_ATTACHMENT0_NV + 6, GL_COLOR_ATTACHMENT0_NV + 7,
			GL_COLOR_ATTACHMENT0_NV + 8, GL_COLOR_ATTACHMENT0_NV + 9,
			GL_COLOR_ATTACHMENT0_NV + 10, GL_COLOR_ATTACHMENT0_NV + 11,
			GL_COLOR_ATTACHMENT0_NV + 12, GL_COLOR_ATTACHMENT0_NV + 13,
			GL_COLOR_ATTACHMENT0_NV + 14, GL_COLOR_ATTACHMENT0_NV + 15
		};
		_ASSERTE(sizeof(p_draw_buffers) / sizeof(p_draw_buffers[0]) >= max_DrawBuffer_Num); // make sure it won't overflow
		glDrawBuffersNV(r_t_config.n_color_buffer_num, p_draw_buffers);
	} else if(!r_t_config.n_color_buffer_num) {
		static const GLenum p_draw_buffers[] = {GL_NONE};
		glDrawBuffersNV(1, p_draw_buffers);
	}
	// set draw buffers state (state per framebuffer object)

	if(r_t_config.b_depth_stencil_buffer_packed) {
		_ASSERTE(r_t_config.b_depth_buffer == r_t_config.b_stencil_buffer);
		_ASSERTE(r_t_config.t_depth_buffer.b_texture_target ==
			r_t_config.t_stencil_buffer.b_texture_target);
		_ASSERTE(r_t_config.t_depth_buffer.n_internal_format ==
			r_t_config.t_stencil_buffer.n_internal_format);
		_ASSERTE(r_t_config.t_depth_buffer.n_multisample_sample_num ==
			r_t_config.t_stencil_buffer.n_multisample_sample_num);
		// format should be set to the same

		if(r_t_config.b_depth_buffer && !r_t_config.t_depth_buffer.b_texture_target) {
			if(!(m_n_depth_rb = r_t_config.t_depth_buffer.Create_RenderBuffer(m_n_width,
			   m_n_height, GL_DEPTH_ATTACHMENT)))
				return false;
			glFramebufferRenderbuffer(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/,
				GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_n_depth_rb);
			// GLES binds depth-stencil in two steps
		}
	} else {
		if(r_t_config.b_depth_buffer && !r_t_config.t_depth_buffer.b_texture_target) {
			if(!(m_n_depth_rb = r_t_config.t_depth_buffer.Create_RenderBuffer(m_n_width,
			   m_n_height, GL_DEPTH_ATTACHMENT)))
				return false;
		}
		if(r_t_config.b_stencil_buffer && !r_t_config.t_stencil_buffer.b_texture_target) {
			if(!(m_n_stencil_rb = r_t_config.t_stencil_buffer.Create_RenderBuffer(m_n_width,
			   m_n_height, GL_STENCIL_ATTACHMENT)))
				return false;
		}
	}
	// create depth/stencil buffers (only in case depth/stencil texture is not going to be bound)

	glBindFramebuffer(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/, 0);
	// unbind now

	return true;
}

void CGLFrameBufferObject::Delete()
{
	for(int i = 0; i < max_DrawBuffer_Num; ++ i) {
		if(m_p_color_rb[i]) {
			glDeleteRenderbuffers(1, &m_p_color_rb[i]);
			m_p_color_rb[i] = 0;
		}
	}
	if(m_n_depth_rb) {
		glDeleteRenderbuffers(1, &m_n_depth_rb);
		m_n_depth_rb = 0;
	}
	if(m_n_stencil_rb) {
		glDeleteRenderbuffers(1, &m_n_stencil_rb);
		m_n_stencil_rb = 0;
	}
	if(m_n_framebuffer) {
		glDeleteFramebuffers(1, &m_n_framebuffer);
		m_n_framebuffer = 0;
	}
}

CGLFrameBufferObject::~CGLFrameBufferObject()
{
	Delete();
}

bool CGLFrameBufferObject::b_Status() const
{
	return m_n_framebuffer != 0;
}

GLenum CGLFrameBufferObject::n_CheckStatus() const
{
	return glCheckFramebufferStatus(GL_FRAMEBUFFER/*GL_DRAW_FRAMEBUFFER*/);
}

int CGLFrameBufferObject::n_Get_MaxSize()
{
	int n_max_size = 0;
	glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &n_max_size);
	return n_max_size;
}

#if 0 // not supported by GLES (yet)
int CGLFrameBufferObject::n_Get_MaxSample_Num()
{
	int n_max_samples = 0;
	glGetIntegerv(GL_MAX_SAMPLES, &n_max_samples);
	return n_max_samples;
}

int CGLFrameBufferObject::n_Get_MaxIntegerSample_Num()
{
	int n_max_samples = 0;
	glGetIntegerv(GL_MAX_INTEGER_SAMPLES, &n_max_samples);
	return n_max_samples;
}
#endif // 0

int CGLFrameBufferObject::n_Get_MaxDrawBuffer_Num()
{
	if(!CGLES20ExtensionHandler::b_SupportedExtension("GL_NV_draw_buffers"))
		return 1;
	int n_max_buffers = 0;
	glGetIntegerv(GL_MAX_DRAW_BUFFERS_NV, &n_max_buffers);
	return n_max_buffers;
}

int CGLFrameBufferObject::n_Get_MaxColorAttachment_Num()
{
	if(!CGLES20ExtensionHandler::b_SupportedExtension("GL_NV_draw_buffers"))
		return 1;
	int n_max_attachments = 0;
	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_NV, &n_max_attachments);
	return n_max_attachments;
}

/*
 *								=== ~CGLFrameBufferObject ===
 */
