123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // 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.
- //-----------------------------------------------------------------------------
- #include "gfx/util/triListOpt.h"
- #include "core/frameAllocator.h"
- #include "platform/profiler.h"
- #include "math/mMathFn.h"
- namespace TriListOpt
- {
- void OptimizeTriangleOrdering(const dsize_t numVerts, const dsize_t numIndices, const U32 *indices, IndexType *outIndices)
- {
- PROFILE_SCOPE(TriListOpt_OptimizeTriangleOrdering);
- if(numVerts == 0 || numIndices == 0)
- {
- dCopyArray(outIndices, indices, numIndices);
- return;
- }
- const U32 NumPrimitives = numIndices / 3;
- AssertFatal(NumPrimitives == U32(mFloor(numIndices / 3.0f)), "Number of indicies not divisible by 3, not a good triangle list.");
- //
- // Step 1: Run through the data, and initialize
- //
- FrameTemp<VertData> vertexData(numVerts);
- FrameTemp<TriData> triangleData(NumPrimitives);
- U32 curIdx = 0;
- for(S32 tri = 0; tri < NumPrimitives; tri++)
- {
- TriData &curTri = triangleData[tri];
- for(S32 c = 0; c < 3; c++)
- {
- const U32 &curVIdx = indices[curIdx];
- AssertFatal(curVIdx < numVerts, "Out of range index.");
- // Add this vert to the list of verts that define the triangle
- curTri.vertIdx[c] = curVIdx;
- VertData &curVert = vertexData[curVIdx];
- // Increment the number of triangles that reference this vertex
- curVert.numUnaddedReferences++;
- curIdx++;
- }
- }
- // Allocate per-vertex triangle lists, and calculate the starting score of
- // each of the verts
- for(S32 v = 0; v < numVerts; v++)
- {
- VertData &curVert = vertexData[v];
- curVert.triIndex = new S32[curVert.numUnaddedReferences];
- curVert.score = FindVertexScore::score(curVert);
- }
- // These variables will be used later, but need to be declared now
- S32 nextNextBestTriIdx = -1, nextBestTriIdx = -1;
- F32 nextNextBestTriScore = -1.0f, nextBestTriScore = -1.0f;
- #define _VALIDATE_TRI_IDX(idx) if(idx > -1) { AssertFatal(idx < NumPrimitives, "Out of range triangle index."); AssertFatal(!triangleData[idx].isInList, "Triangle already in list, bad."); }
- #define _CHECK_NEXT_NEXT_BEST(score, idx) { if(score > nextNextBestTriScore) { nextNextBestTriIdx = idx; nextNextBestTriScore = score; } }
- #define _CHECK_NEXT_BEST(score, idx) { if(score > nextBestTriScore) { _CHECK_NEXT_NEXT_BEST(nextBestTriScore, nextBestTriIdx); nextBestTriIdx = idx; nextBestTriScore = score; } _VALIDATE_TRI_IDX(nextBestTriIdx); }
- // Fill-in per-vertex triangle lists, and sum the scores of each vertex used
- // per-triangle, to get the starting triangle score
- curIdx = 0;
- for(S32 tri = 0; tri < NumPrimitives; tri++)
- {
- TriData &curTri = triangleData[tri];
- for(S32 c = 0; c < 3; c++)
- {
- const U32 &curVIdx = indices[curIdx];
- AssertFatal(curVIdx < numVerts, "Out of range index.");
- VertData &curVert = vertexData[curVIdx];
- // Add triangle to triangle list
- curVert.triIndex[curVert.numReferences++] = tri;
- // Add vertex score to triangle score
- curTri.score += curVert.score;
- curIdx++;
- }
- // This will pick the first triangle to add to the list in 'Step 2'
- _CHECK_NEXT_BEST(curTri.score, tri);
- _CHECK_NEXT_NEXT_BEST(curTri.score, tri);
- }
- //
- // Step 2: Start emitting triangles...this is the emit loop
- //
- LRUCacheModel lruCache;
- for(S32 outIdx = 0; outIdx < numIndices; /* this space intentionally left blank */ )
- {
- // If there is no next best triangle, than search for the next highest
- // scored triangle that isn't in the list already
- if(nextBestTriIdx < 0)
- {
- // TODO: Something better than linear performance here...
- nextBestTriScore = nextNextBestTriScore = -1.0f;
- nextBestTriIdx = nextNextBestTriIdx = -1;
- for(S32 tri = 0; tri < NumPrimitives; tri++)
- {
- TriData &curTri = triangleData[tri];
- if(!curTri.isInList)
- {
- _CHECK_NEXT_BEST(curTri.score, tri);
- _CHECK_NEXT_NEXT_BEST(curTri.score, tri);
- }
- }
- }
- AssertFatal(nextBestTriIdx > -1, "Ran out of 'nextBestTriangle' before I ran out of indices...not good.");
- // Emit the next best triangle
- TriData &nextBestTri = triangleData[nextBestTriIdx];
- AssertFatal(!nextBestTri.isInList, "Next best triangle already in list, this is no good.");
- for(S32 i = 0; i < 3; i++)
- {
- // Emit index
- outIndices[outIdx++] = IndexType(nextBestTri.vertIdx[i]);
- // Update the list of triangles on the vert
- VertData &curVert = vertexData[nextBestTri.vertIdx[i]];
- curVert.numUnaddedReferences--;
- for(S32 t = 0; t < curVert.numReferences; t++)
- {
- if(curVert.triIndex[t] == nextBestTriIdx)
- {
- curVert.triIndex[t] = -1;
- break;
- }
- }
- // Update cache
- lruCache.useVertex(nextBestTri.vertIdx[i], &curVert);
- }
- nextBestTri.isInList = true;
- // Enforce cache size, this will update the cache position of all verts
- // still in the cache. It will also update the score of the verts in the
- // cache, and give back a list of triangle indicies that need updating.
- Vector<U32> trisToUpdate;
- lruCache.enforceSize(MaxSizeVertexCache, trisToUpdate);
- // Now update scores for triangles that need updates, and find the new best
- // triangle score/index
- nextBestTriIdx = -1;
- nextBestTriScore = -1.0f;
- for(Vector<U32>::iterator itr = trisToUpdate.begin(); itr != trisToUpdate.end(); itr++)
- {
- TriData &tri = triangleData[*itr];
- // If this triangle isn't already emitted, re-score it
- if(!tri.isInList)
- {
- tri.score = 0.0f;
- for(S32 i = 0; i < 3; i++)
- tri.score += vertexData[tri.vertIdx[i]].score;
- _CHECK_NEXT_BEST(tri.score, *itr);
- _CHECK_NEXT_NEXT_BEST(tri.score, *itr);
- }
- }
- // If there was no love finding a good triangle, than see if there is a
- // next-next-best triangle, and if there isn't one of those...well than
- // I guess we have to find one next time
- if(nextBestTriIdx < 0 && nextNextBestTriIdx > -1)
- {
- if(!triangleData[nextNextBestTriIdx].isInList)
- {
- nextBestTriIdx = nextNextBestTriIdx;
- nextBestTriScore = nextNextBestTriScore;
- _VALIDATE_TRI_IDX(nextNextBestTriIdx);
- }
- // Nuke the next-next best
- nextNextBestTriIdx = -1;
- nextNextBestTriScore = -1.0f;
- }
- // Validate triangle we are marking as next-best
- _VALIDATE_TRI_IDX(nextBestTriIdx);
- }
- #undef _CHECK_NEXT_BEST
- #undef _CHECK_NEXT_NEXT_BEST
- #undef _VALIDATE_TRI_IDX
- // FrameTemp will call destructInPlace to clean up vertex lists
- }
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- LRUCacheModel::~LRUCacheModel()
- {
- for( LRUCacheEntry* entry = mCacheHead; entry != NULL; )
- {
- LRUCacheEntry* next = entry->next;
- delete entry;
- entry = next;
- }
- }
- //------------------------------------------------------------------------------
- void LRUCacheModel::useVertex(const U32 vIdx, VertData *vData)
- {
- LRUCacheEntry *search = mCacheHead;
- LRUCacheEntry *last = NULL;
- while(search != NULL)
- {
- if(search->vIdx == vIdx)
- break;
- last = search;
- search = search->next;
- }
- // If this vertex wasn't found in the cache, create a new entry
- if(search == NULL)
- {
- search = new LRUCacheEntry;
- search->vIdx = vIdx;
- search->vData = vData;
- }
- if(search != mCacheHead)
- {
- // Unlink the entry from the linked list
- if(last)
- last->next = search->next;
- // Vertex that got passed in is now at the head of the cache
- search->next = mCacheHead;
- mCacheHead = search;
- }
- }
- //------------------------------------------------------------------------------
- void LRUCacheModel::enforceSize(const dsize_t maxSize, Vector<U32> &outTrisToUpdate)
- {
- // Clear list of triangles to update scores for
- outTrisToUpdate.clear();
- dsize_t length = 0;
- LRUCacheEntry *next = mCacheHead;
- LRUCacheEntry *last = NULL;
-
- // Run through list, up to the max size
- while(next != NULL && length < MaxSizeVertexCache)
- {
- VertData &vData = *next->vData;
- // Update cache position on verts still in cache
- vData.cachePosition = length++;
- for(S32 i = 0; i < vData.numReferences; i++)
- {
- const S32 &triIdx = vData.triIndex[i];
- if(triIdx > -1)
- {
- S32 j = 0;
- for(; j < outTrisToUpdate.size(); j++)
- if(outTrisToUpdate[j] == triIdx)
- break;
- if(j == outTrisToUpdate.size())
- outTrisToUpdate.push_back(triIdx);
- }
- }
- // Update score
- vData.score = FindVertexScore::score(vData);
- last = next;
- next = next->next;
- }
- // NULL out the pointer to the next entry on the last valid entry
- last->next = NULL;
- // If next != NULL, than we need to prune entries from the tail of the cache
- while(next != NULL)
- {
- // Update cache position on verts which are going to get tossed from cache
- next->vData->cachePosition = -1;
- LRUCacheEntry *curEntry = next;
- next = next->next;
- delete curEntry;
- }
- }
- //------------------------------------------------------------------------------
- S32 LRUCacheModel::getCachePosition(const U32 vIdx)
- {
- dsize_t length = 0;
- LRUCacheEntry *next = mCacheHead;
- while(next != NULL)
- {
- if(next->vIdx == vIdx)
- return length;
-
- length++;
- next = next->next;
- }
- return -1;
- }
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- // http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
- namespace FindVertexScore
- {
- F32 score(const VertData &vertexData)
- {
- // If nobody needs this vertex, return -1.0
- if(vertexData.numUnaddedReferences < 1)
- return -1.0f;
- F32 Score = 0.0f;
- if(vertexData.cachePosition < 0)
- {
- // Vertex is not in FIFO cache - no score.
- }
- else
- {
- if(vertexData.cachePosition < 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
- {
- AssertFatal(vertexData.cachePosition < MaxSizeVertexCache, "Out of range cache position for vertex");
- // Points for being high in the cache.
- const F32 Scaler = 1.0f / (MaxSizeVertexCache - 3);
- Score = 1.0f - (vertexData.cachePosition - 3) * Scaler;
- Score = mPow(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.
- F32 ValenceBoost = mPow(vertexData.numUnaddedReferences, -FindVertexScore::ValenceBoostPower);
- Score += FindVertexScore::ValenceBoostScale * ValenceBoost;
- return Score;
- }
- } // namspace FindVertexScore
- } // namespace TriListOpt
|