Ignore:
Timestamp:
10/05/07 15:36:52 (17 years ago)
Author:
mattausch
Message:

added partial implementation of chc++. problem: bounding box rendering in Ogre is VERY slow

Location:
GTP/trunk/Lib/Vis/OnlineCullingCHC/src
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • GTP/trunk/Lib/Vis/OnlineCullingCHC/src/CoherentHierarchicalCullingManager.cpp

    r2455 r2555  
    99 
    1010 
    11 static int batchSize = 5; 
    1211 
    1312//----------------------------------------------------------------------- 
     
    3635{ 
    3736        if (0) CullingLogManager::GetSingleton()->LogMessage("chc"); 
    38         QueryQueue queryQueue; 
    3937        unsigned int visiblePixels = 0; 
    4038         
     
    4240        //-- PART 1: process finished occlusion queries 
    4341 
    44         while (!mHierarchyInterface->GetQueue()->empty() || !queryQueue.empty()) 
     42        while (!mHierarchyInterface->GetQueue()->empty() || !mQueryQueue.empty()) 
    4543        { 
    4644                ////////// 
    4745                //-- only wait for result if there are no nodes to process 
    4846 
    49                 while (!queryQueue.empty() &&  
    50                            (NodeInvalid(queryQueue.front().first) || 
    51                             queryQueue.front().second->GetQueryResult( 
     47                while (!mQueryQueue.empty() &&  
     48                           (NodeInvalid(mQueryQueue.front().first) || 
     49                            mQueryQueue.front().second->GetQueryResult( 
    5250                                             visiblePixels, mHierarchyInterface->GetQueue()->empty()))) 
    5351                { 
    54                         HierarchyNode *node = queryQueue.front().first; 
    55                         queryQueue.pop(); 
     52                        HierarchyNode *node = mQueryQueue.front().first; 
     53                        mQueryQueue.pop(); 
    5654 
    5755                        // "invalid nodes" happen for hierarchies that have geometry 
     
    145143                                        ++ mNumQueriesIssued; 
    146144                                         
    147                                         const bool testGeometry = wasVisible && mHierarchyInterface->IsLeaf(node) && mTestGeometryForVisibleLeaves; 
    148  
    149                                         queryQueue.push(QueryPair(node, mHierarchyInterface-> 
     145                                        const bool testGeometry =  
     146                                                wasVisible && mHierarchyInterface->IsLeaf(node) && mTestGeometryForVisibleLeaves; 
     147 
     148                                        mQueryQueue.push(QueryPair(node, mHierarchyInterface-> 
    150149                                                IssueNodeOcclusionQuery(node, testGeometry))); 
    151150                                } 
     
    192191{ 
    193192        // parent was tested invisible in this frame 
    194         HierarchyNode *parent = mHierarchyInterface->GetParent(node); 
    195     return  
    196                 parent &&  
    197                 (mHierarchyInterface->LastVisited(node) == mHierarchyInterface->GetFrameId()) &&  
    198                 !mHierarchyInterface->IsNodeVisible(parent); 
     193    return (  
     194                    mHierarchyInterface->GetParent(node) &&  
     195                    (mHierarchyInterface->LastVisited(node) == mHierarchyInterface->GetFrameId()) &&  
     196                    !mHierarchyInterface->IsNodeVisible(mHierarchyInterface->GetParent(node)) 
     197           ); 
    199198} 
    200199//----------------------------------------------------------------------- 
  • GTP/trunk/Lib/Vis/OnlineCullingCHC/src/CoherentHierarchicalCullingPlusPlusManager.cpp

    r2553 r2555  
    1 #include "BatchedQueriesCullingManager.h" 
     1#include "CoherentHierarchicalCullingPlusPlusManager.h" 
     2#include "CullingLogManager.h" 
     3 
     4#include <time.h> 
     5#include <sstream> 
     6 
    27 
    38 
    49namespace GtpVisibility { 
    510 
    6 //----------------------------------------------------------------------- 
    7 BatchedQueriesCullingManager::BatchedQueriesCullingManager(): 
    8 mMaxPending(5) 
    9 { 
    10 } 
    11 //----------------------------------------------------------------------- 
    12 BatchedQueriesCullingManager::BatchedQueriesCullingManager(const unsigned int  
    13                                                                                                                    assumedVisibility): 
    14 CoherentHierarchicalCullingManager(assumedVisibility), mMaxPending(5) 
    15 { 
    16 } 
    17  
    18 //----------------------------------------------------------------------- 
    19 void BatchedQueriesCullingManager::RenderScene() 
    20 { 
    21         QueryQueue queryQueue; 
     11 
     12static const bool CULL_INVALID_NODES = false; 
     13 
     14//----------------------------------------------------------------------- 
     15CoherentHierarchicalCullingPlusPlusManager::CoherentHierarchicalCullingPlusPlusManager(): 
     16mAssumedVisibility(0), mMaxInvisibleNodesSize(50) 
     17{ 
     18} 
     19//----------------------------------------------------------------------- 
     20CoherentHierarchicalCullingPlusPlusManager::CoherentHierarchicalCullingPlusPlusManager( 
     21                                                                                                                const unsigned int assumedVisibility): 
     22mAssumedVisibility(assumedVisibility), mMaxInvisibleNodesSize(50) 
     23{ 
     24} 
     25//----------------------------------------------------------------------- 
     26void CoherentHierarchicalCullingPlusPlusManager::AssignAssumedVisibility(GtpVisibility::HierarchyNode *node) 
     27{ 
     28        if (!mHierarchyInterface->IsNodeVisible(node)) 
     29                // previously invisible nodes: give random offset just for the first test after 
     30                // becoming visible to avoid that all nodes are tested in the same frame 
     31                mHierarchyInterface->SetNodeAssumedVisible(node, rand() * mAssumedVisibility / RAND_MAX); 
     32        else 
     33                mHierarchyInterface->SetNodeAssumedVisible(node, mAssumedVisibility); 
     34} 
     35//----------------------------------------------------------------------- 
     36void CoherentHierarchicalCullingPlusPlusManager::RenderScene() 
     37{ 
     38        if (0) CullingLogManager::GetSingleton()->LogMessage("chc++"); 
     39         
    2240        unsigned int visiblePixels = 0; 
    2341 
    24         PendingQueue pendingQueue; 
    25  
     42        ///////////// 
    2643        //-- PART 1: process finished occlusion queries 
    27         while (!mHierarchyInterface->GetQueue()->empty() || !queryQueue.empty() ||!pendingQueue.empty()) 
     44 
     45        while (!mHierarchyInterface->GetQueue()->empty() || !mQueryQueue.empty() || 
     46                   !mInvisibleNodes.empty() || !mVisibleNodes.empty()) 
    2847        { 
    29                 while (!queryQueue.empty() &&  
    30                            queryQueue.front().second->GetQueryResult(visiblePixels,  
    31                                                                         mHierarchyInterface->GetQueue()->empty())) 
     48                ////////// 
     49                //-- only wait for result if there are no nodes to process 
     50 
     51                bool resultAvailable = false; 
     52                while ( 
     53                           !mQueryQueue.empty() &&  
     54                           (NodeInvalid(mQueryQueue.front().first) || 
     55                           mHierarchyInterface->GetQueue()->empty() || 
     56                           (resultAvailable = mQueryQueue.front().second->GetQueryResult(visiblePixels, false))) 
     57                           ) 
    3258                { 
    33                 HierarchyNode *node = queryQueue.front().first; 
    34                          
    35                         queryQueue.pop(); 
    36  
     59                        HierarchyNode *node = mQueryQueue.front().first; 
     60 
     61                        // "invalid nodes" happen for hierarchies that store geometry in the interiors 
     62                        // parent was tested invisible => remove children from queue 
     63                        if (NodeInvalid(node)) {mQueryQueue.pop(); continue;} 
     64 
     65                        // during the wait time issue nodes from visible queue 
     66                        while (!mVisibleNodes.empty() && !resultAvailable) 
     67                        { 
     68                                HierarchyNode *vn = mVisibleNodes.front(); 
     69                                mVisibleNodes.pop(); 
     70                                IssueQuery(vn, false); 
     71 
     72                                // check if result is already availalbe 
     73                                resultAvailable = mQueryQueue.front().second->GetQueryResult(visiblePixels, false); 
     74                        } 
     75 
     76                        if (!resultAvailable) 
     77                                mQueryQueue.front().second->GetQueryResult(visiblePixels, true); 
     78 
     79                        resultAvailable = false; 
     80                mQueryQueue.pop(); 
     81 
     82                        // tested visible 
    3783                        if (visiblePixels > mVisibilityThreshold) 
    3884                        { 
    39                                 // ensure that we only traverse once if geometry in node 
     85                                // assing the #frames the node is assumed to stay visible 
     86                                AssignAssumedVisibility(node); 
     87 
     88                                // for previously visible interior node which contains geometry:  
     89                                // ensure that we did not already traverse this node  
     90                                // (which means that the visibility flag is set) 
    4091                                if (!mHierarchyInterface->IsNodeVisible(node)) 
    41                                         mHierarchyInterface->TraverseNode(node); 
     92                                        mHierarchyInterface->TraverseNode2(node); 
    4293 
    4394                                mHierarchyInterface->PullUpVisibility(node); 
     
    50101                                 
    51102                                if (mVisualizeCulledNodes) 
    52                                 { 
    53103                                        mHierarchyInterface->VisualizeCulledNode(node, QUERY_CULLED); 
    54104                                } 
    55                         } 
     105                         
     106                        // update node's visited flag 
     107                        mHierarchyInterface->SetLastVisited(node, mHierarchyInterface->GetFrameId());    
    56108                } 
    57109                 
     110                //////////////// 
    58111                //-- PART 2: hierarchical traversal 
     112 
    59113                if (!mHierarchyInterface->GetQueue()->empty()) 
    60114                { 
     
    62116                        mHierarchyInterface->GetQueue()->pop(); 
    63117                                 
     118                        // parent was tested invisible => remove children from queue 
     119                        if (NodeInvalid(node)) continue; 
     120 
    64121                        bool intersects = false; 
    65122 
     
    69126                                 
    70127                                if (mVisualizeCulledNodes) 
    71                                 { 
    72128                                        mHierarchyInterface->VisualizeCulledNode(node, FRUSTUM_CULLED); 
    73                                 } 
    74                         } 
    75                         // -- if node intersects near plane, skip query because wrong results possible 
     129                        } 
    76130                        else if (intersects) 
    77131                        { 
     132                                //-- if node intersects near plane, skip query because wrong results possible 
    78133                                SkipQuery(node); 
    79134                        } 
     
    85140                                 
    86141                                // if we assume node to be visible in this frame => skip query  
    87                                 bool skipQuery = wasVisible && (mAssumedVisibility > 0) &&  
    88                                         DecideVisible(node) && mHierarchyInterface->HasGeometry(node); 
     142                                const bool skipQuery = wasVisible && 
     143                                                                           DecideVisible(node) &&  
     144                                                                           mHierarchyInterface->HasGeometry(node); 
    89145 
    90146                                if (skipQuery) 
     
    96152                // identify nodes that we cannot skip queries for 
    97153                                // geometry not only in leaves => test for renderable geometry 
    98                                 bool issueQuery = !wasVisible || mHierarchyInterface->HasGeometry(node); 
     154                                const bool issueQuery = !wasVisible || mHierarchyInterface->HasGeometry(node); 
    99155                                                         
    100                                 // reset node's visibility classification 
    101                                 // set visible if geometry in node so we only traverse once 
     156                                // set node's visibility classification 
     157                                // we identify previously visible / invisible nodes in the query queue 
    102158                                mHierarchyInterface->SetNodeVisible(node, wasVisible && issueQuery); 
    103159 
    104                                 // update node's visited flag 
    105                                 mHierarchyInterface->SetLastVisited(node, mHierarchyInterface->GetFrameId()); 
    106  
    107                                 // skip testing previously visible nodes without geometry 
    108160                                if (issueQuery) 
    109161                                { 
    110162                                        ++ mNumQueriesIssued; 
    111                                         // add to the pending queue instead of immediate query 
    112                                         if ((int)pendingQueue.size() < mMaxPending) 
    113                                                 pendingQueue.push(PendingQuery(node, wasVisible)); 
     163 
     164                                        if (!wasVisible) 
     165                                        { 
     166                                                mInvisibleNodes.push_back(node); 
     167 
     168                                                if ((int)mInvisibleNodes.size() >= mMaxInvisibleNodesSize) 
     169                                                { 
     170                                                        mHierarchyInterface->RenderQueue(); 
     171                                                        // issue the batched queries 
     172                                                        IssueBatchedQuery(mInvisibleNodes); 
     173                                                } 
     174                                        } 
    114175                                        else 
    115176                                        { 
    116                                                 IssueMultipleQueries(pendingQueue, queryQueue);  
     177                                                mVisibleNodes.push(node); 
     178                                                //IssueQuery(node, false); 
    117179                                        } 
     180                                } 
     181                                else 
     182                                { 
     183                                        // skip testing previously visible nodes without geometry 
     184                    // just update node's visited flag 
     185                                        mHierarchyInterface->SetLastVisited(node, mHierarchyInterface->GetFrameId()); 
    118186                                } 
    119187                                 
     
    121189                                if (wasVisible) 
    122190                                { 
    123                                         mHierarchyInterface->TraverseNode(node); 
     191                                        mHierarchyInterface->TraverseNode2(node); 
    124192                                } 
    125193                        } 
    126194                } 
    127195 
    128                 // issue rest of queries 
    129                 IssueMultipleQueries(pendingQueue, queryQueue); 
     196                 
     197                if (mHierarchyInterface->GetQueue()->empty() && !mInvisibleNodes.empty()) 
     198                { 
     199                        if (0) 
     200                        { 
     201                                std::stringstream d; 
     202                                d << "inv nodes: " << (int)mInvisibleNodes.size(); 
     203                                CullingLogManager::GetSingleton()->LogMessage(d.str()); 
     204                        } 
     205                         
     206                        mHierarchyInterface->RenderQueue(); 
     207                        IssueBatchedQuery(mInvisibleNodes); 
     208                } 
     209 
     210        // issue rest of nodes from nodes from visible queue 
     211                if (mHierarchyInterface->GetQueue()->empty() && mQueryQueue.empty() && !mVisibleNodes.empty()) 
     212                { 
     213                        if (0) 
     214                        { 
     215                                std::stringstream d; 
     216                                d << "vis nodes: " << (int)mVisibleNodes.size(); 
     217                                CullingLogManager::GetSingleton()->LogMessage(d.str()); 
     218                        } 
     219 
     220                        while (!mVisibleNodes.empty()) 
     221                        { 
     222                                IssueQuery(mVisibleNodes.front(), false); 
     223                                mVisibleNodes.pop(); 
     224                        } 
     225                } 
    130226        } 
    131 } 
    132  
    133 void BatchedQueriesCullingManager::IssueMultipleQueries(PendingQueue &pendingQueue,  
    134                                                                                                                 QueryQueue &queryQueue) 
    135 { 
    136         while (!pendingQueue.empty()) 
     227 
     228 
     229        if (0) 
    137230        { 
    138                 HierarchyNode *node = pendingQueue.front().first; 
    139                 const bool wasVisible = pendingQueue.front().second; 
    140  
    141                 pendingQueue.pop(); 
    142  
    143                 queryQueue.push(QueryPair(node, mHierarchyInterface-> 
    144                                         IssueNodeOcclusionQuery(node, wasVisible))); 
     231                std::stringstream d; 
     232                d << "queries: " << (int)mQueryQueue.size() << " vis: " << (int)mVisibleNodes.size() << " inv: " << (int)mInvisibleNodes.size(); 
     233                CullingLogManager::GetSingleton()->LogMessage(d.str()); 
    145234        } 
    146 } 
    147  
    148 void BatchedQueriesCullingManager::SetMaxPending(int maxPending) 
    149 { 
    150         mMaxPending = maxPending; 
    151 } 
    152  
    153 } // namespace GtpVisibility 
     235         
     236        // render rest 
     237        mHierarchyInterface->RenderQueue(); 
     238} 
     239//----------------------------------------------------------------------- 
     240void CoherentHierarchicalCullingPlusPlusManager::SetAssumedVisibility(const unsigned int assumedVisibility) 
     241{ 
     242        mAssumedVisibility = assumedVisibility; 
     243} 
     244//----------------------------------------------------------------------- 
     245inline bool CoherentHierarchicalCullingPlusPlusManager::DecideVisible(HierarchyNode *node) const 
     246{ 
     247        mHierarchyInterface->DecNodeAssumedVisible(node); 
     248        //std::stringstream d; d << "node visible: " << mHierarchyInterface->GetNodeAssumedVisible(node); 
     249        //CullingLogManager::GetSingleton()->LogMessage(d.str()); 
     250        return mHierarchyInterface->GetNodeAssumedVisible(node) > 0; 
     251} 
     252//----------------------------------------------------------------------- 
     253inline void CoherentHierarchicalCullingPlusPlusManager::SkipQuery(HierarchyNode *node) const 
     254{ 
     255        //-- set node to be visible in this frame, then traverse it 
     256        mHierarchyInterface->SetLastVisited(node, mHierarchyInterface->GetFrameId()); 
     257 
     258        mHierarchyInterface->PullUpVisibility(node);                     
     259        mHierarchyInterface->TraverseNode2(node); 
     260} 
     261//----------------------------------------------------------------------- 
     262inline bool CoherentHierarchicalCullingPlusPlusManager::NodeInvalid(HierarchyNode *node) const 
     263{ 
     264        if (!CULL_INVALID_NODES) return false; 
     265 
     266        // parent was tested invisible in this frame 
     267    return (  
     268                    mHierarchyInterface->GetParent(node) &&  
     269                    (mHierarchyInterface->LastVisited(node) == mHierarchyInterface->GetFrameId()) &&  
     270                    !mHierarchyInterface->IsNodeVisible(mHierarchyInterface->GetParent(node)) 
     271           ); 
     272} 
     273//----------------------------------------------------------------------- 
     274void CoherentHierarchicalCullingPlusPlusManager::SetTestGeometryForVisibleLeaves(const bool testGeometry) 
     275{ 
     276        mTestGeometryForVisibleLeaves = testGeometry; 
     277} 
     278//----------------------------------------------------------------------- 
     279bool CoherentHierarchicalCullingPlusPlusManager::GetTestGeometryForVisibleLeaves() 
     280{ 
     281        return mTestGeometryForVisibleLeaves; 
     282} 
     283//----------------------------------------------------------------------- 
     284inline void CoherentHierarchicalCullingPlusPlusManager::IssueQuery(HierarchyNode *node,  
     285                                                                                                                                   const bool queryGeometry) 
     286{ 
     287        mQueryQueue.push(QueryPair(node, mHierarchyInterface-> 
     288                IssueNodeOcclusionQuery(node, queryGeometry))); 
     289} 
     290//----------------------------------------------------------------------- 
     291void CoherentHierarchicalCullingPlusPlusManager::IssueBatchedQuery(HierarchyNodeContainer &nodes) 
     292{ 
     293        HierarchyNodeContainer::const_iterator nit, nit_end = nodes.end(); 
     294 
     295        for (nit = nodes.begin(); nit != nit_end; ++ nit) 
     296                IssueQuery(*nit, false); 
     297 
     298        nodes.clear(); 
     299} 
     300                 
     301} 
  • GTP/trunk/Lib/Vis/OnlineCullingCHC/src/FrustumCullingManager.cpp

    r2292 r2555  
    3131                { 
    3232                        mHierarchyInterface->SetNodeVisible(node, true); 
    33                         mHierarchyInterface->TraverseNode(node); 
     33                        mHierarchyInterface->TraverseNode2(node); 
    3434                } 
    3535        } 
     36        mHierarchyInterface->RenderQueue(); 
    3637} 
    3738} // namespace GtpVisibility 
  • GTP/trunk/Lib/Vis/OnlineCullingCHC/src/GtpVisibility.vcproj

    r2542 r2555  
    253253                        UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> 
    254254                        <File 
    255                                 RelativePath="..\src\BatchedQueriesCullingManager.cpp"> 
    256                         </File> 
    257                         <File 
    258255                                RelativePath="..\src\CoherentHierarchicalCullingManager.cpp"> 
     256                        </File> 
     257                        <File 
     258                                RelativePath=".\CoherentHierarchicalCullingPlusPlusManager.cpp"> 
    259259                        </File> 
    260260                        <File 
     
    297297                        UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> 
    298298                        <File 
    299                                 RelativePath="..\include\BatchedQueriesCullingManager.h"> 
    300                         </File> 
    301                         <File 
    302299                                RelativePath="..\include\CoherentHierarchicalCullingManager.h"> 
    303300                        </File> 
    304301                        <File 
    305                                 RelativePath="..\include\CoherentHierarchicalCullingManager2.h"> 
     302                                RelativePath="..\include\CoherentHierarchicalCullingPlusPlusManager.h"> 
    306303                        </File> 
    307304                        <File 
  • GTP/trunk/Lib/Vis/OnlineCullingCHC/src/VisibilityManager.cpp

    r2455 r2555  
    55#include "DummyPreprocessingManager.h" 
    66#include "RandomUpdateCullingManager.h" 
     7#include "CoherentHierarchicalCullingPlusPlusManager.h" 
    78#include "CullingLogManager.h" 
    89 
     
    1920mAssumedVisibilityForChc(0) 
    2021{ 
    21 #if 1 
    2222        SetCullingManager(VisibilityEnvironment::COHERENT_HIERARCHICAL_CULLING); 
    23 #endif 
    24 #if 0    
    25         SetCullingManager(VisibilityEnvironment::STOP_AND_WAIT_CULLING); 
    26 #endif 
    27 #if 0 
    28         SetCullingManager(VisibilityEnvironment::FRUSTUM_CULLING); 
    29 #endif 
    3023} 
    3124//----------------------------------------------------------------------- 
     
    6154                        mCullingManager = new FrustumCullingManager(); 
    6255                        break; 
    63                 case VisibilityEnvironment::RANDOM_UPDATE_CULLING: 
     56                case VisibilityEnvironment::COHERENT_HIERARCHICAL_CULLING_PLUSPLUS: 
    6457                        { 
    65                                 RandomUpdateCullingManager *rum = new RandomUpdateCullingManager(mRandomCandidatesForRuc); 
    66                                 rum->SetTestGeometryForVisibleLeaves(mTestGeometryForVisibleLeaves); 
    67                                 mCullingManager = rum; 
     58                                CoherentHierarchicalCullingPlusPlusManager *chcm =  
     59                                        new CoherentHierarchicalCullingPlusPlusManager(mAssumedVisibilityForChc); 
     60                                mCullingManager = chcm; 
    6861                        } 
    6962                        break; 
     
    118111        mAssumedVisibilityForChc = assumedVisibility; 
    119112 
    120         if (mCullingManagerType == VisibilityEnvironment::COHERENT_HIERARCHICAL_CULLING) 
     113        switch (mCullingManagerType) 
    121114        { 
     115        case VisibilityEnvironment::COHERENT_HIERARCHICAL_CULLING: 
    122116                static_cast<CoherentHierarchicalCullingManager *>(mCullingManager)-> 
    123117                                SetAssumedVisibility(mAssumedVisibilityForChc); 
     118                break; 
     119        case VisibilityEnvironment::COHERENT_HIERARCHICAL_CULLING_PLUSPLUS: 
     120                static_cast<CoherentHierarchicalCullingPlusPlusManager *>(mCullingManager)-> 
     121                                SetAssumedVisibility(mAssumedVisibilityForChc); 
     122                break; 
    124123        } 
    125124} 
     
    129128        mRandomCandidatesForRuc = randomCandidates; 
    130129 
    131         if (mCullingManagerType == VisibilityEnvironment::RANDOM_UPDATE_CULLING) 
     130        if (mCullingManagerType == VisibilityEnvironment::COHERENT_HIERARCHICAL_CULLING_PLUSPLUS) 
    132131        { 
    133132                static_cast<RandomUpdateCullingManager *>(mCullingManager)-> 
     
    160159                                SetTestGeometryForVisibleLeaves(mTestGeometryForVisibleLeaves); 
    161160        } 
    162         else if (mCullingManagerType == VisibilityEnvironment::RANDOM_UPDATE_CULLING) 
     161        /*else if (mCullingManagerType == VisibilityEnvironment::COHERENT_HIERARCHICAL_CULLING_PLUSPLUS) 
    163162        { 
    164163                static_cast<RandomUpdateCullingManager *>(mCullingManager)-> 
    165164                                SetTestGeometryForVisibleLeaves(mTestGeometryForVisibleLeaves); 
    166         } 
     165        }*/ 
    167166} 
    168167//----------------------------------------------------------------------- 
Note: See TracChangeset for help on using the changeset viewer.