Occlusion query code failing on ATI 9600

Sage
Posts: 1,199
Joined: 2004.10
Post: #1
Subject says it all, my occlusion query code, which is working on NVIDIA 5200, is failing on an ATI 9600. I've only got the two cards to test against, so I don't know where else, if at all, it works.

What I do is make two tests, one with depth testing turned off, to get the number of fragments that should pass, and one with depth testing on, to see how many actually do pass. I use the ratio to determine the radius & opacity of a glare I draw.

Looking at output on the console ( when running on an ATI 9600 ), I see that the first test -- the one which determines how many should pass -- always returns 0. The second test, however, works as expected.

Here's my setup, where I test for the presence of the occlusion query extension and make setup:

Code:
void CelestialBody::setUseOcclusionQuery( bool oq )
{
    /*
        Determine if occlusion querying is available, and if
        it's real -- the opengl superbible says that some cards
        report true here, but you have to actually see how many
        bits are supported.
    */
    
    _occlusionQueryAvailable = GLEW_ARB_occlusion_query;
    
    if ( _occlusionQueryAvailable )
    {
        GLint queryCounterBits = 0;
        glGetQueryiv(GL_SAMPLES_PASSED, GL_QUERY_COUNTER_BITS, &queryCounterBits);
        if ( queryCounterBits == 0 )
        {
            _occlusionQueryAvailable = false;
        }
    }
    
    
    if ( !_occlusionQueryAvailable )
    {
        using namespace PANSICore;
        Logger::log( LogEntry::Debug, "CelestialBody::setUseOcclusionQuery",
                     "OcclusionQuery is not available, using ray testing." );
                    
        oq = false;
    }
    
    /*
        Now, create queries, or delete them.
    */

    if ( oq )
    {
        if ( !_queryIDs[0] )
        {
            glGenQueries( 2, &_queryIDs[0] );
        }
    }
    else
    {
        if ( _queryIDs[0] )
        {
            glDeleteQueries( 2, _queryIDs );
            _queryIDs[0] = 0;
            _queryIDs[1] = 0;
        }
    }
    
}

Here's where I perform the two tests:

Code:
void CelestialBody::performOcclusionQuery( void )
{
    Camera *camera = world()->activeCamera();

    float distance = world()->worldSize() * 2.0f,
          size = sinf( _arcSize * DEG2RAD ) * distance * 0.707f;

    vec3 pos( camera->position() + (_dir * distance )),
         up, right, down, left;

    camera->billboardVectors( up, right, down, left );
    
    right *= size;
    up *= size;
    left *= size;
    down *= size;
    
    vec3 ll( pos + left + down ),
         lr( pos + right + down ),
         ur( pos + right + up ),
         ul( pos + left + up );

    glActiveTextureARB( GL_TEXTURE0_ARB );
    glDisable( GL_TEXTURE_2D );

    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );    
    glColor4fv( Color::black );

    glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
    glDepthMask( GL_FALSE );

    /*
        Run first query to see how many fragments *would* pass.
        To do this turn off depth test.
    */
    
    glDisable( GL_DEPTH_TEST );
    glBeginQuery( GL_SAMPLES_PASSED, _queryIDs[0] );

    glBegin( GL_QUADS );

        glVertex3fv( ll.v );
        glVertex3fv( lr.v );
        glVertex3fv( ur.v );
        glVertex3fv( ul.v );        

    glEnd();

    glEndQuery( GL_SAMPLES_PASSED );
        
    
    /*
        Run second query to see how many fragments *did* pass,
        by turning depth test back on.
    */

    glEnable( GL_DEPTH_TEST );
    glBeginQuery( GL_SAMPLES_PASSED, _queryIDs[1] );

    glBegin( GL_QUADS );

        glVertex3fv( ll.v );
        glVertex3fv( lr.v );
        glVertex3fv( ur.v );
        glVertex3fv( ul.v );        

    glEnd();

    glEndQuery( GL_SAMPLES_PASSED );
    

    glEnable( GL_TEXTURE_2D );
    glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );    
}

And here's where I get the ratio ( with printfs inserted for testing )

Code:
void CelestialBody::readOcclusionQueryResult( void )
{    
    if ( _firstOcclusionCheck )
    {
        _firstOcclusionCheck = false;
        return;
    }

    if ( _queryIDs[0] > 0 && _queryIDs[1] > 0 )
    {
        GLint fragmentsThatShouldPass = 0, fragmentsThatDidPass = 0,
              available[2];
              
        glGetQueryObjectiv( _queryIDs[0],GL_QUERY_RESULT_AVAILABLE, &available[0] );
        glGetQueryObjectiv( _queryIDs[1],GL_QUERY_RESULT_AVAILABLE, &available[1] );

        if ( available[0] && available[1] )
        {
            glGetQueryObjectiv( _queryIDs[0], GL_QUERY_RESULT, &fragmentsThatShouldPass );
            glGetQueryObjectiv( _queryIDs[1], GL_QUERY_RESULT, &fragmentsThatDidPass );
            
            printf( "fragmentsThatShouldPass: %d fragmentsThatDidPass: %d\n",
                fragmentsThatShouldPass, fragmentsThatDidPass );

            if ( fragmentsThatShouldPass == 0 )
            {
                /*
                    This is a delicate situation. If the drawn image is offscreen,
                    it's not testable, so we've got to use a raycast
                */
                
                Camera *camera = world()->activeCamera();
                
                if ( camera )
                {

                    float d = camera->looking() * _dir;

                    /*
                        No point going further if we're facing away
                    */
                    if ( d < EPSILON )
                    {
                        _occlusionQueryResult = 0.0f;
                    }
                    else
                    {
                        _occlusionQueryResult = _ray.cast( camera->position(), _dir, dInfinity, false ).didCollide ?
                                                0.0f : 1.0f;
                    }
                }
                else
                {
                    _occlusionQueryResult = 0.0f;
                }
            }
            else
            {
                _occlusionQueryResult = (float) fragmentsThatDidPass / (float) fragmentsThatShouldPass;
            }
        }
        else
        {
            printf( "Neither result is available\n" );
        }
    }

    /*
        Observe, if we can't read the queries, stick with the last result.
    */
}

As far as I know, I'm doing this correctly. I use two occlusion query objects, and for performance purposes, instead of performing the test and then reading back the result, I always use the result of the test made in the last frame, so I don't stall the pipeline.

Any ideas? Please?

I have a fallback ( for older cards ) that uses ray intersection tests, but it's not as nice looking... I'd rather not have to use it everywhere.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #2
Hah! I posted this more than a year ago, and just yesterday came across the same bug again in my new code. Googling for occlusion query and GL_DEPTH_TEST got me my own post from 13 months ago!

But, last night I was able to solve it, and I know this works on ATI now, in case anybody finds this thread in the future.

The answer is, when you're rendering two occlusion queries, one without depth testing to get a reference fragment count and a second with testing to get an actual passed-fragment count, instead of disabling GL_DEPTH_TEST, use glDepthFunc( GL_ALWAYS ).

It works on ATI, and gives you an accurate percentage-visible metric.

[Image: OcclusionQueries.png]
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #3
ATI hardware requires depth test to be on during occlusion query (they are counting fragments that "would modify the depth buffer".)

My interpretation of the spec is that occlusion query is supposed to count fragments that "pass the stencil & depth tests", which is different from what their hardware is actually doing.

I've had some discussions with ATI about this, but since this is a fundamental hardware design issue, I think your workaround of GL_ALWAYS is all you can do.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #4
arekkusu Wrote:ATI hardware requires depth test to be on during occlusion query (they are counting fragments that "would modify the depth buffer".)

That's interesting to me, since I run this code with the color and depth masks all locked against writing.

Screwy! Wacko
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #5
I think "would" is the operative word here. DepthMask happens at the end of the pipeline, after depth test pass/fail has been determined.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  SOIL_load_OGL_texture - failing to load texture kropcke 3 7,038 Sep 5, 2012 10:08 AM
Last Post: kropcke
  query on Level Designing mchowdhury1983 1 2,820 Nov 20, 2011 12:08 PM
Last Post: sealfin
  2D Pixel Collision Detection using OCCLUSION Elphaba 0 3,279 Jun 8, 2009 06:30 AM
Last Post: Elphaba
  Occlusion query failing for fogged occluders TomorrowPlusX 12 6,344 Jan 8, 2006 05:03 PM
Last Post: arekkusu
  gl errors with occlusion queries TomorrowPlusX 1 2,411 Oct 12, 2005 10:08 AM
Last Post: TomorrowPlusX