//========= Copyright Valve Corporation, All rights reserved. ============//
//                       TOGL CODE LICENSE
//
//  Copyright 2011-2014 Valve Corporation
//  All Rights Reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//
// cglmfbo.cpp
//
//===============================================================================

#include "togles/rendermechanism.h"

// memdbgon -must- be the last include file in a .cpp file.
#include "tier0/memdbgon.h"

CGLMFBO::CGLMFBO( GLMContext *ctx )
{
	m_ctx = ctx;
	m_ctx->CheckCurrent();
	
	gGL->glGenFramebuffers( 1, &m_name );
	
	memset( m_attach, 0, sizeof( m_attach ) );
}


CGLMFBO::~CGLMFBO( )
{
	m_ctx->CheckCurrent();

	// detach all known attached textures first... necessary ?
	for( int index = 0; index < kAttCount; index++)
	{
		if (m_attach[ index ].m_tex)
		{
			TexDetach( (EGLMFBOAttachment)index );
		}
	}
	
	gGL->glDeleteFramebuffers( 1, &m_name );
	
	m_name = 0;
	m_ctx = NULL;
}

// the tex attach path should also select a specific slice of the texture...
// and we need a way to make renderbuffers..

static GLenum EncodeAttachmentFBO( EGLMFBOAttachment index )
{
	if (index < kAttDepth)
	{
		return GL_COLOR_ATTACHMENT0 + (int) index;
	}
	else
	{
		switch( index )
		{
			case kAttDepth:
				return	GL_DEPTH_ATTACHMENT;
			break;

			case kAttStencil:
				return	GL_STENCIL_ATTACHMENT;
			break;
			
			case kAttDepthStencil:
				return	GL_DEPTH_STENCIL_ATTACHMENT;
			break;
			
			default:
				GLMStop(); // bad news
			break;
		}
	}
	
	GLMStop(); // bad news
	// shouldn't get here
	return GL_COLOR_ATTACHMENT0;
}

void	CGLMFBO::TexAttach( GLMFBOTexAttachParams *params, EGLMFBOAttachment attachIndex, GLenum fboBindPoint )
{
	// force our parent context to be current
	m_ctx->MakeCurrent();

	// bind to context (will cause FBO object creation on first use)
	m_ctx->BindFBOToCtx( this, fboBindPoint );
	
	// it's either a plain 2D, a 2D face of a cube map, or a slice of a 3D.
	CGLMTex			*tex = params->m_tex;
	
	// always detach what is currently there, if anything
	this->TexDetach( attachIndex, fboBindPoint );

	if (!tex)
	{
		// andif they pass NULL to us, then we are done.
		return;
	}
	
	GLMTexLayout	*layout = tex->m_layout;

	GLenum			target = tex->m_layout->m_key.m_texGLTarget;
	
	GLenum			attachIndexGL = EncodeAttachmentFBO( attachIndex );
	
	switch( target )
	{
		case GL_TEXTURE_2D:
		{
			// we will attach the underlying RBO on a multisampled tex, iff the tex has one, **and** we're not being asked to attach it to the read buffer.
			// if we get a req to attach an MSAA tex to the read buffer, chances are it's BlitTex calling, andit has already resolved the tex, so in those
			// cases you really do want to attach the texture and not the RBO to the FBO in question.
			
			bool useRBO = false;	// initial state
			
			if (layout->m_key.m_texFlags & kGLMTexMultisampled)
			{
				// it is an MSAA tex
				if (fboBindPoint == GL_READ_FRAMEBUFFER)
				{
					// I think you just want to read a resolved tex.
					// But I will check that it is resolved first..
					Assert( tex->IsRBODirty() == false );
				}
				else
				{
					// you want to draw into it.  You get the RBO bound instead of the tex.
					useRBO = true;
				}
			}

			if (useRBO)
			{
				// MSAA path - attach the RBO, not the texture, and mark the RBO dirty
				if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT)
				{
					// you have to attach it both places...
					// http://www.opengl.org/wiki/GL_EXT_framebuffer_object

					// bind the RBO to the GL_RENDERBUFFER target
					gGL->glBindRenderbuffer( GL_RENDERBUFFER, tex->m_rboName );

					// attach the GL_RENDERBUFFER target to the depth and stencil attach points
					gGL->glFramebufferRenderbuffer( fboBindPoint, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, tex->m_rboName);
					gGL->glFramebufferRenderbuffer( fboBindPoint, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, tex->m_rboName);

					// no need to leave the RBO hanging on
					gGL->glBindRenderbuffer( GL_RENDERBUFFER, 0 );
				}
				else
				{
					// color attachment (likely 0)
					gGL->glBindRenderbuffer( GL_RENDERBUFFER, tex->m_rboName );
					gGL->glFramebufferRenderbuffer( fboBindPoint, attachIndexGL, GL_RENDERBUFFER, tex->m_rboName);
					gGL->glBindRenderbuffer( GL_RENDERBUFFER, 0 );
				}
				tex->ForceRBODirty();
			}
			else
			{
				// regular path - attaching a texture2d
				
				if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT)
				{
					// you have to attach it both places...
					// http://www.opengl.org/wiki/GL_EXT_framebuffer_object
					
					gGL->glFramebufferTexture2D( fboBindPoint, GL_DEPTH_ATTACHMENT, target, tex->m_texName, params->m_mip );
					gGL->glFramebufferTexture2D( fboBindPoint, GL_STENCIL_ATTACHMENT, target, tex->m_texName, params->m_mip );
				}
				else
				{
					gGL->glFramebufferTexture2D( fboBindPoint, attachIndexGL, target, tex->m_texName, params->m_mip );
				}
			}
		}
		break;

		case GL_TEXTURE_3D:
		{
//			gGL->glFramebufferTexture3DEXT( fboBindPoint, attachIndexGL, target, tex->m_texName, params->m_mip, params->m_zslice );
		}
		break;

		case GL_TEXTURE_CUBE_MAP:
		{
			// adjust target to steer to the proper face of the cube map
			target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + params->m_face;

			gGL->glFramebufferTexture2D( fboBindPoint, attachIndexGL, target, tex->m_texName, params->m_mip );
		}
		break;
	}
	
	// log the attached tex
	m_attach[ attachIndex ] = *params;
	
	// indicate that the tex has been bound to an RT
	tex->m_rtAttachCount++;
}

void	CGLMFBO::TexDetach( EGLMFBOAttachment attachIndex, GLenum fboBindPoint )
{
	// force our parent context to be current
	m_ctx->MakeCurrent();

	// bind to context (will cause FBO object creation on first use)
	m_ctx->BindFBOToCtx( this, fboBindPoint );

	if (m_attach[ attachIndex ].m_tex)
	{
		CGLMTex			*tex = m_attach[ attachIndex ].m_tex;
		GLMTexLayout	*layout = tex->m_layout;
		GLenum			target = tex->m_layout->m_key.m_texGLTarget;
		
		GLenum			attachIndexGL = EncodeAttachmentFBO( attachIndex );

		switch( target )
		{
			case GL_TEXTURE_2D:
			{
				if (layout->m_key.m_texFlags & kGLMTexMultisampled)
				{
					// MSAA path - detach the RBO, not the texture
					// (is this the right time to resolve?  probably better to wait until someone tries to sample the texture)

					gGL->glBindRenderbuffer( GL_RENDERBUFFER, 0 );
						
					if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT)
					{
						// detach the GL_RENDERBUFFER target at depth and stencil attach points
						gGL->glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);						
							
						gGL->glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
					}
					else
					{
						// color attachment (likely 0)
						gGL->glFramebufferRenderbuffer( GL_FRAMEBUFFER, attachIndexGL, GL_RENDERBUFFER, 0);
					}
				}
				else
				{
					// plain tex detach
					if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT)
					{
						// you have to detach it both places...
						// http://www.opengl.org/wiki/GL_EXT_framebuffer_object
						
						gGL->glFramebufferTexture2D( fboBindPoint, GL_DEPTH_ATTACHMENT, target, 0, 0 );
						gGL->glFramebufferTexture2D( fboBindPoint, GL_STENCIL_ATTACHMENT, target, 0, 0 );
					}
					else
					{
						gGL->glFramebufferTexture2D( fboBindPoint, attachIndexGL, target, 0, 0 );
					}
				}
			}
			break;

			case GL_TEXTURE_3D:
			{
//				gGL->glFramebufferTexture3DEXT( fboBindPoint, attachIndexGL, target, 0, 0, 0 );
			}
			break;

			case GL_TEXTURE_CUBE_MAP:
			{
				gGL->glFramebufferTexture2D( fboBindPoint, attachIndexGL, target, 0, 0 );
			}
			break;
		}
		
		// un-log the attached tex
		memset( &m_attach[ attachIndex ], 0, sizeof( m_attach[0] ) );
		
		// drop the RT attach count
		tex->m_rtAttachCount--;
	}
	else
	{
		//Debugger(); // odd, but not harmful - typ comes from D3D code passing NULL into SetRenderTarget
	}
}

void CGLMFBO::TexScrub( CGLMTex *tex )
{
	// see if it's attached anywhere
	for( int attachIndex = 0; attachIndex < kAttCount; attachIndex++ )
	{
		if (m_attach[ attachIndex ].m_tex == tex)
		{
			// blammo
			TexDetach( (EGLMFBOAttachment)attachIndex, GL_DRAW_FRAMEBUFFER );
		}
	}
}


bool	CGLMFBO::IsReady( void )
{
	bool result = false;
	
	// ensure our parent context is current
	m_ctx->CheckCurrent();
	
	// bind to context (will cause FBO object creation on first use)
	m_ctx->BindFBOToCtx( this );

	GLenum status;
	status = gGL->glCheckFramebufferStatus(GL_FRAMEBUFFER);

#if 0
	switch(status)
	{
		case GL_FRAMEBUFFER_COMPLETE_EXT:
			result = true;
		break;

		case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
			result = false;
			DebuggerBreak();
			/* choose different formats */
		break;

		default:
			result = false;
			DebuggerBreak();
			/* programming error; will fail on all hardware */
		break;
	}
#endif
	return true;
}