/** 
 * @file lltextureatlasmanager.cpp
 * @brief LLTextureAtlasManager class implementation.
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */
#include "llviewerprecompiledheaders.h"
#include "linden_common.h"
#include "llerror.h"
#include "llmath.h"
#include "lltextureatlas.h"
#include "lltextureatlasmanager.h"
#include "llspatialpartition.h"

const S8 MAX_NUM_EMPTY_ATLAS = 2 ;
const F32 MIN_ATLAS_FULLNESS = 0.6f ;

//*********************************************************************************************
//implementation of class LLTextureAtlasInfo
//*********************************************************************************************
LLTextureAtlasSlot::LLTextureAtlasSlot(LLTextureAtlas* atlasp, LLSpatialGroup* groupp, S16 col, S16 row, F32 xoffset, F32 yoffset, S8 slot_width) : 
	mAtlasp(atlasp),
	mGroupp(groupp),
	mCol(col),
	mRow(row),
	mReservedSlotWidth(slot_width),
	mValid(FALSE),
	mUpdatedTime(0),
	mTexCoordOffset(xoffset, yoffset),
	mTexCoordScale(1.f, 1.f)
{
	llassert_always(mAtlasp || mGroupp || mReservedSlotWidth) ;
}

LLTextureAtlasSlot::~LLTextureAtlasSlot()
{
	if(mAtlasp)
	{
		mAtlasp->releaseSlot(mCol, mRow, mReservedSlotWidth) ;
		if(mAtlasp->isEmpty())
		{
			LLTextureAtlasManager::getInstance()->releaseAtlas(mAtlasp) ;
		}
		mAtlasp = NULL ;
	}
}

//void LLTextureAtlasSlot::setAtlas(LLTextureAtlas* atlasp) 
//{
//	mAtlasp = atlasp ;
//}
//void LLTextureAtlasSlot::setSlotPos(S16 col, S16 row) 
//{
//	mCol = col ;
//	mRow = row ;
//}
//void LLTextureAtlasSlot::setSlotWidth(S8 width) 
//{
//	//slot is a square with each edge length a power-of-two number
//	mReservedSlotWidth = width ;
//}
//void LLTextureAtlasSlot::setTexCoordOffset(F32 xoffset, F32 yoffset) 
//{
//	mTexCoordOffset.mV[0] = xoffset ;
//	mTexCoordOffset.mV[1] = yoffset ;
//}

void LLTextureAtlasSlot::setSpatialGroup(LLSpatialGroup* groupp) 
{
	mGroupp = groupp ;
}
void LLTextureAtlasSlot::setTexCoordScale(F32 xscale, F32 yscale) 
{
	mTexCoordScale.mV[0] = xscale ;
	mTexCoordScale.mV[1] = yscale ;
}
//*********************************************************************************************
//END of implementation of class LLTextureAtlasInfo
//*********************************************************************************************

//*********************************************************************************************
//implementation of class LLTextureAtlasManager
//*********************************************************************************************
LLTextureAtlasManager::LLTextureAtlasManager() :
	mAtlasMap(4),
	mEmptyAtlasMap(4) 
{
}

LLTextureAtlasManager::~LLTextureAtlasManager()
{
	for(S32 i = 0 ; i < 4 ; i++)
	{
		for(ll_texture_atlas_list_t::iterator j = mAtlasMap[i].begin() ; j != mAtlasMap[i].end() ; ++j)
		{
			*j = NULL ;
		}
		for(ll_texture_atlas_list_t::iterator j = mEmptyAtlasMap[i].begin() ; j != mEmptyAtlasMap[i].end() ; ++j)
		{
			*j = NULL ;
		}

		mAtlasMap[i].clear() ;
		mEmptyAtlasMap[i].clear() ;
	}
	mAtlasMap.clear() ;
	mEmptyAtlasMap.clear() ;
}

//return TRUE if qualified
BOOL LLTextureAtlasManager::canAddToAtlas(S32 w, S32 h, S8 ncomponents, LLGLenum target) 
{
	if(ncomponents < 1 || ncomponents > 4)
	{
		return FALSE ;
	}
	//only support GL_TEXTURE_2D
	if(GL_TEXTURE_2D != target)
	{
		return FALSE ;
	}
	//real image size overflows
	if(w < 8 || w > LLTextureAtlas::sMaxSubTextureSize || h < 8 || h > LLTextureAtlas::sMaxSubTextureSize)
	{
		return FALSE ;
	}

	//if non-power-of-two number
	if((w & (w - 1)) || (h & (h - 1)))
	{
		return FALSE ;
	}

	return TRUE ;
}

void LLTextureAtlasManager::releaseAtlas(LLTextureAtlas* atlasp)
{	
	LLSpatialGroup* groupp = atlasp->getLastSpatialGroup() ;
	while(groupp)
	{
		groupp->removeAtlas(atlasp, FALSE) ;
		atlasp->removeLastSpatialGroup() ;

		groupp = atlasp->getLastSpatialGroup() ;
	}

	S8 type = atlasp->getComponents() - 1 ;	
	//insert to the empty list
	if(mEmptyAtlasMap[type].size() < MAX_NUM_EMPTY_ATLAS)
	{					
		mEmptyAtlasMap[type].push_back(atlasp) ;
	}
		
	//delete the atlasp
	mAtlasMap[type].remove(atlasp) ;
}

//
//this function reserves an appropriate slot from atlas pool for an image.
//return non-NULL if succeeds.
//Note:
//1, this function does not check if the image this slot assigned for qualifies for atlas or not, 
//       call LLTextureAtlasManager::canAddToAtlas(...) to do the check before calling this function.
//2, this function also dose not check if the image is already in atlas. It always assigns a new slot anyway.
//3, this function tries to group sub-textures from same spatial group into ONE atlas to improve render batching.
//
LLPointer<LLTextureAtlasSlot> LLTextureAtlasManager::reserveAtlasSlot(S32 sub_texture_size, S8 ncomponents, 
																		  LLSpatialGroup* groupp, LLViewerTexture* imagep)
{
	if(!groupp)
	{
		//do not insert to atlas if does not have a group.
		return NULL ;
	}

	//bits_len must <= 8 and is a power of two number, i.e.: must be one of these numbers: 1, 2, 4, 8.
	if(sub_texture_size > LLTextureAtlas::sMaxSubTextureSize)
	{
		sub_texture_size = LLTextureAtlas::sMaxSubTextureSize ;
	}
	S8 bits_len = sub_texture_size / LLTextureAtlas::sSlotSize ;
	if(bits_len < 1)
	{
	   bits_len = 1 ;
	}
		
	S16 col = -1, row = -1;
	S8 total_bits = bits_len * bits_len ;

	//insert to the atlas reserved by the same spatial group
	LLPointer<LLTextureAtlas> atlasp = groupp->getAtlas(ncomponents, total_bits) ;
	if(atlasp.notNull())
	{
		if(!atlasp->getNextAvailableSlot(bits_len, col, row))
		{
			//failed
			atlasp = NULL ;
		}		
	}

   //search an atlas to fit for 'size'
	if(!atlasp)
	{
		S8 atlas_index = ncomponents - 1 ;
		ll_texture_atlas_list_t::iterator iter = mAtlasMap[atlas_index].begin() ;
		for(; iter != mAtlasMap[atlas_index].end(); ++iter) 
		{
			LLTextureAtlas* cur = (LLTextureAtlas*)*iter ;
			if(cur->getFullness() < MIN_ATLAS_FULLNESS)//this atlas is empty enough for this group to insert more sub-textures later if necessary.
			{
				if(cur->getNextAvailableSlot(bits_len, col, row))
				{
					atlasp = cur ;
					groupp->addAtlas(atlasp) ;				
					break ;
				}
			}
		}
	}

	//create a new atlas if necessary
	if(!atlasp)
	{
		if(mEmptyAtlasMap[ncomponents - 1].size() > 0)
		{
			//there is an empty one
			atlasp = mEmptyAtlasMap[ncomponents - 1].back() ;
			mEmptyAtlasMap[ncomponents - 1].pop_back() ;
		}
		else
		{
			atlasp = new LLTextureAtlas(ncomponents, 16) ;
		}
		mAtlasMap[ncomponents - 1].push_back(atlasp) ;
		atlasp->getNextAvailableSlot(bits_len, col, row) ;		
		groupp->addAtlas(atlasp) ;
	}

	F32 xoffset, yoffset ;
	atlasp->getTexCoordOffset(col, row, xoffset, yoffset) ;
	LLPointer<LLTextureAtlasSlot> slot_infop = new LLTextureAtlasSlot(atlasp, groupp, col, row, xoffset, yoffset, bits_len) ;
	
	return slot_infop ;
}

//*********************************************************************************************
//END of implementation of class LLTextureAtlasManager
//*********************************************************************************************
