CHC - Coherent Hierarchical Culling

Abstract

On this page a demo for the Coherent Hierarchical Culling algorithm described in the paper "Coherent Hierarchical Culling: Hardware Occlusion Queries Made Useful" by Jiri Bittner, Michael Wimmer, Harald Piringer and Werner Purgathofer is presented. The demo presented here is on the CD of the XXXXXXX book (to appear in 2005), accompanying the article: "Hardware Occlusion Queries Made Useful". The author expects prior knowledge of computer graphics, an understanding of the occlusion query construct and of the Coherent Hierarchical Culling algorithm as described in the paper.

The Algorithm

One problem with hardware occlusion queries are stalls of the cpu. We need to get the query result back from the graphics card and this requires a round trip of the graphics hardware(issuing the query, waiting for completion and reading the result back) and destroys the asynchronous execution of code on the CPU and GPU. The Coherent Hierarchical Culling algorithm tries to minimize this stalls by using the wait time for issuing other, unrelated occlusion queries.

Implementation

In this chapter we will develop the actual code of the demo and explain which design considerations where made and why. The implementation of the algorithm is straight forward. We need 3 data structures:
  • A hierarchical structure to hold the bounding box hierarchie: kd-tree, scenegraph, ...
  • A queue to hold the currently running occlusion queries
  • A stack or even better a priority queue to hold the nodes of our hierarchy we need to traverse
  • Given the pseudo-code from the article:
    TraverseNode(node) 
    {
      if ( IsLeaf(node) )
        Render(node);
      else
        TraversalStack.PushChildren(node);
    }
    
    PullUpVisibility(node) 
    {
      while (!node.visible) 
      { 
        node.visible = true; 
        node = node.parent; 
      }
    }
    
    TraversalStack.Push(hierarchy.Root);
    while ( not TraversalStack.Empty() or
            not QueryQueue.Empty() ) 
    {
      //-- PART 1: process finished occlusion queries
      while ( not QueryQueue.Empty() and
             (ResultAvailable(QueryQueue.Front()) or
              TraversalStack.Empty()) ) 
      {
        node = QueryQueue.Dequeue();
        // wait if result not available
        visiblePixels = GetOcclusionQueryResult(node);
        if ( visiblePixels > VisibilityThreshold ) 
        {
          PullUpVisibility(node);
          TraverseNode(node);
        }
      }
      
      //-- PART 2: hierarchical traversal
      if ( not TraversalStack.Empty() ) 
      {
        node = TraversalStack.Pop();
        if ( InsideViewFrustum(node) ) 
        {
          // identify previously visible nodes
          wasVisible = node.visible and 
                       (node.lastVisited == frameID - 1);
          // identify nodes that we cannot skip queries for
          leafOrWasInvisible = not wasVisible or IsLeaf(node);
          // reset node's visibility classification
          node.visible = false;
          // update node's visited flag
          node.lastVisited = frameID;
          // skip testing previously visible interior nodes
          if ( leafOrWasInvisible ) 
          {
            IssueOcclusionQuery(node); 
            QueryQueue.Enqueue(node);
          }
          // always traverse a node if it was visible
          if ( wasVisible )
            TraverseNode(node);
        }
      }
    }