Skip to main content
GameDev.net gamedev.net
🔒 Locked

Implemeting a vertex cache algorithm

Started by Bakura Oct 5, 2007 at 2:48 PM 26 replies 6.7k views
Original Post
Bakura
Bakura
Hi everyone, Since some days, I'm trying to implement an easy vertex cache algorithm, in order to load a model in an ASE format (non-optimized), optimized it, and then save it into a new file. I'm implemeting that algorithm : http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html, which seems good, judging by the results. But the problem is that I've implemented it, but the optimized version is slower than the non-optimized version (my graphics card is a Radeon 9800 Pro, so I think it's have a cache). But maybe my implementation is not good, for those who have implemented this algorithm, please say me why :(. First, it is a pseudo-code of what I have understand by reading the article :
Quote:
Initialisation (just one time) : numberOfTrianglesThatUseThisVertex = 0 // The name is simple posInCache = -1 // The position in the modelled cache For each triangle of the mesh : We increment numberOfTrianglesThatUseThisVertex for the three vertices of the triangle For each vertex, we calculate its score For each triangle, we calculate its score (the sum of its three vertices' scores) Body of the algorithm : modelledCache [32] // Array of 32 vertices While there are triangles to draw : We add the triangle with the higher score to another array, and delete it from the original array For each of its vertices : We decrement numberOfTrianglesThatUseThisVertex Each vertex is shift 3 If the vertex is not in the cache We add it at the top Else We erase it from the cache, and add it in the top of the cache For each vertex, we calculate its new score For each triangle, we calculate its new score
Now, here is the code. I assume that I have already an array of the vertices, and another of the indices, loaded from the ASE file. The two classes, one for the vertices, and another for the triangles... The code is from the algorithm :
const float FindVertexScore_CacheDecayPower = 1.5f;
const float FindVertexScore_LastTriScore = 0.75f;
const float FindVertexScore_ValenceBoostScale = 2.0f;
const float FindVertexScore_ValenceBoostPower = 0.5f;
const std::size_t MaxSizeVertexCache = 32;

struct Vert
{
	int positionInCache; // La position dans le cache modèle
	float score; // Le score de la vertice
	std::size_t numTrianglesThatUseIt; // Nombre de triangles qui l'utilisent
	std::vector<std::size_t> trianglesIndices; // Indices des triangles
	
	bool operator== (const Vert & vert)
	{
		if (positionInCache == vert.positionInCache &&
			 score == vert.score && numTrianglesThatUseIt == vert.numTrianglesThatUseIt && trianglesIndices == vert.trianglesIndices)
			 return true;
		
             return false;
	}

	void ComputeScore ()
	{
		score = 0.0f;

		if (numTrianglesThatUseIt == 0)
		{
         score = -1.0f;
			return;
		}

		if (positionInCache < 0)
		{
         // Vertex is not in FIFO cache - no score.
		}

		else
		{
         if (positionInCache < 3)
         {
            // This vertex was used in the last triangle,
            // so it has a fixed score, whichever of the three
            // it's in. Otherwise, you can get very different
            // answers depending on whether you add
            // the triangle 1,2,3 or 3,1,2 - which is silly.
            score = FindVertexScore_LastTriScore;
         }

         else
         {
				assert (positionInCache < MaxSizeVertexCache);
            // Points for being high in the cache.
            const float Scaler = 1.0f / (MaxSizeVertexCache);

            score = 1.0f - (positionInCache) * Scaler;
				score = std::powf (score, FindVertexScore_CacheDecayPower);
         }
		}

			// Bonus points for having a low number of tris still to
			// use the vert, so we get rid of lone verts quickly.
			float ValenceBoost = std::powf (numTrianglesThatUseIt,
                                -FindVertexScore_ValenceBoostPower);

			score += FindVertexScore_ValenceBoostScale * ValenceBoost;
	}

};

struct Tri
{
	bool isAdded; // Est-il ajouté ?
	float score; // Le score du triangle
	std::size_t indices[3]; // Indices des vertices

	bool operator< (const Tri & anotherTri)
	{
		return score < anotherTri.score;
	}
};

Now when I execute the algorithm. It's not optimized, but it should work :/ : verts is the array from the ase file, like faces, so it's the unoptimized faces orders :

std::vector<Vert> vertTab;
	vertTab.reserve (verts.size());
	std::vector<Tri> triTab;
	triTab.reserve (faces.size());
	
        // We add as many numbers of vertices as the unoptimized version
	for (std::size_t i = 0 ; i != verts.size() ; ++i)
	{
		Vert oneVert;
		oneVert.positionInCache = -1;
		oneVert.numActiveTriangles = oneVert.numTrianglesThatUseIt = 0;

		vertTab.push_back (oneVert);
	}
        
        // Don't know what the algorithm needs that...
	for (std::size_t i = 0 ; i != faces.size() ; ++i)
	{
		Tri oneTri;
		oneTri.isAdded = false;
		oneTri.indices[0] = faces.at(i).indexVertices[0];
		oneTri.indices[1] = faces.at(i).indexVertices[1];
		oneTri.indices[2] = faces.at(i).indexVertices[2];
		
		triTab.push_back (oneTri);
	}
	
        // Calculate le number of triangles that use each vertex
	for (std::size_t i = 0 ; i != triTab.size() ; ++i)
	{
		++vertTab[triTab.indices[0]].numActiveTriangles;
		++vertTab[triTab.indices[0]].numTrianglesThatUseIt;
		vertTab[triTab.indices[0]].trianglesIndices.push_back (i);

		++vertTab[triTab.indices[1]].numActiveTriangles;
		++vertTab[triTab.indices[1]].numTrianglesThatUseIt;
		vertTab[triTab.indices[1]].trianglesIndices.push_back (i);

		++vertTab[triTab.indices[2]].numActiveTriangles;
		++vertTab[triTab.indices[2]].numTrianglesThatUseIt;
		vertTab[triTab.indices[2]].trianglesIndices.push_back (i);
	}
        
        // Compute the score for eahc vertex
	std::for_each (vertTab.begin(), vertTab.end(), std::mem_fun_ref (‖::ComputeScore));
	
        // And for each triangle
	for (std::size_t i = 0 ; i != triTab.size() ; ++i)
	{
		triTab.score = vertTab[triTab.indices[0]].score +
								vertTab[triTab.indices[1]].score +
								vertTab[triTab.indices[2]].score;
	}
        
        // newTriTab is the new array (the drawing list)... So it must be the optimized order of faces...
	std::vector <Tri> newTriTab;
	newTriTab.reserve (triTab.size());

	const std::size_t NumVertexInCache = 32;
	Vert modelledCache [NumVertexInCache];
	Vert onevert;

	const std::size_t triTabsize = triTab.size();
        // Debug purpose
	for (std::size_t i = 0 ; i != 20 ; ++i)
	{
		std::cout << triTab.indices[0] << ' ' << 
			triTab.indices[1] << ' ' <<
			triTab.indices[2] << std::endl;
	}
	
        // While all the triangles of the old list are not added to the new
	while (newTriTab.size() != triTabsize)
	{
                // Looking for the higher scoring triangle
		std::vector<Tri>::iterator it = std::max_element (triTab.begin(), triTab.end());
		Tri higherScoreTri (*it);	
                // Copy it into the new array and erase it from the old
		newTriTab.push_back (higherScoreTri);
		triTab.erase (it);
		// We decremente the number of triangles that use the three vertices
		for (std::size_t i = 0 ; i != 3 ; ++i)
			--(vertTab [higherScoreTri.indices].numTrianglesThatUseIt);

		// On décale chaque vertice dans le cache
		for (std::size_t i = NumVertexInCache - 1 ; i > 2 ; --i)
		{
			modelledCache = modelledCache[i - 3];
			modelledCache.positionInCache = i;
		}

		for (std::size_t i = NumVertexInCache - 1 ; i > 2 ; --i)
		{
			if ((modelledCache == vertTab [higherScoreTri.indices[0]]) ||
				 (modelledCache == vertTab [higherScoreTri.indices[1]]) ||
				 (modelledCache == vertTab [higherScoreTri.indices[2]]))
				 modelledCache = onevert;
		}
		
		for (std::size_t i = 0 ; i != 3 ; ++i)
		{
			//if (std::find (modelledCache, modelledCache + NumVertexInCache,
			//			  vertTab [higherScoreTri.indices]))
			modelledCache = vertTab [higherScoreTri.indices];
			vertTab [higherScoreTri.indices].positionInCache = i;
		}

		std::for_each (vertTab.begin(), vertTab.end(), std::mem_fun_ref (‖::ComputeScore));

		for (std::size_t i = 0 ; i != triTab.size() ; ++i)
		{
			triTab.score = vertTab[triTab.indices[0]].score +
									vertTab[triTab.indices[1]].score +
									vertTab[triTab.indices[2]].score;
		}
	}


I think the code is quite clear given the pseudo code... I don't know what it doesn't work. Some things are really strange, because whatever the maxCacheSize is (8, 32, 1024...) the order is the same, but on the paper given above, the lists seems to be different (because he gets different results...). Maybe there is an error either from my implementation, or the understanding of the algorithm... I contacted the author but he didn't answer me... So if you could help me, thanks for your help ;)
lonesock
lonesock
I've implemented it and it does work quite well. Make sure that you implement the vertex re-ordering step (found under "Additional Notes"). Without that step I ended up having worse rendering performance for large meshes.

Also, I would write a small routine which checks the ACMR of a given mesh for a given cache size (I test my meshes before and after this procedure to see what kind of improvement I am getting. I use a cache size of 14 to model the ATI cache size, nVidia's cache sizes seem to be universally larger). Remember to use a FIFO cache model for the ACMR checking routine, instead of a LRU cache as is being used in the algorithm.

If you're still having trouble I'd be happy to post or email you my implementation.
Bakura
Bakura
Thank you for your answer. I would like, if it doesn't bother you, that you send me your implementation of the algorithm (I'm gonna send you my mail by PM).

I don't understand well what the additionnal notes mean :

"Next, the order of the vertices in the vertex buffer is found. Start with an empty vertex buffer. Scan through the triangles in the order found above. Check whether each of the three vertices has been added to the VB yet. If not, add them, and remember the remapping from the old index number to the new one. Keep going until you run out of triangles. Finally, remap all the old indices to the new ones, but do not change the order the triangles are rendered in. The vertices have now been reordered so that when the graphics card renders the triangles, it will read vertices in a mostly-linear fashion (it would be completely linear, except for vertices that dropped out of the post-transform vertex cache). This reordering of vertex data helps the pre-transform cache to use the memory bandwidth effectively. This step can produce just as much of a speed improvement as the reordering of the triangles, so although the algorithm is simple, it is important."

The order of the vertices ? That order is the order of the new indices ? I really don't understand, my vertices aren't duplicate, so why should I check if they are already added to the VB ?

There are some things I didn't implement :
Instead, each vertex holds the following data:

* Its position in the modelled cache (-1 if it is not in the cache)
* Its current score
* The total number of triangles that use it
* The number of triangles not yet added that use it
* The list of triangle indices that use it

I really don't know what the list of triangles indices that use it can be usual... Because I have a list of vertices, and what I want to reorder is the indices, right ?

Maybe the order of the vertices in the VBO is important ? I don't know.
Enrico
Enrico
Quote:
Original post by lonesock
If you're still having trouble I'd be happy to post or email you my implementation.

Would you post your code here in the forums, so everybody can use it? :)
--
lonesock
lonesock
Quote:
Original post by Enrico
Would you post your code here in the forums, so everybody can use it? :)

[8^) Sure! Here it is:

mesh_helper.h
/*	Jonathan Dummer	March 2, 2007	just some routines that may help with meshes	MIT License*/#ifndef HEADER_MESH_HELPER#define HEADER_MESH_HELPER#include <set>#include <vector>//	structures for the triangle order optimzationstruct tri_data{	bool added;	float score;	unsigned int verts[3];};struct vert_data{	float score;	std::set<unsigned int> remaining_tris;};///	return the number of cache_misses per triangle (lower is better)float calculate_average_cache_miss_ratio(		const std::vector<unsigned int> &tri_indices,		unsigned int cache_size = 14 );///	reorder the triangles, trying to minimize number of cache_misses///	per triangle (using Tom Forsyth's super simple method.)bool optimize_vertex_cache_order(		std::vector<unsigned int> &tri_indices,		unsigned int cache_size = 32 );#endif // HEADER_MESH_HELPER


mesh_helper.cpp
/*	Jonathan Dummer	March 2, 2007	just some routines that may help with meshes	MIT License*/#include "mesh_helper.h"#include <set>#include <cmath>#include <cassert>#include <iostream>bool optimize_vertex_cache_order( std::vector<unsigned int> &tri_indices, unsigned int cache_size ){	if( (tri_indices.size() < 3) || (tri_indices.size() % 3 != 0) || (cache_size < 4) )		return false;	unsigned int num_triangles = tri_indices.size() / 3;	unsigned int num_vertices = 0;	for( unsigned int i = 0; i < num_triangles*3; ++i )		if( tri_indices > num_vertices )			num_vertices = tri_indices;	++num_vertices;	//	size of the optimization cache	std::vector<float> cache_score(cache_size + 3, 0.75);	std::vector<int> cache_idx(cache_size + 3, -1);	std::vector<int> grow_cache_idx(cache_size + 3, -1);	for( unsigned int i = 3; i < cache_size; ++i )		cache_score = powf( (cache_size - i) / (cache_size - 3.0), 1.5 );	for( unsigned int i = 0; i < 3; ++i )		cache_score[cache_size+i] = 0.0;	//	how many tris do we need to add?	int tris_left = num_triangles;	//	add all verts and tris to the lists	std::vector<tri_data> t(num_triangles);	std::vector<vert_data> v(num_vertices);	for( unsigned int i = 0; i < num_vertices; ++i )	{		//	initialize this vert		v.score = 0.0;		v.remaining_tris.clear();	}	for( unsigned int i = 0; i < num_triangles; ++i )	{		//	set up this tri		t.added = false;		t.score = 0.0;		t.verts[0] = tri_indices[i*3+0];		t.verts[1] = tri_indices[i*3+1];		t.verts[2] = tri_indices[i*3+2];		//	and add this tri index to each of it's verts		v[tri_indices[i*3+0]].remaining_tris.insert( i );		v[tri_indices[i*3+1]].remaining_tris.insert( i );		v[tri_indices[i*3+2]].remaining_tris.insert( i );	}	//	now initialize all the scores for the vertices	for( unsigned int i = 0; i < num_vertices; ++i )	{		//	none of them are in the index yet, just use thier valence score		v.score = powf( v.remaining_tris.size(), -0.5 ) * 2.0;	}	//	and the triangles' scores	float best_score = 0.0;	int best_idx = -1;	for( unsigned int i = 0; i < num_triangles; ++i )	{		t.score = v[t.verts[0]].score + v[t.verts[1]].score + v[t.verts[2]].score;		if( t.score > best_score )		{			best_score = t.score;			best_idx = i;		}	}	//	now keep adding triangles	while( tris_left > 0 )	{		//	scan all tris if the best score is suspicious		//if( best_score < 1.0 )		if( best_score < 0.01 )		{			//std::cerr << "low score looks suspicious...re-checking!" << std::endl;			best_score = 0.0;			best_idx = -1;			for( unsigned int i = 0; i < num_triangles; ++i )			if( !t.added )			{				if( t.score > best_score )				{					best_score = t.score;					best_idx = i;				}			}		}		if( best_idx < 0 )		{			//	not good			std::cerr << "triangle order optimizer failed with " << tris_left <<				" out of " << num_triangles << " left for processing." << std::endl;			tris_left = 0;			//	need a warning...for now, an assert			assert( !"Your stupid triangle re-organizing code isn't working, Jonathan!!" );		}		else		{			//	add in this tri			int a = t[best_idx].verts[0];			int b = t[best_idx].verts[1];			int c = t[best_idx].verts[2];			//	put this tri back into circulation			tri_indices[(num_triangles - tris_left)*3+0] = a;			tri_indices[(num_triangles - tris_left)*3+1] = b;			tri_indices[(num_triangles - tris_left)*3+2] = c;			//	remove this tri from the association of each of the verts			for( int i = 0; i < 3; ++i )			{				v[t[best_idx].verts].remaining_tris.erase( best_idx );			}			t[best_idx].added = true;			--tris_left;			//std::cerr << best_idx << "," << best_score << std::endl;			//	put these 3 verts at the top of the LRU list			grow_cache_idx[0] = a;			grow_cache_idx[1] = b;			grow_cache_idx[2] = c;			int idx = 3;			for( unsigned int i = 0; i < cache_size; ++i )			{				//	clear out my growing cache				grow_cache_idx[i+3] = -1;				if( (cache_idx != a) &&					(cache_idx != b) &&					(cache_idx != c) )				{					grow_cache_idx[idx++] = cache_idx;				}			}			cache_idx = grow_cache_idx;			//	update the weights			//	(cache_size+3 because I want to update the triangles whose			//	vertices fell out of the cache as well.)			for( unsigned int i = 0; i < cache_size+3; ++i )			if( cache_idx >= 0 )			{				idx = cache_idx;				//	store the old score				float old_score = v[idx].score;				//	get the new score				float new_score = cache_score + 2.0 * powf( v[idx].remaining_tris.size(), -0.5 );				 v[idx].score = new_score;				//	now update all remaining linked triangles!				for( std::set<unsigned int>::iterator						it = v[idx].remaining_tris.begin();						it != v[idx].remaining_tris.end();						++it )				{					t[*it].score += new_score - old_score;				}			}			//	search for the next best tri			best_score = 0.0;			best_idx = -1;			for( unsigned int i = 0; i < cache_size; ++i )			if( cache_idx >= 0 )			{				idx = cache_idx;				//	is one of these triangles the best?				for( std::set<unsigned int>::iterator						it = v[idx].remaining_tris.begin();						it != v[idx].remaining_tris.end();						++it )				{					if( t[*it].score > best_score )					{						best_score = t[*it].score;						best_idx = *it;					}				}			}		}	}	return true;}float calculate_average_cache_miss_ratio(		const std::vector<unsigned int> &tri_indices,		unsigned int cache_size ){	if( tri_indices.size() <= cache_size )		return -1.0;	std::vector<int> cache_idx(cache_size, -1);	int cache_ptr = 0;	int cache_misses = 0;	for( unsigned int i = 0; i < tri_indices.size(); ++i )	{		//	is the newest vertex in the ring buffer (FIFO)?		bool cache_hit = false;		for( unsigned int j = 0; j < cache_size; ++j )		{			//	if this cache entry == the index, then cache_hit = true			cache_hit |= ( cache_idx[j] == (int)tri_indices );		}		if( !cache_hit )		{			//	store the newest vertex ID in the cache			cache_idx[cache_ptr] = tri_indices;			//	move to the next sopt (ring buffer FIFO)			cache_ptr = (cache_ptr + 1) % cache_size;			//	count'em			++cache_misses;		}	}	//	cache misses per triangle	return cache_misses / (tri_indices.size() / 3.0);}


And for some reason, I had this function to re-order the verticies (to match how they'll be used by the newly ordered triangles) separate. This is the portion I was mentioning, Bakura. It's important to do this, especially for large meshes, because if you have the perfect ordering of triangles but they use vertices which are not near each other (i.e. do not fit in the GPU cache) it will still take a long time to render. Anyway, here's the snippet:
	//	now, re-order the vertices to take advantage of this new triangle order	//	(this definitely helps!)	std::map<GLuint,GLuint> old2new;	std::vector<GLfloat> new_xyz;	//	vertices	std::vector<GLfloat> new_uv;	//	texture coordinates	std::vector<GLfloat> new_ijk;	//	normal vectors	GLuint counter = 0;	for( unsigned int i = 0; i < num_triangles*3; ++i )	{		//	has this point (vertex id) been found yet?		int vid = abc;		if( old2new.find( vid ) == old2new.end() )		{			//	not yet found!  add the original vertex info to the new vectors			new_xyz.push_back( xyz[vid*3+0] );			new_xyz.push_back( xyz[vid*3+1] );			new_xyz.push_back( xyz[vid*3+2] );			new_uv.push_back( uv[vid*2+0] );			new_uv.push_back( uv[vid*2+1] );			new_ijk.push_back( ijk[vid*3+0] );			new_ijk.push_back( ijk[vid*3+1] );			new_ijk.push_back( ijk[vid*3+2] );			//	add it to the map			old2new[vid] = counter;			//	and swap it!			abc = counter;			//	increment my counter			++counter;		} else		{			//	yeah, have this one already			abc = old2new[vid];		}	}	//	keep the updated data vectors	xyz.swap( new_xyz );	uv.swap( new_uv );	ijk.swap( new_ijk );
yoshscout
yoshscout
this is really fascinating stuff. im thinking about applying this to my models before i save them into my custom format. tom says that going over 32 isnt recommended because it takes longer and doesnt produce better results, but couldnt that be from his card not being able to caches a larger section of vertices.(although i doubt he doesnt have a beast of a card lol)

i was kind of thinking about exporting models into a ready for cache method and running some test on the client machine to determine best cache size and reconfiguring meshes for their machine. its kinda wild but everything to get that extra push.

i remember reading in here somewhere that pre-T&L cache could hold a whole mesh if you stayed in the < 65535 ie. 16 bit indexes. that would be around the order of 2-4Mbs im guessing i dont know haha what kind of crazy tangent am i on?
Bakura
Bakura
Thanks lonesock for the code :).
lonesock
lonesock
Quote:
Original post by yoshscout
...tom says that going over 32 isnt recommended because it takes longer and doesnt produce better results, but couldnt that be from his card not being able to caches a larger section of vertices...

Probably not. The ACMR that he is calculating comes from a function like the calculate_average_cache_miss_ratio() function that I listed above. And as you can see from the table near the bottom of Tom's paper, he tested the results with a simulated cache size of 1024, which is ridiculously large.

@Bakura: You're welcome!
Bakura
Bakura
Hi !

It's me, again ! First, thank you for the code loneshock. I tried it and works perfectly well, but I wanted to do it a little differently, and I'm trying to make it works since 3 days, and it doesn't work, and I'm trying to getting really mad ! For the health of my computer, please help me :d.

In fact, I've just changed some things. Goodbye your grow_cache_size and goodbye your cache_score. I created only one smart "LRU Cache" made with a deque. It does some things :

If I insert an element, will verificate if it's not already in the cache. If not, it will be added at the top, and if it is, it will put it at the top, without adding another values, so there isn't any duplicate values. Furthemore, when I add an element, it will return either the added value if the cache is not oversize, or will return the delete value. So a simple check like that :
int a = myCache.insert (b);

if (a!=b)
// a was in the cache, but is not anymore because it was deleted, if a == b, so no // value was added.

Here is the code for that :

[source="cpp"]// Pour un cache de type dequetemplate <typename Value_Type>class LRUCacheDeq{	public:		LRUCacheDeq (const std::size_t maxSize)			: maxSize (maxSize)		{		}			private:		// Quelques typedefs pour faciliter le travail...		typedef typename std::deque<Value_Type>::iterator deq_iterator;		typedef typename std::deque<Value_Type>::const_iterator deq_const_iterator;				// Taille maximale du cache		std::size_t maxSize;		// Les données en elle-même		std::deque<Value_Type> LRUList;	public:		// On cherche un élément		deq_iterator find (const Value_Type & value)		{			// On recherche l'élément			deq_iterator deq_iter = std::find (LRUList.begin(), LRUList.end(),														  value);			// Si aucun élément n'a été trouvé, on retourne l'itérateur vers la fin			if (deq_iter == LRUList.end())				return deq_iter;			// Sinon, on replace l'élément au début de la liste			LRUList.insert (LRUList.begin(), *deq_iter);			LRUList.erase (deq_iter);			// On renvoit l'élément			return (LRUList.begin());		}		// Fonction pour insérer un élément		Value_Type & insert (const Value_Type & value)		{			// On recherche si l'élément est déjà dans la liste			deq_iterator deq_iter = find (value);						// Si l'élément n'est pas encore présent, on l'ajoute à la fin de la liste.			// S'il est présent, il sera automatiquement ajouté au début (MRU)			if (deq_iter == LRUList.end())				LRUList.push_front (value);			// On vérifie si on ne dépasse pas la taille maximale du cache			if (LRUList.size() > maxSize)			{				// On récupère la valeur de l'élément qui va être supprimé				Value_Type & val = LRUList.back();				// On supprime le dernier élément				LRUList.pop_back();				// On renvoit l'élément				return val;			}			// Sinon, on retourne l'élément qui vient d'être ajouté			return (LRUList.front());		}		Value_Type & operator[] (const std::size_t idx)		{			assert (idx < maxSize);						return LRUList[idx];		}					// On affiche les éléments dans l'ordre, du plus récent au plus ancien		void Write () const		{			for (deq_const_iterator deq_const_iter = LRUList.begin() ; 				  deq_const_iter != LRUList.end() ; ++deq_const_iter)			{				std::cout << *deq_const_iter << std::endl;			}		}			};


That code works really well, so I'm sure it's not the reason why my program crashes. And here is the code of the optimization order. It is quite similar to loneshocks', only the cache implementation is different.

I've read and reread my code again and again, and I don't know why it doesn't work, while loneshocks' works perfectly well... It should acts the same !!

MESHOptimizer.hpp :
[source="cpp"]#ifndef MESHOPTIMIZER_HPP#define MESHOPTIMIZER_HPP#include <iostream>#include <vector>#include <set>#include <cmath>#include <algorithm>#include "LRUCache.hpp"// Plusieurs constantes utilisées pour le calcule des scoresconst float FindVertexScore_CacheDecayPower = 1.5f;const float FindVertexScore_LastTriScore = 0.75f;const float FindVertexScore_ValenceBoostScale = 2.0f;const float FindVertexScore_ValenceBoostPower = 0.5f;// Quelques structures pour la mise en place de l'algorithme :struct TriData{	bool isAdded; // Le triangle est-il ajoutée à la liste de dessin ?	float score; // Score du triangle (somme des scores de ses trois vertices)	std::size_t indices[3]; // Les indices vers ses vertices};struct VertData{	float score; // Score de la vertice	std::set<std::size_t> remainingTriangles; // Indices vers les triangles qui															// utilisent cette vertice non ajoutés															// à la liste des triangles à dessiner		float CalculateScore (const int positionInCache, const std::size_t cacheSize)	{		if (remainingTriangles.empty())		{			score = -1.0f;			return score;		}				// N'est pas dans le cache		if (positionInCache < 0)		{		}		if (positionInCache < 3)		{			// Cette vertice est utilisée dans le dernier triangle ajouté, elle a			// un score spécial			score = FindVertexScore_LastTriScore;		}		else		{			assert (positionInCache < cacheSize);			const float scaler = 1.0f / (cacheSize - 3);			score = std::powf (1.0f - (positionInCache - 3) * scaler, FindVertexScore_CacheDecayPower);		}		float valenceBoost = std::powf (remainingTriangles.size(),												  -FindVertexScore_ValenceBoostPower);		score += FindVertexScore_ValenceBoostScale * valenceBoost;		return score;	}};// Cette fonction prend en paramètre un tableau contenant les indices non-optimisés// ainsi que la taille du cache (avec les cartes graphiques actuelles, la valeur// optimale est d'environ 32. On peut également envoyer le nombre de vertices si// il est connu à l'avance, ce qui évite de le calculer à l'éxecutionbool OptimizeIndicesOrder (std::vector<std::size_t> & triIndices,									const std::size_t cacheSize = 32,									const std::size_t numVertices = 0);


MESHOptimizer.cpp :

[source="cpp"]#include "MeshOptimizer.hpp"// Cette fonction prend en paramètre un tableau contenant les indices non-optimisés// ainsi que la taille du cache (avec les cartes graphiques actuelles, la valeur// optimale est d'environ 32. Elle utilise la très simple méthode trouvée par // Tom Forsyth (mais pas pour autant peu performante ^^). On peut également envoyer// le nombre de vertices si il est connu à l'avance, ce qui évite de le calculer à // l'éxecution. Le tableau d'indices passé en paramètre NE DOIT PAS avoir d'indices// équivalents (par exemple 1, 1...), sinon le programme plantera. Appelez la fonction// unique sur le vector pour supprimer les indices identiques.bool OptimizeIndicesOrder (std::vector<std::size_t> & triIndices,									const std::size_t cacheSize,									const std::size_t numberOfVertices){	// On vérifie qu'il n'y ait pas moins de 3 indices, ou que le cache soit trop	// faible (auquel cas les cartes graphiques visées sont trop anciennes et un	// système de triangle strips s'avère plus efficace	if ((triIndices.size() < 3) || (cacheSize < 4))		return false;	std::size_t numTriangles = triIndices.size() / 3; // 3 indices par triangle...	std::size_t trianglesLeft = numTriangles; // Nombre de triangles restant à dessiner	std::size_t numVertices = numberOfVertices;	// On calcule le nombre de vertices s'il n'est pas connu (numberOfVertices == 0)	if (numVertices == 0)	{		std::size_t maxIndice = *std::max_element (triIndices.begin(), triIndices.end());		numVertices = maxIndice;	}	if (numberOfVertices == 0)		++numVertices;	// On crée un cache de la taille indiquée en paramètre	LRUCacheDeq<int> cacheIdx (cacheSize);		// On initialise le cache à une valeur supérieure à n'importe quelle indice	// de vertice, et chacune étant différente de l'autre (puisqu'on n'autorise	// pas de valeurs dupliquées)	for (std::size_t i = 0 ; i != cacheSize ; ++i)		cacheIdx.insert (-1 - i);	// On crée deux vectors contenant nos données pour l'algorithme	std::vector<TriData> triData (numTriangles);	std::vector<VertData> vertData (numVertices);		// Puis on initialise chaque triangle de la même façon, à part qu'on utilise pas	// d'itérateurs (ben ouais, j'ai besoin de i dans ce cas...)	for (std::size_t i = 0 ; i != numTriangles ; ++i)	{		triData.isAdded = false; // Le triangle n'est pas ajouté...		triData.score = 0.0f;		// On initialise les indices de chaque triangle avec le tableau passé en 		// paramètre à la fonction		triData.at(i).indices[0] = triIndices.at(i * 3);		triData.at(i).indices[1] = triIndices.at(i * 3 + 1);		triData.at(i).indices[2] = triIndices.at(i * 3 + 2);		// Enfin, on ajoute l'indice de ce triangle à chacune des vertices qui		// l'utilise		vertData.at(triData.at(i).indices[0]).remainingTriangles.insert (i);		vertData.at(triData.at(i).indices[1]).remainingTriangles.insert (i);		vertData.at(triData.at(i).indices[2]).remainingTriangles.insert (i);	}	// On calcule le score pour chaque vertice. Etant donné qu'aucune des vertices	// n'est actuellement dans le cache, seule la valence sera calculée	for (std::vector<VertData>::iterator it = vertData.begin() ; it != vertData.end() ;		  ++it)	{		it->score = std::powf (it->remainingTriangles.size(), -FindVertexScore_ValenceBoostPower)			                    * FindVertexScore_ValenceBoostScale;			}	// On calcule le score de chaque triangle et on récupère l'indice du triangle	// ayant le score le plus haut	float bestScore = 0.0f;	int bestIdx = -1;	for (std::size_t i = 0 ; i != numTriangles ; ++i)	{		triData.score = vertData.at(triData.at(i).indices[0]).score +								 vertData.at(triData.at(i).indices[1]).score +								 vertData.at(triData.at(i).indices[2]).score;		if (triData.score > bestScore)		{			bestScore = triData.at(i).score;			bestIdx = i;		}	}	// On arrive à présent dans le corps de l'algorithme. L'algorithme tournera tant	// qu'il y aura des triangles à ajouter	while (trianglesLeft != 0)	{		// On cherche dans tous les triangles non ajoutés si le score du triangle		// trouvé est anormalement bas		if (bestScore < 0.01f)		{			bestScore = 0.0f;			bestIdx = -1;			for (std::size_t i = 0 ; i < numTriangles ; ++i)			{				if (!triData.at(i).isAdded)				{					if (triData.at(i).score > bestScore)					{						bestScore = triData.at(i).score;						bestIdx = i;					}				}			}		}				// On récupère les indices des trois vertices du meilleur triangle		std::size_t vertA = triData.at(bestIdx).indices[0];		std::size_t vertB = triData.at(bestIdx).indices[1];		std::size_t vertC = triData.at(bestIdx).indices[2];		// On supprime, pour chaque vertice utilisant ce triangle, l'indice vers		// celui-ci		vertData.at(vertA).remainingTriangles.erase (bestIdx);		vertData.at(vertB).remainingTriangles.erase (bestIdx);		vertData.at(vertC).remainingTriangles.erase (bestIdx);		// On met à jour les indices (ceux passés en paramètre)		triIndices.at((numTriangles - trianglesLeft) * 3) = vertA;		triIndices.at((numTriangles - trianglesLeft) * 3 + 1) = vertB;		triIndices.at((numTriangles - trianglesLeft) * 3 + 2) = vertC;		// On met à jour l'état de ce triangle		triData.at(bestIdx).isAdded = true;		--trianglesLeft;				// Enfin, on ajoute dans le cache les trois vertices qui composent ce triangle,		// la structure de données étant faite pour que tout soit décalé automatiquement		// de trois éléments. On récupère également les vertices ayant dégagés du		// cache. Si l'indice de la vertice est strictement inférieure au nombre de vertices		// contenues dans le modèle, on ne fait rien, sinon on calcule le score de la vertice qui		// sort, qui n'est que sa valence étant donné qu'elle n'est plus dans le cache		int rejectedVertice = cacheIdx.insert (vertA);		if ((rejectedVertice >= 0) && (rejectedVertice != vertA))		{				float oldScore = vertData.at(rejectedVertice).score;				float newScore = vertData.at(rejectedVertice).CalculateScore (-1, cacheSize);				vertData.at (rejectedVertice).score = newScore;				float newScoreMinOldScore = newScore - oldScore;				for (std::set<std::size_t>::iterator iter = vertData.at(rejectedVertice).remainingTriangles.begin() ;					  iter != vertData.at(rejectedVertice).remainingTriangles.end() ; ++iter)				{					triData.at(*iter).score += newScoreMinOldScore;					triData.at(*iter).score = vertData.at(triData.at(*iter).indices[0]).score +								 vertData.at(triData.at(*iter).indices[1]).score +								 vertData.at(triData.at(*iter).indices[2]).score;				}		}		rejectedVertice = cacheIdx.insert (vertB);		if ((rejectedVertice >= 0) && (rejectedVertice != vertB))		{				float oldScore = vertData.at(rejectedVertice).score;				float newScore = vertData.at(rejectedVertice).CalculateScore (-1, cacheSize);				vertData.at (rejectedVertice).score = newScore;				float newScoreMinOldScore = newScore - oldScore;				for (std::set<std::size_t>::iterator iter = vertData.at(rejectedVertice).remainingTriangles.begin() ;					  iter != vertData.at(rejectedVertice).remainingTriangles.end() ; ++iter)				{					triData.at(*iter).score += newScoreMinOldScore;					triData.at(*iter).score = vertData.at(triData.at(*iter).indices[0]).score +								 vertData.at(triData.at(*iter).indices[1]).score +								 vertData.at(triData.at(*iter).indices[2]).score;				}		}		rejectedVertice = cacheIdx.insert (vertC);		if ((rejectedVertice >= 0) && (rejectedVertice != vertC))		{				float oldScore = vertData.at(rejectedVertice).score;				float newScore = vertData.at(rejectedVertice).CalculateScore (-1, cacheSize);				vertData.at (rejectedVertice).score = newScore;				float newScoreMinOldScore = newScore - oldScore;				for (std::set<std::size_t>::iterator iter = vertData.at(rejectedVertice).remainingTriangles.begin() ;					  iter != vertData.at(rejectedVertice).remainingTriangles.end() ; ++iter)				{					triData.at(*iter).score += newScoreMinOldScore;					triData.at(*iter).score = vertData.at(triData.at(*iter).indices[0]).score +								 vertData.at(triData.at(*iter).indices[1]).score +								 vertData.at(triData.at(*iter).indices[2]).score;				}		}		for (std::size_t i = 0 ; i != cacheSize ; ++i)		{			int vertIdx = cacheIdx;			if (vertIdx >= 0)			{				float oldScore = vertData.at(vertIdx).score;				float newScore = vertData.at(vertIdx).CalculateScore (i, cacheSize);				vertData.at (vertIdx).score = newScore;				float newScoreMinOldScore = newScore - oldScore;				for (std::set<std::size_t>::iterator iter = vertData.at(vertIdx).remainingTriangles.begin() ;					  iter != vertData.at(vertIdx).remainingTriangles.end() ; ++iter)				{					triData.at(*iter).score += newScoreMinOldScore;					triData.at(*iter).score = vertData.at(triData.at(*iter).indices[0]).score +								 vertData.at(triData.at(*iter).indices[1]).score +								 vertData.at(triData.at(*iter).indices[2]).score;				}			}		}		// Enfin, on cherche de nouveau le triangle ayant le plus haut score parmi		// les triangles rattachées aux vertices dans le cache		bestScore = 0.0f;		bestIdx = -1;		for (std::size_t i = 0 ; i != cacheSize ; ++i)		{					// On vérifie qu'on ait bien une vertice valide (à l'initialisation du			// cache, chaque valeur est négative)			if (cacheIdx >= 0)			{				int vertIdx = cacheIdx;								// Pour chaque triangle				for (std::set<std::size_t>::iterator iter = vertData.at(vertIdx).remainingTriangles.begin() ;					  iter != vertData.at(vertIdx).remainingTriangles.end() ; ++iter)				{					if (triData.at(*iter).score > bestScore)					{						bestScore = triData.at(*iter).score;						bestIdx = *iter;											}				}			}		}		// On recherche également parmi les trois vertices qui sont parties juste		// avant	}	return true;}


I know it's a big piece of code, but please, help me, because if you don't I think my computer will die :'(. For me, there aren't any errors...
It appears that after adding some triangles, any of the vertices is the cache have remaining triangles, so bestScore is 0.0f and bestIdx = -1, but after, it checked for ALL the triangle, so it shouldn't crash...

You can test it by loading a mesh model...
Bakura
Bakura
Hi !

Thanks for checking the code. I found some new things. If I set a cache size of around 10000, it works, but of course it takes a loooong time and it's not optimal !

I've found that the error is about my cache, but, as you will see, it's unbelievable ! First, here is the code of my implementation (the same as above) :

// Pour un cache de type deque
[source="cpp]template <typename Value_Type>class LRUCacheDeq{	public:		LRUCacheDeq (const std::size_t maxSize)			: maxSize (maxSize)		{		}			private:		// Quelques typedefs pour faciliter le travail...		typedef typename std::deque<Value_Type>::iterator deq_iterator;		typedef typename std::deque<Value_Type>::const_iterator deq_const_iterator;				// Taille maximale du cache		std::size_t maxSize; 		// Les données en elle-même		std::deque<Value_Type> LRUList; 	public:		// On cherche un élément		deq_iterator find (const Value_Type & value)		{			// On recherche l'élément			deq_iterator deq_iter = std::find (LRUList.begin(), LRUList.end(),														  value);						// Si aucun élément n'a été trouvé, on retourne l'itérateur vers la fin			if (deq_iter == LRUList.end())				return deq_iter; 			// Sinon, on replace l'élément au début de la liste			LRUList.push_front (*deq_iter);			LRUList.erase (deq_iter); 			// On renvoit l'élément			return (LRUList.begin());		} 		// Fonction pour insérer un élément		Value_Type & insert (const Value_Type & value)		{			// On recherche si l'élément est déjà dans la liste			deq_iterator deq_iter = find (value);						// Si l'élément n'est pas encore présent, on l'ajoute à la fin de la liste.			// S'il est présent, il sera automatiquement ajouté au début (MRU)			if (deq_iter == LRUList.end())				LRUList.push_front (value); 			// On vérifie si on ne dépasse pas la taille maximale du cache			if (LRUList.size() > maxSize)			{				// On récupère la valeur de l'élément qui va être supprimé				Value_Type & val = LRUList.back(); 				// On supprime le dernier élément				LRUList.pop_back(); 				// On renvoit l'élément				return val;			} 			// Sinon, on retourne l'élément qui vient d'être ajouté			return (LRUList.front());		} 		Value_Type & operator[] (const std::size_t idx)		{			assert (idx < maxSize);						return LRUList[idx];		}					// On affiche les éléments dans l'ordre, du plus récent au plus ancien		void Write () const		{			for (deq_const_iterator deq_const_iter = LRUList.begin() ; 				  deq_const_iter != LRUList.end() ; ++deq_const_iter)			{				std::cout << *deq_const_iter << std::endl;			}		}			};


Nwo, if I run only that code (nothing else, just that) :

[source="cpp"]LRUCacheDeq<std::size_t> cache (31); for (std::size_t i = 0 ; i != 31 ; ++i)	cache.insert (i);	cache.insert (30);


so with a cache size of 31 (the last element is 30, and I add another 30), it works perfectly well. The same with that code :

[source="cpp"]LRUCacheDeq<std::size_t> cache (33); for (std::size_t i = 0 ; i != 33 ; ++i)	cache.insert (i);	cache.insert (32);


The same with a cache size of 30. But with that code :

[source="cpp"]LRUCacheDeq<std::size_t> cache (32); for (std::size_t i = 0 ; i != 32 ; ++i)   cache.insert (i);	cache.insert (31);


so a cache size of 32, it crashes :|. Yes, it's completely unbelievable ! I don't understand NOTHING about that !
Bakura
Bakura
Hi !

Thanks for checking the code. I found some new things. If I set a cache size of around 10000, it works, but of course it takes a loooong time and it's not optimal !

I've found that the error is about my cache, but, as you will see, it's unbelievable ! First, here is the code of my implementation (the same as above) :

// Pour un cache de type deque
[source="cpp]template <typename Value_Type>class LRUCacheDeq{	public:		LRUCacheDeq (const std::size_t maxSize)			: maxSize (maxSize)		{		}			private:		// Quelques typedefs pour faciliter le travail...		typedef typename std::deque<Value_Type>::iterator deq_iterator;		typedef typename std::deque<Value_Type>::const_iterator deq_const_iterator;				// Taille maximale du cache		std::size_t maxSize; 		// Les données en elle-même		std::deque<Value_Type> LRUList; 	public:		// On cherche un élément		deq_iterator find (const Value_Type & value)		{			// On recherche l'élément			deq_iterator deq_iter = std::find (LRUList.begin(), LRUList.end(),														  value);						// Si aucun élément n'a été trouvé, on retourne l'itérateur vers la fin			if (deq_iter == LRUList.end())				return deq_iter; 			// Sinon, on replace l'élément au début de la liste			LRUList.push_front (*deq_iter);			LRUList.erase (deq_iter); 			// On renvoit l'élément			return (LRUList.begin());		} 		// Fonction pour insérer un élément		Value_Type & insert (const Value_Type & value)		{			// On recherche si l'élément est déjà dans la liste			deq_iterator deq_iter = find (value);						// Si l'élément n'est pas encore présent, on l'ajoute à la fin de la liste.			// S'il est présent, il sera automatiquement ajouté au début (MRU)			if (deq_iter == LRUList.end())				LRUList.push_front (value); 			// On vérifie si on ne dépasse pas la taille maximale du cache			if (LRUList.size() > maxSize)			{				// On récupère la valeur de l'élément qui va être supprimé				Value_Type & val = LRUList.back(); 				// On supprime le dernier élément				LRUList.pop_back(); 				// On renvoit l'élément				return val;			} 			// Sinon, on retourne l'élément qui vient d'être ajouté			return (LRUList.front());		} 		Value_Type & operator[] (const std::size_t idx)		{			assert (idx < maxSize);						return LRUList[idx];		}					// On affiche les éléments dans l'ordre, du plus récent au plus ancien		void Write () const		{			for (deq_const_iterator deq_const_iter = LRUList.begin() ; 				  deq_const_iter != LRUList.end() ; ++deq_const_iter)			{				std::cout << *deq_const_iter << std::endl;			}		}			};


Nwo, if I run only that code (nothing else, just that) :

[source="cpp"]LRUCacheDeq<std::size_t> cache (31); for (std::size_t i = 0 ; i != 31 ; ++i)	cache.insert (i);	cache.insert (30);


so with a cache size of 31 (the last element is 30, and I add another 30), it works perfectly well. The same with that code :

[source="cpp"]LRUCacheDeq<std::size_t> cache (33); for (std::size_t i = 0 ; i != 33 ; ++i)	cache.insert (i);	cache.insert (32);


The same with a cache size of 30. But with that code :

[source="cpp"]LRUCacheDeq<std::size_t> cache (32); for (std::size_t i = 0 ; i != 32 ; ++i)   cache.insert (i);	cache.insert (31);


so a cache size of 32, it crashes :|. Yes, it's completely unbelievable ! I don't understand NOTHING about that !
Bakura
Bakura
Ok, it works, with a list instead of a deque... Really don't understand why it doesn't work with a deque :/.

EDIT : it works now. The problem was that lines :

LRUList.push_front (*deq_iter);
LRUList.erase (deq_iter);

In fact, push_front invalidate the iterator. It works with that lines :

Value_type val = *deq_iter;
LRUList.erase (deq_iter);
LRUList.push_front (val);

Thanks for you :).

I test the code, now I've got an ACMR of 0.6402 with optimized indices instead of 0.9548 with the unoptimized version of indices :).

But I've got a question for you loneshock. I test with another small model of around 300 vertices. The ACMR is better with the optimized version : 1.2..., and unoptimizd : 1.6. But is it normal that some models has such an high ACMR ?

Even with a cache size of 1024 it's about the same score.

[Edited by - Bakura on October 11, 2007 3:08:06 PM]
lonesock
lonesock
I'm glad it's working now!

Quote:
Original post by Bakura
I test with another small model of around 300 vertices. The ACMR is better with the optimized version : 1.2..., and unoptimizd : 1.6. But is it normal that some models has such an high ACMR ?

Unfortunately, yes this is normal. You will have at least 1 cache miss per vertex (because at the start of rendering no vertices have been transformed). Imagine the case of a cube. It has 12 triangles, and because each face has its own normal vector there are 24 vertices (instead of only 8 which would require strange texturing and strange normal vectors to be shared for each vertex on the corners of the cube). So for this simple case, you have an ACMR of 2.0, even if the cache size is infinite.
Bakura
Bakura
Ok, thanks for the answers :)
Bakura
Bakura
Ok, thanks for the answers :)
snk_kid
snk_kid
this line:
Value_Type & val = LRUList.back(); // On supprime le dernier élémentLRUList.pop_back();


Is incorrect, you have invalidated your reference.

[Edited by - snk_kid on October 13, 2007 8:30:41 AM]
Bakura
Bakura
Thank you ! You're absolutely right :).
ndhb
ndhb

Hi all.

Lonesock, I have tried to implement your code in Java but I think the results are strange - I am getting the same ordering on the indices regardless what size vertex cache I provide.

CacheDecayPower = 1.5;
LastTriScore = 0.75;
ValenceBoostPower = 2.0;
ValenceBoostScale = 0.5;

This is the order BEFORE optimization:
unoptimized.gif

This is the order AFTER optimization (cache_size = 4):
cache_size_4.gif

Cache_size: 4 = Unoptimized (ACMR 1.11111120), Optimized (ACMR 1.27777780)
Cache_size: 8 = Unoptimized (ACMR 1.11111120), Optimized (ACMR 1.03703700)
Cache_size: 12 = Unoptimized (ACMR 1.11111120), Optimized (ACMR 0.86419755)
Cache_size: 16 = Unoptimized (ACMR 1.11111120), Optimized (ACMR 0.74691355)
Cache_size: 20 = Unoptimized (ACMR 0.61728394), Optimized (ACMR 0.71604940)
Cache_size: 24 = Unoptimized (ACMR 0.61728394), Optimized (ACMR 0.70370370)
Cache_size: 32 = Unoptimized (ACMR 0.61728394), Optimized (ACMR 0.65432100)
Cache_size: 1024 = Unoptimized (ACMR 0.61728394), Optimized (ACMR 0.65432100)

Regardless what cache_size I supply, the order AFTER optimization is the same (the last picture above is identical no matter what cache_size I optimize for).

1) It makes sense to me that the unoptimized ACMR drops suddenly when it can fit the "lines" of vertices into a cache size of 20.
2) It doesn't make sense to me that the optimization order isn't sensitive to the cache_size. For larger cache_size it should choose a different scheme than for lower cache_size, right?

Perhaps it's a bug in my "C++ > Java" translation but I tried to make it an exact duplicate of yours.

Could you please comment on these results?

kind regards,
Nicolai

ndhb
ndhb
Here is the Java translation:

/*	Jonathan Dummer	March 2, 2007		just some routines that may help with meshes		MIT License*/import java.util.*;//structures for the triangle order optimzationclass tri_data2 {	boolean added;	float score;	int[] verts = new int[3];} // classclass vert_data2 {	float score;	// std::set<unsigned int> remaining_tris;        // TreeSet<Integer> remaining_tris = new TreeSet<Integer>();	ArrayDeque<Integer> remaining_tris = new ArrayDeque<Integer>();	// HashSet<Integer> remaining_tris = new HashSet<Integer>();} // classpublic class MeshHelper2 {	public float FindVertexScore_CacheDecayPower = 1.5f;	public float FindVertexScore_LastTriScore = 0.75f;	public float FindVertexScore_ValenceBoostScale = 2.0f;	public float FindVertexScore_ValenceBoostPower = 0.5f;			// reorder the triangles, trying to minimize number of cache_misses	// per triangle (using Tom Forsyth's super simple method.)	public boolean optimize_vertex_cache_order(int[] tri_indices, int cache_size) {		if ((tri_indices.length < 3) || (tri_indices.length % 3 != 0) || (cache_size < 4)) {			return false;		} // if fail				int num_triangles = tri_indices.length / 3;				int num_vertices = 0;		for (int i = 0; i < num_triangles * 3; ++i) {			if (tri_indices > num_vertices) {				num_vertices = tri_indices;			} // if		} // for		++num_vertices;				// size of the optimization cache		float[] cache_score = new float[cache_size + 3];		for (int i = 0; i < cache_size + 3; ++i)			cache_score = FindVertexScore_LastTriScore;		int[] cache_idx = new int[cache_size + 3]; 		for (int i = 0; i < cache_size + 3; ++i)			cache_idx = -1;		int[] grow_cache_idx = new int[cache_size + 3];		for (int i = 0; i < cache_size + 3; ++i)			grow_cache_idx = -1;				for (int i = 3; i < cache_size; ++i)			cache_score = (float)(Math.pow( (cache_size - i) / (cache_size - 3.0), FindVertexScore_CacheDecayPower));		for(int i = 0; i < 3; ++i)			cache_score[cache_size + i] = 0.0f;		// how many tris do we need to add?		int tris_left = num_triangles;				// add all verts and tris to the lists		tri_data2[] t = new tri_data2[num_triangles];		vert_data2[] v = new vert_data2[num_vertices];				// initialize vertices		for (int i = 0; i < num_vertices; ++i) {			// initialize this vert			v = new vert_data2();			v.score = 0.0f;			v.remaining_tris.clear();		} // for				// initialize triangles		for (int i = 0; i < num_triangles; ++i) {			// set up this tri			t = new tri_data2();			t.added = false;			t.score = 0.0f;			t.verts[0] = tri_indices[i * 3 + 0];			t.verts[1] = tri_indices[i * 3 + 1];			t.verts[2] = tri_indices[i * 3 + 2];			// and add this tri index to each of it's verts			v[tri_indices[i * 3 + 0]].remaining_tris.add(i);			v[tri_indices[i * 3 + 1]].remaining_tris.add(i);			v[tri_indices[i * 3 + 2]].remaining_tris.add(i);		} // for all triangles						// now initialize all the scores for the vertices		for (int i = 0; i < num_vertices; ++i) {			// none of them are in the index yet, just use their valence score			v.score = (float)(Math.pow(v.remaining_tris.size(), -FindVertexScore_ValenceBoostPower) * FindVertexScore_ValenceBoostScale);		} // for				// and the triangles' scores		float best_score = 0.0f;		int best_idx = -1;		for (int i = 0; i < num_triangles; ++i) {			t.score = v[t.verts[0]].score + v[t.verts[1]].score + v[t.verts[2]].score;					if( t.score > best_score ) {				best_score = t.score;				best_idx = i;			} // if triangle score is better than current best_score		} // for all triangles				// now keep adding triangles		while (tris_left > 0) {//			System.out.print("Best Triangle (index=" + best_idx + ", score=" + best_score + ") ");//			System.out.println("(" + t.get(best_idx).verts[0] + ", " + t.get(best_idx).verts[1] + ", " + t.get(best_idx).verts[2] + ") (" + v.get(t.get(best_idx).verts[0]).score + ", " + v.get(t.get(best_idx).verts[1]).score + ", " + v.get(t.get(best_idx).verts[2]).score + ")");//			// scan all tris if the best score is suspicious			if (best_score < 0.01f) {//				System.err.println("low score looks suspicious...re-checking!");				best_score = 0.0f;				best_idx = -1;				for (int i = 0; i < num_triangles; ++i) {					if( !t.added ) {						if( t.score > best_score ) {							best_score = t.score;							best_idx = i;						} // if triangle score is better than current best_score					} // if not added				} // for all triangles			} // if best_score too low			if (best_idx < 0 ) {				// not good				System.err.println("triangle order optimizer failed with " + tris_left + " out of " + num_triangles + " left for processing.");				tris_left = 0; // ndhb: to stop while loop				System.err.println("Your stupid triangle re-organizing code isn't working!!" );			} else {				// add in this tri				int a = t[best_idx].verts[0];				int b = t[best_idx].verts[1];				int c = t[best_idx].verts[2];				// put this tri back into circulation				tri_indices[(num_triangles - tris_left) * 3 + 0] = a;				tri_indices[(num_triangles - tris_left) * 3 + 1] = b;				tri_indices[(num_triangles - tris_left) * 3 + 2] = c;				// remove this tri from the association of each of the verts				for (int i = 0; i < 3; ++i) {					v[t[best_idx].verts].remaining_tris.remove(best_idx);				} // for 3 vertices in triangle with best score				t[best_idx].added = true;				--tris_left;//				System.out.println(best_idx + ", " + best_score);				// put these 3 verts at the top of the LRU list				grow_cache_idx[0] = a;				grow_cache_idx[1] = b;				grow_cache_idx[2] = c;				int idx = 3;				for (int i = 0; i < cache_size; ++i) {					// clear out my growing cache					grow_cache_idx[i+3] = -1;					if ((cache_idx != a) && (cache_idx != b) && (cache_idx != c)) {						grow_cache_idx[idx++] = cache_idx;					} // if not in?				} // for cache entries				cache_idx = grow_cache_idx;				// update the weights				// (cache_size+3 because I want to update the triangles whose				// vertices fell out of the cache as well.)				for (int i = 0; i < cache_size + 3; ++i ) {					if (cache_idx >= 0) {						idx = cache_idx;						// store the old score						float old_score = v[idx].score;						// get the new score						float new_score = cache_score + FindVertexScore_ValenceBoostScale * (float)(Math.pow(v[idx].remaining_tris.size(), -FindVertexScore_ValenceBoostPower));						v[idx].score = new_score;												// now update all remaining linked triangles!						Iterator<Integer> it = v[idx].remaining_tris.iterator();						while (it.hasNext()) {							int iteratorIndex = it.next();							t[iteratorIndex].score += new_score - old_score;						} // while iterator has more					} // if cache index				} // for all cache entries?!				// search for the next best tri				best_score = 0.0f;				best_idx = -1;				for (int i = 0; i < cache_size; ++i) {					if (cache_idx >= 0) {						idx = cache_idx;						// is one of these triangles the best?						Iterator<Integer> it = v[idx].remaining_tris.iterator();						while (it.hasNext()) {							int iteratorIndex = it.next();							if (t[iteratorIndex].score > best_score) {								best_score = t[iteratorIndex].score;								best_idx = iteratorIndex;							} // if better						} // while has more					} // if cache index				} // for all cache entries			} // else add best triangle		} // while tris_left		return true;	} // method		// return the number of cache_misses per triangle (lower is better)	public float calculate_average_cache_miss_ratio(int[] tri_indices, int cache_size) {		if (tri_indices.length <= cache_size) {			System.err.println("Warning: The cache (size " + cache_size + ") can hold all indices (" + tri_indices.length + "). There are no cache misses.");			return -1.0f;		} // if		int[] cache_idx = new int[cache_size];		for (int i = 0; i < cache_idx.length; i++)			cache_idx = -1;		int cache_ptr = 0;		int cache_misses = 0;		for (int i = 0; i < tri_indices.length; ++i) {			// is the newest vertex in the ring buffer (FIFO)?			boolean cache_hit = false;			for (int j = 0; j < cache_size; ++j) {				// if this cache entry == the index, then cache_hit = true				cache_hit |= (cache_idx[j] == tri_indices);			} // for all cache entries			if (!cache_hit) {				// store the newest vertex ID in the cache				cache_idx[cache_ptr] = tri_indices;				// move to the next sopt (ring buffer FIFO)				cache_ptr = (cache_ptr + 1) % cache_size;				// count'em				++cache_misses;			} // if cache miss (not cache hit)		} // for all tri_indices//		cache misses per triangle		return cache_misses / (tri_indices.length / 3.0f);	} // method


I set up an array of indices and call it like this:
int cache_size = 32;			MeshHelper2 mh2 = new MeshHelper2();			mh2.FindVertexScore_CacheDecayPower = 1.5f;			mh2.FindVertexScore_LastTriScore = 0.75f;			mh2.FindVertexScore_ValenceBoostPower = 2.0f;			mh2.FindVertexScore_ValenceBoostScale = 0.5f;					System.out.println("Unoptimized Indices (ACMR " + mh2.calculate_average_cache_miss_ratio(indices, cache_size) + ")");			System.out.println(java.util.Arrays.toString(indices));						System.out.println("Running optimize_vertex_cache_order...");							mh2.optimize_vertex_cache_order(indices, cache_size);						System.out.println("Optimized Indices (ACMR " + mh2.calculate_average_cache_miss_ratio(indices, cache_size) + ")");			System.out.println(java.util.Arrays.toString(indices));


[Edited by - ndhb on October 22, 2007 5:37:10 PM]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.