DragonNest/Client/ActorEternity/AtgStrip.cpp
Cussrro 47f7895977 Revert "修复编码问题"
This reverts commit 9e69c01767.
2024-12-21 10:04:04 +08:00

1522 lines
48 KiB
C++

//--------------------------------------------------------------------------------------
// AtgStrip.cpp
//
// Tristrip routines (which convert a mesh into a list of optimized
// triangle strips).
//
// Xbox Advanced Technology Group.
// Copyright (C) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "stdafx.h"
#include "AtgStrip.h"
// Cache size to optimize for.
const int CACHE_SIZE = 16;
// Estimated length of the vertex shader to use when comparing the cost of
// different stripifications.
const int SHADER_CYCLES = 20;
//--------------------------------------------------------------------------------------
// Structs
//--------------------------------------------------------------------------------------
typedef WORD (*TRIANGLELIST)[3];
struct TRIANGLEINFO
{
INT neighbortri[3]; // Triangle sharing edge (i,i+1), or -1
INT neighboredge[3]; // Edge (j,j+1) of neighboring triangle.
};
//--------------------------------------------------------------------------------------
// Name: struct CStrip
// Desc: A single triangle strip. After a mesh is stripified, it typically is
// composed of several of these.
//--------------------------------------------------------------------------------------
struct CStrip
{
BOOL m_bIsStripCW;
DWORD m_dwNumTriangles;
INT* m_pTriangles;
DWORD m_dwNumIndices;
WORD* m_pIndices;
DWORD m_dwNumNeighbors;
CStrip( DWORD dwNumTris, DWORD dwNumIndices )
{
m_dwNumTriangles = dwNumTris;
m_pTriangles = new int[ dwNumTris ];
m_dwNumIndices = dwNumIndices;
m_pIndices = new WORD[ dwNumIndices ];
}
~CStrip()
{
delete[] m_pTriangles;
delete[] m_pIndices;
}
};
//--------------------------------------------------------------------------------------
// Name: struct CSubStrip
// Desc: A structure that specifies part of a strip in a striplist.
//--------------------------------------------------------------------------------------
struct CSubStrip
{
INT m_iStrip; // Index into striplist.
INT m_iStart; // Starting triangle index
INT m_iEnd; // Ending triangle index.
};
//--------------------------------------------------------------------------------------
// Name: struct CStripList
// Desc: A list of triangle strips.
//--------------------------------------------------------------------------------------
struct CStripList
{
CStrip** m_pStrips;
DWORD m_dwNumStrips;
CStrip** begin()
{
return (m_dwNumStrips) ? &m_pStrips[0] : NULL;
}
VOID RemoveStripFromList( CStrip** pStrip )
{
for( DWORD i=0; i<m_dwNumStrips; i++ )
{
if( &m_pStrips[i] == pStrip )
{
delete m_pStrips[i];
m_dwNumStrips--;
while( i < m_dwNumStrips )
{
m_pStrips[i] = m_pStrips[i+1];
i++;
}
break;
}
}
}
VOID RemoveFirst()
{
RemoveStripFromList( &m_pStrips[0] );
}
VOID AddStripToList( CStrip* pStrip )
{
m_pStrips[m_dwNumStrips++] = pStrip;
}
CStripList(DWORD dwMaxSize)
{
m_pStrips = new CStrip*[dwMaxSize];
m_dwNumStrips = 0L;
}
~CStripList()
{
for( DWORD i=0; i<m_dwNumStrips; i++ )
{
delete m_pStrips[i];
}
delete[] m_pStrips;
}
};
//--------------------------------------------------------------------------------------
// Name: struct SortEntry
// Desc: Class used to vertices for locality of access.
//--------------------------------------------------------------------------------------
struct SortEntry
{
INT iFirstUsed;
INT iOrigIndex;
// Define the < operator, which is needed for the STL sort() routine.
BOOL operator<( const SortEntry& rhs ) { return iFirstUsed < rhs.iFirstUsed; }
};
//--------------------------------------------------------------------------------------
// Name: class CVertCache
// Desc: Vertex cache class
//--------------------------------------------------------------------------------------
class CVertCache
{
public:
CVertCache() { Reset(); }
~CVertCache() {};
// Reset cache
void Reset()
{
m_iCachePtr = 0;
m_cachehits = 0;
memset( m_rgCache, 0xff, sizeof(m_rgCache) );
}
// Add vertindex to cache
int Add(int strip, int vertindex);
// Check if a vert is in the cache.
BOOL IsCached(int vertindex)
{
for( int iCache = 0; iCache < CACHE_SIZE; iCache++ )
{
if( vertindex == m_rgCache[iCache] )
{
// Item is in the cache
return TRUE;
}
}
return FALSE;
}
// Check if a vert uses one of the last two cached verts.
BOOL IsOld(int vertindex)
{
if( vertindex == m_rgCache[m_iCachePtr] )
{
// Item is in the cache
return TRUE;
}
return FALSE;
}
int NumCacheHits() const
{
return m_cachehits;
}
private:
int m_cachehits; // current # of cache hits
WORD m_rgCache[CACHE_SIZE]; // vertex cache
int m_rgCacheStrip[CACHE_SIZE]; // strip # which added or re-used vert
int m_iCachePtr; // FIFO ptr
bool m_bReUsed[CACHE_SIZE]; // true if vert was re-used.
};
//--------------------------------------------------------------------------------------
// Name: class CStripper
// Desc: Main stripper class
//--------------------------------------------------------------------------------------
class CStripper
{
public:
int m_dwNumTris; // Number of tris
TRIANGLELIST m_pTriangles; // Trilist
TRIANGLEINFO* m_pTriInfo; // Tri edge, neighbor info
int* m_pUsed; // Tri used flag
// Ctors/dtors
CStripper( int dwNumtris, TRIANGLELIST pTriangles );
~CStripper();
// Initialize tri info
VOID InitTriangleInfo( int tri, int vert );
// Get maximum length strip from tri/vert
int CreateStrip( int tri, int vert, int maxlen, int *pswaps,
BOOL bLookAhead, BOOL bNonSequential, BOOL bStartCW,
int* pTris );
// Turn a list of triangles into indices.
int CreateIndices( BOOL bStartCW, int iNumTriangles, int* pTriangles,
WORD* pStripVerts );
// Stripify entire mesh
VOID BuildStrips( CStripList* pStripList, int maxlen, BOOL bLookAhead,
BOOL bNonSequential, BOOL bSwapOrientation );
// Blast strip indices to ppstripindices
int CreateManyStrips( CStripList *pstriplist, WORD **ppstripindices );
int CreateLongStrip( CStripList *pstriplist, WORD **ppstripindices );
int CreateCachedStrip( CStripList *pstriplist, WORD **ppstripindices );
// Find the best cached strip.
CStrip** FindBestCachedStrip( CStripList* pStripList, const CVertCache &VertexCache );
int GetNeighborCount( int tri )
{
int count = 0;
for( int vert = 0; vert < 3; vert++ )
{
int neighbortri = m_pTriInfo[tri].neighbortri[vert];
count += (neighbortri != -1) && !m_pUsed[neighbortri];
}
return count;
}
};
//--------------------------------------------------------------------------------------
// Name: CreateStrip()
// Desc: Get maximum length of strip starting at tri/vert
//--------------------------------------------------------------------------------------
int CStripper::CreateStrip( int tri, int vert, int maxlen, int *pswaps,
BOOL bLookAhead, BOOL bNonSequential,
BOOL bStartCW, int* pTris )
{
*pswaps = 0;
// this guy has already been used?
if(m_pUsed[tri])
return 0;
// mark tri as used
m_pUsed[tri] = 1;
int swaps = 0;
// add first tri
int iNumTris = 0;
pTris[iNumTris++] = tri;
// toggle orientation
bStartCW = !bStartCW;
// get next tri information
int edge = (bStartCW ? vert + 2 : vert + 1) % 3;
int nexttri = m_pTriInfo[tri].neighbortri[edge];
int nextvert = m_pTriInfo[tri].neighboredge[edge];
// start building the strip until we run out of room or indices
for(int stripcount = 3; stripcount < maxlen; stripcount++)
{
// dead end?
if(nexttri == -1 || m_pUsed[nexttri])
break;
// move to next tri
tri = nexttri;
vert = nextvert;
// toggle orientation
bStartCW = !bStartCW;
// find the next natural edge
edge = (bStartCW ? vert + 2 : vert + 1) % 3;
nexttri = m_pTriInfo[tri].neighbortri[edge];
nextvert = m_pTriInfo[tri].neighboredge[edge];
BOOL bSwap = FALSE;
if(nexttri == -1 || m_pUsed[nexttri])
{
// if the next tri is a dead end - try swapping orientation
bSwap = TRUE;
}
else if( bLookAhead )
{
// try a swap and see who our new neighbor would be
int edgeswap = (bStartCW ? vert + 1 : vert + 2) % 3;
int nexttriswap = m_pTriInfo[tri].neighbortri[edgeswap];
int nextvertswap = m_pTriInfo[tri].neighboredge[edgeswap];
if( nexttriswap != -1 && !m_pUsed[nexttriswap] )
{
// if the swap neighbor has a lower count, change directions
if( GetNeighborCount(nexttriswap) < GetNeighborCount(nexttri) )
{
bSwap = TRUE;
}
else if( GetNeighborCount(nexttriswap) == GetNeighborCount(nexttri) )
{
// if they have the same number of neighbors - check their neighbors
edgeswap = (bStartCW ? nextvertswap + 2 : nextvertswap + 1) % 3;
nexttriswap = m_pTriInfo[nexttriswap].neighbortri[edgeswap];
int edge1 = (bStartCW ? nextvert + 1 : nextvert + 2) % 3;
int nexttri1 = m_pTriInfo[nexttri].neighbortri[edge1];
if( nexttri1 == -1 || m_pUsed[nexttri1] )
{
// natural winding order leads us to a dead end so turn
bSwap = TRUE;
}
else if( nexttriswap != -1 && !m_pUsed[nexttriswap] )
{
// check neighbor counts on both directions and swap if it's better
if( GetNeighborCount(nexttriswap) < GetNeighborCount(nexttri1) )
bSwap = TRUE;
}
}
}
}
if( bSwap && bNonSequential )
{
// we've been told to change directions so make sure we actually can
// and then add the swap vertex
int edgeswap = (bStartCW ? vert + 1 : vert + 2) % 3;
nexttri = m_pTriInfo[tri].neighbortri[edgeswap];
nextvert = m_pTriInfo[tri].neighboredge[edgeswap];
if( nexttri != -1 && !m_pUsed[nexttri] )
{
stripcount++;
swaps++;
bStartCW = !bStartCW;
}
}
pTris[iNumTris++] = tri;
// mark triangle as used
m_pUsed[tri] = 1;
}
// clear the used flags
for( int j = 0; j < iNumTris; j++ )
m_pUsed[pTris[j]] = 0;
// return swap count and striplen
*pswaps = swaps;
return iNumTris;
}
//--------------------------------------------------------------------------------------
// Name: CreateIndices()
// Desc: Make strip indices from triangle list.
//--------------------------------------------------------------------------------------
int CStripper::CreateIndices( BOOL bStartCW, int iNumTriangles, int* pTriangles, WORD* pStripVerts )
{
int stripcount = 0;
BOOL bCW = bStartCW;
int in_edge = -1;
for( int i = 0; i < iNumTriangles; i++ )
{
int out_edge;
int tri = pTriangles[i];
// get next tri information
if( i < iNumTriangles-1 )
{
int nexttri = pTriangles[i+1];
for( out_edge = 0; out_edge < 3; out_edge++ )
if( m_pTriInfo[tri].neighbortri[out_edge] == nexttri )
break;
}
else
{
out_edge = (bCW ? (in_edge + 1) : (in_edge + 2)) % 3;
}
if( i == 0 )
{
if( bCW )
{
pStripVerts[0] = m_pTriangles[tri][(out_edge + 2) % 3];
pStripVerts[1] = m_pTriangles[tri][(out_edge + 0) % 3];
pStripVerts[2] = m_pTriangles[tri][(out_edge + 1) % 3];
}
else
{
pStripVerts[0] = m_pTriangles[tri][(out_edge + 2) % 3];
pStripVerts[1] = m_pTriangles[tri][(out_edge + 1) % 3];
pStripVerts[2] = m_pTriangles[tri][(out_edge + 0) % 3];
}
stripcount = 3;
}
else
{
if( out_edge == (bCW ? (in_edge + 1) : (in_edge + 2)) % 3 )
{
// In order.
pStripVerts[stripcount++] = m_pTriangles[tri][(in_edge + 2) % 3];
}
else
{
// Swap.
if( bCW )
{
pStripVerts[stripcount++] = m_pTriangles[tri][(in_edge + 0) % 3];
pStripVerts[stripcount++] = m_pTriangles[tri][(in_edge + 2) % 3];
}
else
{
pStripVerts[stripcount++] = m_pTriangles[tri][(in_edge + 1) % 3];
pStripVerts[stripcount++] = m_pTriangles[tri][(in_edge + 2) % 3];
}
bCW = !bCW;
}
}
in_edge = m_pTriInfo[tri].neighboredge[out_edge];
bCW = !bCW;
}
return stripcount;
}
//--------------------------------------------------------------------------------------
// Name: FindBestStrip()
// Desc: Given a striplist and current cache state, pick the best next strip
//--------------------------------------------------------------------------------------
CStrip** FindBestStrip( CStripList* pStripList, const CVertCache &VertexCache )
{
if( 0 == pStripList->m_dwNumStrips )
return NULL;
CStrip** ppBestStrip = pStripList->begin();
DWORD dwBestStripLen = (*ppBestStrip)->m_dwNumIndices;
BOOL bStartCW = (*ppBestStrip)->m_bIsStripCW;
BOOL bBestStripFlipped = FALSE;
int MaxCacheHits = -1;
// Go through all the other strips looking for the best caching
for( DWORD i = 0; i < pStripList->m_dwNumStrips; i++ )
{
CStrip* pStrip = pStripList->m_pStrips[i];
DWORD dwStripLen = pStrip->m_dwNumIndices;
BOOL bStripFlipped = FALSE;
// Check cache if this strip is the same type as us (ie: cw/odd)
if( ( pStrip->m_bIsStripCW == bStartCW) && ( (dwBestStripLen & 0x1) == (dwStripLen & 0x1) ) )
{
// Copy current state of cache
CVertCache NewVertexCache = VertexCache;
// Figure out what this guy would do to our cache
for( DWORD ivert = 0; ivert < dwStripLen; ivert++ )
NewVertexCache.Add( 2, pStrip->m_pIndices[ivert] );
// For even length strips - see if we can get better cache hits
// if we reverse the vertex cache contents
if( !(dwStripLen & 0x1) )
{
// Create a copy of the vertex cache, with all vertices flipped
CVertCache FlippedVertexCache = VertexCache;
for( int ivert = pStrip->m_dwNumIndices-1; ivert >= 0; ivert-- )
FlippedVertexCache.Add( 2, pStrip->m_pIndices[ivert] );
// Accept the flipped cache if it gives us more cahce hits
if( FlippedVertexCache.NumCacheHits() > NewVertexCache.NumCacheHits() )
{
NewVertexCache = FlippedVertexCache;
bStripFlipped = TRUE;
}
}
// Record the best number of cache hits to date
int NumCacheHits = NewVertexCache.NumCacheHits() - VertexCache.NumCacheHits();
if( NumCacheHits > MaxCacheHits )
{
MaxCacheHits = NumCacheHits;
ppBestStrip = &pStripList->m_pStrips[i];
dwBestStripLen = dwStripLen;//? added by mikey
bBestStripFlipped = bStripFlipped;
}
}
}
if( bBestStripFlipped )
{
CStrip* pStrip = *ppBestStrip;
int first = 0;
int last = pStrip->m_dwNumIndices - 1;
while( first < last )
{
// Swap vertex indices
WORD temp = pStrip->m_pIndices[first];
pStrip->m_pIndices[first] = pStrip->m_pIndices[last];
pStrip->m_pIndices[last] = temp;
first++;
last--;
}
}
// Make sure we keep the list in order and always pull off
// the first dude.
if( ppBestStrip != pStripList->begin() )
{
// Swap strips
CStrip* temp = (*ppBestStrip);
(*ppBestStrip) = (*pStripList->begin());
(*pStripList->begin()) = temp;
}
return pStripList->begin();
}
//--------------------------------------------------------------------------------------
// Name: FindBestCachedStrip()
// Desc: Given a striplist and current cache state, pick the best next strip
//--------------------------------------------------------------------------------------
CStrip** CStripper::FindBestCachedStrip( CStripList* pStripList,
const CVertCache& VertexCache )
{
if( 0 == pStripList->m_dwNumStrips )
return NULL;
CStrip** ppBestStrip = pStripList->begin();
BOOL bBestStripFlipped = FALSE;
int MaxCacheHits = -1;
DWORD dwBestNeighborCount = (*ppBestStrip)->m_dwNumNeighbors;
// Go through all the other strips looking for the best caching
for( DWORD i = 0; i < pStripList->m_dwNumStrips; i++ )
{
CStrip* pStrip = pStripList->m_pStrips[i];
DWORD dwStripLen = pStrip->m_dwNumIndices;
BOOL bStripFlipped = FALSE;
// Copy current state of cache
CVertCache NewVertexCache = VertexCache;
// Figure out what this guy would do to our cache
for( DWORD ivert = 0; ivert < dwStripLen; ivert++ )
NewVertexCache.Add( 2, pStrip->m_pIndices[ivert] );
// See if we can get better cache hits if we reverse the order we draw
// the strip in.
{
// Create a copy of the vertex cache, with all vertices flipped
CVertCache FlippedVertexCache = VertexCache;
for( int ivert = pStrip->m_dwNumIndices-1; ivert >= 0; ivert-- )
FlippedVertexCache.Add( 2, pStrip->m_pIndices[ivert] );
// Accept the flipped cache if it gives us more cache hits
if( FlippedVertexCache.NumCacheHits() > NewVertexCache.NumCacheHits() )
{
NewVertexCache = FlippedVertexCache;
bStripFlipped = TRUE;
}
}
// Record the best number of cache hits to date
int NumCacheHits = NewVertexCache.NumCacheHits() - VertexCache.NumCacheHits();
if( NumCacheHits > MaxCacheHits )
{
MaxCacheHits = NumCacheHits;
ppBestStrip = &pStripList->m_pStrips[i];
bBestStripFlipped = bStripFlipped;
dwBestNeighborCount = pStripList->m_pStrips[i]->m_dwNumNeighbors;
}
else if ( NumCacheHits == MaxCacheHits &&
pStripList->m_pStrips[i]->m_dwNumNeighbors < dwBestNeighborCount )
{
ppBestStrip = &pStripList->m_pStrips[i];
bBestStripFlipped = bStripFlipped;
dwBestNeighborCount = pStripList->m_pStrips[i]->m_dwNumNeighbors;
}
}
if( bBestStripFlipped )
{
CStrip* pStrip = *ppBestStrip;
int first = 0;
int last = pStrip->m_dwNumIndices - 1;
while( first < last )
{
// Swap vertex indices
WORD temp = pStrip->m_pIndices[first];
pStrip->m_pIndices[first] = pStrip->m_pIndices[last];
pStrip->m_pIndices[last] = temp;
first++;
last--;
}
// We must also reverse the starting winding for odd length strips.
if( (pStrip->m_dwNumIndices & 0x1) )
{
pStrip->m_bIsStripCW = !pStrip->m_bIsStripCW;
}
}
// Make sure we keep the list in order and always pull off
// the first dude.
if( ppBestStrip != pStripList->begin() )
{
// Swap strips
CStrip* temp = (*ppBestStrip);
(*ppBestStrip) = (*pStripList->begin());
(*pStripList->begin()) = temp;
}
return pStripList->begin();
}
//--------------------------------------------------------------------------------------
// Name: CreateManyStrips()
// Desc: Don't merge the strips - just blast em into the stripbuffer one by one
// (useful for debugging)
//--------------------------------------------------------------------------------------
int CStripper::CreateManyStrips( CStripList* pStripList, WORD** ppStripIndices )
{
// Count the number of indices. Allow room for each of the strips size
// plus the final 0
DWORD dwIndexCount = pStripList->m_dwNumStrips + 1;
// We're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format
for( DWORD i = 0; i < pStripList->m_dwNumStrips; i++ )
{
// Add striplength plus potential degenerate to swap ccw --> cw
dwIndexCount += pStripList->m_pStrips[i]->m_dwNumIndices + 1;
}
// Alloc the space for all this stuff
WORD* pStripIndices = new WORD[dwIndexCount];
DWORD dwNumStripIndices = 0;
CVertCache VertexCache;
// Loop through all strips
CStrip** ppStrip = pStripList->begin();
while( pStripList->m_dwNumStrips > 0 )
{
CStrip* pStrip = *ppStrip;
if( !pStrip->m_bIsStripCW )
{
// Add an extra index if it's ccw
pStripIndices[dwNumStripIndices++] = (WORD)pStrip->m_dwNumIndices + 1;
pStripIndices[dwNumStripIndices++] = pStrip->m_pIndices[0];
}
else
{
// Add the strip length
pStripIndices[dwNumStripIndices++] = (WORD)pStrip->m_dwNumIndices;
}
// Add all the strip indices
for( DWORD i = 0; i < pStrip->m_dwNumIndices; i++)
{
pStripIndices[dwNumStripIndices++] = pStrip->m_pIndices[i];
VertexCache.Add( 1, pStrip->m_pIndices[i] );
}
// Free this guy and pop him off the list
pStripList->RemoveFirst();
// Get the next best strip
ppStrip = FindBestStrip( pStripList, VertexCache );
}
// Add terminating zero
pStripIndices[dwNumStripIndices++] = 0;
(*ppStripIndices) = pStripIndices;
return dwNumStripIndices;
}
//--------------------------------------------------------------------------------------
// Name: CreateLongStrip()
// Desc: Merge striplist into one big uberlist with (hopefully) optimal caching
//--------------------------------------------------------------------------------------
int CStripper::CreateLongStrip( CStripList* pStripList, WORD** ppwStripIndices )
{
// Allow room for one strip length plus a possible 3 extra indices per
// concatenated strip list plus the final 0
int dwIndexCount = (pStripList->m_dwNumStrips * 3) + 2;
// We're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format
for( DWORD i=0; i < pStripList->m_dwNumStrips; i ++ )
{
dwIndexCount += pStripList->m_pStrips[i]->m_dwNumIndices;
}
// Alloc the space for all this stuff
WORD* pStripIndices = new WORD[dwIndexCount];
int dwNumStripIndices = 0;
CVertCache VertexCache;
// Add first strip
CStrip** ppStrip = pStripList->begin();
CStrip* pStrip = *ppStrip;
// Note: first strip should be clock-wise
for( DWORD ivert = 0; ivert < pStrip->m_dwNumIndices; ivert++ )
{
pStripIndices[dwNumStripIndices++] = pStrip->m_pIndices[ivert];
VertexCache.Add( 1, pStrip->m_pIndices[ivert] );
}
// Kill first dude
pStripList->RemoveStripFromList( ppStrip );
// Add all the others
while( pStripList->m_dwNumStrips )
{
ppStrip = FindBestStrip( pStripList, VertexCache );
pStrip = *ppStrip;
WORD wLastVertex = pStripIndices[dwNumStripIndices - 1];
WORD wFirstVertex = pStrip->m_pIndices[0];
if( wFirstVertex != wLastVertex )
{
// Add degenerate from last strip
pStripIndices[dwNumStripIndices++] = wLastVertex;
// Add degenerate from our strip
pStripIndices[dwNumStripIndices++] = wFirstVertex;
}
// If we're not orientated correctly, we need to add a degenerate
if( pStrip->m_bIsStripCW != !(dwNumStripIndices & 0x1) )
{
// This shouldn't happen - we're currently trying very hard
// to keep everything oriented correctly.
pStripIndices[dwNumStripIndices++] = wFirstVertex;
}
// Add these verts
for( DWORD ivert = 0; ivert < pStrip->m_dwNumIndices; ivert++ )
{
pStripIndices[dwNumStripIndices++] = pStrip->m_pIndices[ivert];
VertexCache.Add( 1, pStrip->m_pIndices[ivert] );
}
// Free these guys
pStripList->RemoveStripFromList( ppStrip );
}
(*ppwStripIndices) = pStripIndices;
return dwNumStripIndices;
}
//--------------------------------------------------------------------------------------
// Name: CreateCachedStrip()
// Desc: Merge striplist into one big uberlist with (hopefully) optimal caching
//--------------------------------------------------------------------------------------
int CStripper::CreateCachedStrip( CStripList* pStripList, WORD** ppwStripIndices )
{
DWORD i;
WORD pTempVerts[CACHE_SIZE*4];
// Split up the strips into cache friendly pieces.
CStripList* pNewList = new CStripList(m_dwNumTris);
while( pStripList->m_dwNumStrips )
{
CStrip** ppStrip = pStripList->begin();
CStrip* pStrip = *ppStrip;
int start = 0;
int ssize = pStrip->m_dwNumTriangles;
do
{
int dsize = ssize;
if( dsize > CACHE_SIZE )
dsize = CACHE_SIZE;
int j = pNewList->m_dwNumStrips++;
// Create temp triangle list/index list.
int num_indices = CreateIndices( pStrip->m_bIsStripCW, dsize,
pStrip->m_pTriangles + start,
pTempVerts );
// Make new strip.
pNewList->m_pStrips[j] = new CStrip( dsize, num_indices );
pNewList->m_pStrips[j]->m_bIsStripCW = pStrip->m_bIsStripCW;
// Copy triangles.
memcpy( pNewList->m_pStrips[j]->m_pTriangles,
pStrip->m_pTriangles + start, dsize * sizeof(int) );
// Copy indices.
memcpy( pNewList->m_pStrips[j]->m_pIndices,
pTempVerts, num_indices * sizeof(WORD) );
start += dsize;
ssize -= dsize;
}
while( ssize > 0 );
pStripList->RemoveStripFromList( ppStrip );
}
// Count the number of adjacent triangles to each strip.
// an edge of the mesh.
for( i = 0; i < pNewList->m_dwNumStrips; i++ )
{
CStrip* pStrip = pNewList->m_pStrips[i];
DWORD count = 0;
for( DWORD j = 0; j < pStrip->m_dwNumTriangles; j++ )
{
// Count the number of neighbors.
for( int vert = 0; vert < 3; vert++ )
{
if (m_pTriInfo[pStrip->m_pTriangles[j]].neighbortri[vert] != -1)
count++;
}
}
pStrip->m_dwNumNeighbors = count;
}
// Should we remove/ignore very small strips?
// Allow room for one strip length plus a possible 3 extra indices per
// concatenated strip list plus the final 0
int dwIndexCount = (pNewList->m_dwNumStrips * 3) + 2;
// We're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format
for( i = 0; i < pNewList->m_dwNumStrips; i++ )
{
dwIndexCount += pNewList->m_pStrips[i]->m_dwNumIndices;
}
// Alloc the space for all this stuff
WORD* pStripIndices = new WORD[dwIndexCount];
DWORD dwNumStripIndices = 0;
CVertCache VertexCache;
// Add the strips
while( pNewList->m_dwNumStrips )
{
CStrip** ppStrip = FindBestCachedStrip( pNewList, VertexCache );
CStrip* pStrip = *ppStrip;
WORD wFirstVertex = pStrip->m_pIndices[0];
DWORD ivert = 0;
if (dwNumStripIndices > 0)
{
WORD wLastVertex = pStripIndices[dwNumStripIndices - 1];
assert( dwNumStripIndices > 2 );
if( wLastVertex == pStrip->m_pIndices[1] &&
pStripIndices[dwNumStripIndices - 2] == wFirstVertex &&
pStrip->m_bIsStripCW == !(dwNumStripIndices & 0x1) )
{
// We are re-stitching strips together, so skip the first two
// verts of this strip.
ivert = 2;
}
else if( wFirstVertex != wLastVertex )
{
// Add degenerate from last strip
pStripIndices[dwNumStripIndices++] = wLastVertex;
// Add degenerate from our strip
pStripIndices[dwNumStripIndices++] = wFirstVertex;
}
}
// If we're not orientated correctly, we need to add a degenerate
if( pStrip->m_bIsStripCW != !(dwNumStripIndices & 0x1) )
{
pStripIndices[dwNumStripIndices++] = wFirstVertex;
}
// Add these verts and update cache.
while( ivert < pStrip->m_dwNumIndices )
{
pStripIndices[dwNumStripIndices] = pStrip->m_pIndices[ivert];
VertexCache.Add( 1, pStrip->m_pIndices[ivert] );
dwNumStripIndices++;
ivert++;
}
// Free these guys
pNewList->RemoveStripFromList( ppStrip );
}
delete pNewList;
(*ppwStripIndices) = pStripIndices;
return dwNumStripIndices;
}
//--------------------------------------------------------------------------------------
// Name: BuildStrips()
// Desc: Build a (hopefully) optimal set of strips from a trilist
//--------------------------------------------------------------------------------------
void CStripper::BuildStrips( CStripList* pStripList, int maxlen, BOOL bLookAhead,
BOOL bNonSequential, BOOL bSwapOrientation )
{
// temp indices storage
const int cNumTmpVerts = 1024;
WORD pStripVerts[cNumTmpVerts + 1];
int pStripTris[cNumTmpVerts + 1];
// clear all the used flags for the tris
ZeroMemory( m_pUsed, m_dwNumTris * sizeof(m_pUsed[0]) );
BOOL bStartCW = TRUE;
for( ;; )
{
int besttri = 0;
int bestvert = 0;
float bestratio = 2.0f;
int bestneighborcount = INT_MAX;
for( int tri = 0; tri < m_dwNumTris; tri++)
{
// if used the continue
if(m_pUsed[tri])
continue;
// get the neighbor count
int curneighborcount = GetNeighborCount(tri);
// push all the singletons to the very end
if( !curneighborcount )
curneighborcount = 4;
// if this guy has more neighbors than the current best - bail
if( curneighborcount > bestneighborcount )
continue;
// try starting the strip with each of this tris verts
for( int vert = 0; vert < 3; vert++ )
{
int swaps;
int num_tris = CreateStrip( tri, vert, maxlen, &swaps, bLookAhead,
bNonSequential, bStartCW, pStripTris );
int len = 2 + num_tris + swaps;
float ratio = (len == 3) ? 1.0f : (float)swaps / len;
// check if this ratio is better than what we've already got for
// this neighborcount
if( (curneighborcount < bestneighborcount) ||
(curneighborcount == bestneighborcount && ratio < bestratio) )
{
bestneighborcount = curneighborcount;
besttri = tri;
bestvert = vert;
bestratio = ratio;
}
}
}
// no strips found?
if( bestneighborcount == INT_MAX )
break;
// recreate this strip
int swaps;
int num_tris = CreateStrip( besttri, bestvert, maxlen, &swaps, bLookAhead,
bNonSequential, bStartCW, pStripTris );
// Mark the tris on the best strip as used
for( int tri = 0; tri < num_tris; tri++ )
m_pUsed[pStripTris[tri]] = 1;
// Make the indices from the triangle verts.
int num_indices = CreateIndices( bStartCW, num_tris, pStripTris, pStripVerts );
// Create a new CStrip and stuff in the list.
CStrip* pStrip = new CStrip( num_tris, num_indices );
pStrip->m_bIsStripCW = bStartCW;
for( int j = 0; j < num_tris; j++ )
pStrip->m_pTriangles[j] = pStripTris[j];
for( int k = 0; k < num_indices; k++ )
pStrip->m_pIndices[k] = pStripVerts[k];
// Store the CStrip
pStripList->AddStripToList( pStrip );
if( bSwapOrientation )
{
// if strip was odd - swap orientation
if( (num_indices & 0x1) )
bStartCW = !bStartCW;
}
}
}
//--------------------------------------------------------------------------------------
// Name: InitTriangleInfo()
// Desc: Initialize triangle information (edges, #neighbors, etc.)
//--------------------------------------------------------------------------------------
void CStripper::InitTriangleInfo(int tri, int vert)
{
WORD* ptriverts = &m_pTriangles[tri + 1][0];
int vert1 = m_pTriangles[tri][(vert + 1) % 3];
int vert2 = m_pTriangles[tri][vert];
for( int itri = tri + 1; itri < m_dwNumTris; itri++, ptriverts += 3 )
{
if( m_pUsed[itri] != 0x7 )
{
for( int ivert = 0; ivert < 3; ivert++ )
{
if( ( ptriverts[ivert] == vert1) &&
( ptriverts[(ivert + 1) % 3] == vert2 ) )
{
// add the triangle info
m_pTriInfo[tri].neighbortri[vert] = itri;
m_pTriInfo[tri].neighboredge[vert] = ivert;
m_pUsed[tri] |= (1 << vert);
m_pTriInfo[itri].neighbortri[ivert] = tri;
m_pTriInfo[itri].neighboredge[ivert] = vert;
m_pUsed[itri] |= (1 << ivert);
return;
}
}
}
}
}
//--------------------------------------------------------------------------------------
// Name: CStripper()
// Desc: Constructor
//--------------------------------------------------------------------------------------
CStripper::CStripper( int dwNumTris, TRIANGLELIST pTriangles )
{
// Store trilist info
m_dwNumTris = dwNumTris;
m_pTriangles = pTriangles;
m_pUsed = new int[dwNumTris];
m_pTriInfo = new TRIANGLEINFO[dwNumTris];
// Init triinfo
for( int itri = 0; itri < dwNumTris; itri++ )
{
m_pTriInfo[itri].neighbortri[0] = -1;
m_pTriInfo[itri].neighbortri[1] = -1;
m_pTriInfo[itri].neighbortri[2] = -1;
}
// Clear the used flag
ZeroMemory( m_pUsed, m_dwNumTris * sizeof(m_pUsed[0]) );
// Go through all the triangles and find edges, neighbor counts
for( int itri = 0; itri < dwNumTris; itri++ )
{
for( int ivert = 0; ivert < 3; ivert++ )
{
if( !(m_pUsed[itri] & (1 << ivert)) )
InitTriangleInfo( itri, ivert );
}
}
// Clear the used flags from InitTriangleInfo
ZeroMemory( m_pUsed, m_dwNumTris * sizeof(m_pUsed[0]) );
}
//--------------------------------------------------------------------------------------
// Name: ~CStripper()
// Desc: Destructor
//--------------------------------------------------------------------------------------
CStripper::~CStripper()
{
delete[] m_pUsed;
delete[] m_pTriInfo;
}
//--------------------------------------------------------------------------------------
// Name: Add()
// Desc: Add an index to the cache - returns true if it was added, false otherwise
//--------------------------------------------------------------------------------------
int CVertCache::Add( int strip, int vertindex )
{
// Find index in cache
for( int iCache = 0; iCache < CACHE_SIZE; iCache++ )
{
if( vertindex == m_rgCache[iCache] )
{
// If it's in the cache and it's from a different strip
// change the strip to the new one and count the cache hit
if( strip != m_rgCacheStrip[iCache] )
{
m_cachehits++;
m_rgCacheStrip[iCache] = strip;
m_bReUsed[iCache] = true;
}
// Item is already in the cache, so no need to add it
return 0;
}
}
int retval = 1;
// If we are push one of the verts add by our strip out of the cache, return two.
if ( m_rgCache[m_iCachePtr] != -1 && m_rgCacheStrip[m_iCachePtr] == strip &&
!m_bReUsed[m_iCachePtr] )
retval = 2;
// Not in cache, add vert and strip
m_rgCache[m_iCachePtr] = (WORD)vertindex;
m_rgCacheStrip[m_iCachePtr] = strip;
m_bReUsed[m_iCachePtr] = false;
m_iCachePtr = (m_iCachePtr + 1) % CACHE_SIZE;
return retval;
}
//--------------------------------------------------------------------------------------
// Name: CountCacheMisses()
// Desc: Count the number of cache misses for a given strip.
//--------------------------------------------------------------------------------------
DWORD CountCacheMisses( DWORD dwIndexCount, WORD *pStripIndices )
{
CVertCache VertexCache;
DWORD dwMisses = 0;
for ( DWORD i = 0; i < dwIndexCount; i++ )
dwMisses += (VertexCache.Add( 1, pStripIndices[i] ) != 0);
return dwMisses;
}
//--------------------------------------------------------------------------------------
// Name: TriStripToTriList()
// Desc: Convert a tri-strip to a tri-list.
//--------------------------------------------------------------------------------------
DWORD TriStripToTriList( DWORD dwNumStripIndices, const WORD *pStripIndices,
WORD *pTriangleIndices )
{
DWORD dwNumTriangleIndices = 0;
// Unstrip the indices.
WORD ind0 = 0;
WORD ind1 = pStripIndices[0];
WORD ind2 = pStripIndices[1];
for( DWORD src = 2; src < dwNumStripIndices; src++ )
{
ind0 = ind1;
ind1 = ind2;
ind2 = pStripIndices[src];
// Check for null-triangles.
if( ind0 != ind1 && ind1 != ind2 && ind2 != ind0 )
{
if( src & 1 )
{
pTriangleIndices[dwNumTriangleIndices] = ind1;
dwNumTriangleIndices++;
pTriangleIndices[dwNumTriangleIndices] = ind0;
dwNumTriangleIndices++;
// always put the new index last
pTriangleIndices[dwNumTriangleIndices] = ind2;
dwNumTriangleIndices++;
}
else
{
pTriangleIndices[dwNumTriangleIndices] = ind0;
dwNumTriangleIndices++;
pTriangleIndices[dwNumTriangleIndices] = ind1;
dwNumTriangleIndices++;
// always put the new index last
pTriangleIndices[dwNumTriangleIndices] = ind2;
dwNumTriangleIndices++;
}
}
}
return dwNumTriangleIndices;
}
//--------------------------------------------------------------------------------------
// Name: MakeStrips()
// Desc: Stripify routine
//--------------------------------------------------------------------------------------
DWORD TriStripper::MakeStrips( DWORD dwNumTriangles, WORD* pTriangles,
DWORD* pdwNumIndices, WORD** ppStripIndices,
DWORD dwFlags )
{
if( 0 == dwNumTriangles || NULL == pTriangles )
return 0;
*ppStripIndices = 0;
// The stripper, and storage for it's best results
CStripper stripper( dwNumTriangles, (TRIANGLELIST)pTriangles );
DWORD dwBestCost = 0xffffffff;
DWORD dwBestIndexCount = 0;
WORD* pTempStripIndices;
// Map of various args to try stripifying mesh with
struct ARGMAP
{
DWORD dwMaxLength; // Maximum length of strips
BOOL bLookAhead; // Whether to use sgi greedy lookahead
BOOL bNonSequential; // Take non-sequential exits to lengthen strips.
};
ARGMAP argmap[] =
{
{ 1024, TRUE, TRUE },
{ 1024, FALSE, TRUE },
{ 1024, FALSE, FALSE },
};
const int dwNumArgMaps = sizeof(argmap)/sizeof(ARGMAP);
// Build strips with the various maxlength and lookahead arguments, and
// pick the one with the least result index count.
for( int map = 0; map < dwNumArgMaps; map++ )
{
// Build the strip with the various maxlength and lookahead arguments
CStripList* pStripList = new CStripList(dwNumTriangles);
stripper.BuildStrips( pStripList, argmap[map].dwMaxLength,
argmap[map].bLookAhead, argmap[map].bNonSequential,
(dwFlags & ATGTRISTRIPPER_OPTIMIZE_FOR_INDICES) != 0 );
DWORD dwIndexCount;
DWORD dwCost;
// Build strip (freeing the strip list).
if( dwFlags & ATGTRISTRIPPER_OPTIMIZE_FOR_INDICES )
{
dwIndexCount = stripper.CreateLongStrip( pStripList, &pTempStripIndices );
// Cost is just the number of indices.
dwCost = dwIndexCount;
}
else
{
dwIndexCount = stripper.CreateCachedStrip( pStripList, &pTempStripIndices );
// Count number of cache misses.
DWORD dwMisses = CountCacheMisses( dwIndexCount, pTempStripIndices );
if( dwFlags & ATGTRISTRIPPER_OUTPUT_TRILIST )
{
// Nulls don't matter for tri-lists.
dwCost = dwMisses;
}
else
{
// Cost is the (shader length) / 2 + (# null tris) * 2
dwCost = dwMisses * (SHADER_CYCLES/2) +
(dwIndexCount - (dwNumTriangles + 2)) * 2;
}
}
if ( dwCost < dwBestCost )
{
// Free the old best list
if( *ppStripIndices )
delete[] *ppStripIndices;
// store the new best list
*ppStripIndices = pTempStripIndices;
dwBestCost = dwCost;
dwBestIndexCount = dwIndexCount;
}
else
{
delete[] pTempStripIndices;
}
delete pStripList;
}
if( dwFlags & ATGTRISTRIPPER_OUTPUT_TRILIST )
{
// Convert to triangle list.
WORD* pTempIndices = new WORD[dwNumTriangles*3];
dwBestIndexCount = TriStripToTriList( dwBestIndexCount, *ppStripIndices,
pTempIndices );
assert( dwBestIndexCount <= dwNumTriangles*3 );
delete[] *ppStripIndices;
*ppStripIndices = pTempIndices;
}
if( pdwNumIndices )
(*pdwNumIndices) = dwBestIndexCount;
return dwBestIndexCount;
}
//--------------------------------------------------------------------------------------
// Name: ComputeVertexPermutation()
// Desc: Reorder the vertices
//--------------------------------------------------------------------------------------
VOID TriStripper::ComputeVertexPermutation( DWORD dwNumStripIndices, WORD* pStripIndices,
DWORD dwNumVertices, WORD** ppVertexPermutation )
{
// Sort verts to maximize locality.
SortEntry* pSortTable = new SortEntry[dwNumVertices];
// Fill in original index.
for( DWORD i = 0; i < dwNumVertices; i++ )
{
pSortTable[i].iOrigIndex = i;
pSortTable[i].iFirstUsed = -1;
}
// Fill in first used flag.
for( DWORD i = 0; i < dwNumStripIndices; i++ )
{
int index = pStripIndices[i];
if( pSortTable[index].iFirstUsed == -1 )
pSortTable[index].iFirstUsed = i;
}
// Sort the table, using the STL sort() routine.
std::sort( pSortTable, pSortTable + dwNumVertices );
// Copy re-mapped to original vertex permutation into output array.
(*ppVertexPermutation) = new WORD[dwNumVertices];
for( DWORD i = 0; i < dwNumVertices; i++ )
{
(*ppVertexPermutation)[i] = (WORD)pSortTable[i].iOrigIndex;
}
// Build original to re-mapped permutation.
WORD* pInversePermutation = new WORD[dwNumVertices];
for( DWORD i = 0; i < dwNumVertices; i++ )
{
pInversePermutation[pSortTable[i].iOrigIndex] = (WORD)i;
}
// We need to remap indices as well.
for( DWORD i = 0; i < dwNumStripIndices; i++ )
{
pStripIndices[i] = pInversePermutation[ pStripIndices[i] ];
}
delete[] pSortTable;
delete[] pInversePermutation;
}
//--------------------------------------------------------------------------------------
// Name: CalcCacheHits()
// Desc: Calculate the number of cache hits and degenerate triangles
//--------------------------------------------------------------------------------------
HRESULT TriStripper::CalcCacheHits( D3DPRIMITIVETYPE dwPrimType, DWORD dwVertexSize,
WORD* pIndices, DWORD dwNumIndices,
DWORD* pdwNumDegenerateTris,
DWORD* pdwNumCacheHits,
DWORD* pdwNumPagesCrossed )
{
// Check arguments
if( NULL == pdwNumDegenerateTris || NULL == pdwNumCacheHits ||
NULL == pdwNumPagesCrossed )
return E_INVALIDARG;
// Initialize results
(*pdwNumDegenerateTris) = 0;
(*pdwNumCacheHits) = 0;
(*pdwNumPagesCrossed) = 1;
// Simulate a vertex cache
static const int CACHE_SIZE = 12;
static const int PAGE_SIZE = 4096;
DWORD rgdwCache[CACHE_SIZE];
INT iCachePtr = 0;
BOOL bIsTriStrip = (dwPrimType == D3DPT_TRIANGLESTRIP);
DWORD dwLastPageAddr = 0;
memset( rgdwCache, 0xff, sizeof(rgdwCache) );
// Run all vertices through the simulated vertex cache, tallying cache hits,
// degenerate triangles, and pages crossed.
for( DWORD i = 0; i < dwNumIndices; i++ )
{
// This makes all kinds of assumptions such as page size is 4k,
// page across then back is ok, etc etc. Seems to be an ok
// estimate on data locality though.
DWORD dwPage = dwVertexSize * pIndices[i] / PAGE_SIZE;
if( ( dwPage > dwLastPageAddr ) || ( dwPage+1 < dwLastPageAddr ) )
{
(*pdwNumPagesCrossed)++;
dwLastPageAddr = dwVertexSize * pIndices[i] / PAGE_SIZE;
}
// Update our count of degenerate tris
if( bIsTriStrip && (i > 1) )
if( ( pIndices[i-0] == pIndices[i-1] ) ||
( pIndices[i-0] == pIndices[i-2] ) ||
( pIndices[i-1] == pIndices[i-2] ) )
(*pdwNumDegenerateTris)++;
// Check to see if the vertex would be in the cache
BOOL bVertexInCache = FALSE;
for( int cache_index = 0; cache_index < CACHE_SIZE; cache_index++ )
{
if( pIndices[i] == rgdwCache[cache_index] )
{
bVertexInCache = TRUE;
break;
}
}
if( bVertexInCache )
{
// Keep track of cache hits
(*pdwNumCacheHits)++;
}
else
{
// Add vertex to simulated cache
rgdwCache[iCachePtr] = pIndices[i];
iCachePtr = (iCachePtr + 1) % CACHE_SIZE;
}
}
return S_OK;
}