//-------------------------------------------------------------------------------------- // 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; im_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; }