//========= Copyright Valve Corporation, All rights reserved. ============//
// TOGL CODE LICENSE
//
// Copyright 2011-2014 Valve Corporation
// All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// cglmquery.cpp
//
//===============================================================================
# include "togles/rendermechanism.h"
# ifndef _WIN32
# include <unistd.h>
# endif
// memdbgon -must- be the last include file in a .cpp file.
# include "tier0/memdbgon.h"
//===============================================================================
// http://www.opengl.org/registry/specs/ARB/occlusion_query.txt
// Workaround for "Calling either GenQueriesARB or DeleteQueriesARB while any query of any target is active causes an INVALID_OPERATION error to be generated."
uint CGLMQuery : : s_nTotalOcclusionQueryCreatesOrDeletes ;
extern ConVar gl_errorcheckall ;
extern ConVar gl_errorcheckqueries ;
extern ConVar gl_errorchecknone ;
// how many microseconds to wait after a failed query-available test
// presently on MTGL this doesn't happen, but it could change, keep this handy
ConVar gl_nullqueries ( " gl_nullqueries " , " 0 " ) ;
//===============================================================================
CGLMQuery : : CGLMQuery ( GLMContext * ctx , GLMQueryParams * params )
{
// get the type of query requested
// generate name(s) needed
// set initial state appropriately
m_ctx = ctx ;
m_params = * params ;
m_name = 0 ;
m_syncobj = 0 ;
m_started = m_stopped = m_done = false ;
m_nullQuery = false ;
// assume value of convar at start time
// does not change during individual query lifetime
// started null = stays null
// started live = stays live
switch ( m_params . m_type )
{
case EOcclusion :
{
//make an occlusion query (and a fence to go with it)
gGL - > glGenQueries ( 1 , & m_name ) ;
s_nTotalOcclusionQueryCreatesOrDeletes + + ;
GLMPRINTF ( ( " -A- CGLMQuery(OQ) created name %d " , m_name ) ) ;
}
break ;
case EFence :
//make a fence - no aux fence needed
m_syncobj = 0 ;
if ( gGL - > m_bHave_GL_ARB_sync )
{ /* GL_ARB_sync doesn't separate gen and set, so we do glFenceSync() later. */ }
else if ( gGL - > m_bHave_GL_NV_fence )
gGL - > glGenFencesNV ( 1 , & m_name ) ;
else if ( gGL - > m_bHave_GL_APPLE_fence )
gGL - > glGenFencesAPPLE ( 1 , & m_name ) ;
GLMPRINTF ( ( " -A- CGLMQuery(fence) created name %d " , m_name ) ) ;
break ;
}
}
CGLMQuery : : ~ CGLMQuery ( )
{
GLMPRINTF ( ( " -A-> ~CGLMQuery " ) ) ;
// make sure query has completed (might not be necessary)
// delete the name(s)
switch ( m_params . m_type )
{
case EOcclusion :
{
// do a finish occlusion query ?
GLMPRINTF ( ( " -A- ~CGLMQuery(OQ) deleting name %d " , m_name ) ) ;
gGL - > glDeleteQueries ( 1 , & m_name ) ;
s_nTotalOcclusionQueryCreatesOrDeletes + + ;
}
break ;
case EFence :
{
// do a finish fence ?
GLMPRINTF ( ( " -A- ~CGLMQuery(fence) deleting name %llu " , gGL - > m_bHave_GL_ARB_sync ? ( unsigned long long ) m_syncobj : ( unsigned long long ) m_name ) ) ;
# ifdef HAVE_GL_ARB_SYNC
if ( gGL - > m_bHave_GL_ARB_sync )
gGL - > glDeleteSync ( m_syncobj ) ;
else
# endif
if ( gGL - > m_bHave_GL_NV_fence )
gGL - > glDeleteFencesNV ( 1 , & m_name ) ;
else if ( gGL - > m_bHave_GL_APPLE_fence )
gGL - > glDeleteFencesAPPLE ( 1 , & m_name ) ;
}
break ;
}
m_name = 0 ;
m_syncobj = 0 ;
GLMPRINTF ( ( " -A-< ~CGLMQuery " ) ) ;
}
void CGLMQuery : : Start ( void ) // "start counting"
{
m_nullQuery = ( gl_nullqueries . GetInt ( ) ! = 0 ) ; // latch value for remainder of query life
m_started = true ;
m_stopped = false ;
m_done = false ;
switch ( m_params . m_type )
{
case EOcclusion :
{
if ( m_nullQuery )
{
// do nothing..
}
else
{
gGL - > glBeginQuery ( GL_ANY_SAMPLES_PASSED , m_name ) ;
}
}
break ;
case EFence :
# ifdef HAVE_GL_ARB_SYNC
if ( gGL - > m_bHave_GL_ARB_sync )
{
if ( m_syncobj ! = 0 ) gGL - > glDeleteSync ( m_syncobj ) ;
m_syncobj = gGL - > glFenceSync ( GL_SYNC_GPU_COMMANDS_COMPLETE , 0 ) ;
}
else
# endif
if ( gGL - > m_bHave_GL_NV_fence )
gGL - > glSetFenceNV ( m_name , GL_ALL_COMPLETED_NV ) ;
else if ( gGL - > m_bHave_GL_APPLE_fence )
gGL - > glSetFenceAPPLE ( m_name ) ;
m_stopped = true ; // caller should not call Stop on a fence, it self-stops
break ;
}
}
void CGLMQuery : : Stop ( void ) // "stop counting"
{
Assert ( m_started ) ;
if ( m_stopped )
return ;
switch ( m_params . m_type )
{
case EOcclusion :
{
if ( m_nullQuery )
{
// do nothing..
}
else
{
gGL - > glEndQuery ( GL_ANY_SAMPLES_PASSED ) ; // we are only putting the request-to-stop-counting into the cmd stream.
}
}
break ;
case EFence :
// nop - you don't "end" a fence, you just test it and/or finish it out in Complete
break ;
}
m_stopped = true ;
}
bool CGLMQuery : : IsDone ( void )
{
Assert ( m_started ) ;
Assert ( m_stopped ) ;
if ( ! m_done ) // you can ask more than once, but we only check until it comes back as done.
{
// on occlusion: glGetQueryObjectivARB - large cost on pre SLGU, cheap after
// on fence: glTestFence* on the fence
switch ( m_params . m_type )
{
case EOcclusion : // just test the fence that was set after the query begin
{
if ( m_nullQuery )
{
// do almost nothing.. but claim work is complete
m_done = true ;
}
else
{
// prepare to pay a big price on drivers prior to 10.6.4+SLGU
GLuint available = 0 ;
gGL - > glGetQueryObjectuiv ( m_name , GL_QUERY_RESULT_AVAILABLE , & available ) ;
m_done = ( available ! = 0 ) ;
}
}
break ;
case EFence :
{
# ifdef HAVE_GL_ARB_SYNC
if ( gGL - > m_bHave_GL_ARB_sync )
m_done = ( gGL - > glClientWaitSync ( m_syncobj , 0 , 0 ) = = GL_ALREADY_SIGNALED ) ;
else
# endif
if ( m_name = = 0 )
m_done = true ;
else if ( gGL - > m_bHave_GL_NV_fence )
m_done = gGL - > glTestFenceNV ( m_name ) ! = 0 ;
else if ( gGL - > m_bHave_GL_APPLE_fence )
m_done = gGL - > glTestFenceAPPLE ( m_name ) ! = 0 ;
if ( m_done )
{
if ( gGL - > m_bHave_GL_ARB_sync )
{ /* no-op; we already know it's set to GL_ALREADY_SIGNALED. */ }
else
{
if ( gGL - > m_bHave_GL_NV_fence )
gGL - > glFinishFenceNV ( m_name ) ; // no set fence goes un-finished
else if ( gGL - > m_bHave_GL_APPLE_fence )
gGL - > glFinishFenceAPPLE ( m_name ) ; // no set fence goes un-finished
}
}
}
break ;
}
}
return m_done ;
}
void CGLMQuery : : Complete ( uint * result )
{
uint resultval = 0 ;
//bool bogus_available = false;
// blocking call if not done
Assert ( m_started ) ;
Assert ( m_stopped ) ;
switch ( m_params . m_type )
{
case EOcclusion :
{
if ( m_nullQuery )
{
m_done = true ;
resultval = 0 ; // we did say "null queries..."
}
else
{
gGL - > glGetQueryObjectuiv ( m_name , GL_QUERY_RESULT , & resultval ) ;
m_done = true ;
}
}
break ;
case EFence :
{
if ( ! m_done )
{
# ifdef HAVE_GL_ARB_SYNC
if ( gGL - > m_bHave_GL_ARB_sync )
{
if ( gGL - > glClientWaitSync ( m_syncobj , 0 , 0 ) ! = GL_ALREADY_SIGNALED )
{
GLenum syncstate ;
do {
const GLuint64 timeout = 10 * ( ( GLuint64 ) 1000 * 1000 * 1000 ) ; // 10 seconds in nanoseconds.
( void ) timeout ;
syncstate = gGL - > glClientWaitSync ( m_syncobj , GL_SYNC_FLUSH_COMMANDS_BIT , 0 ) ;
} while ( syncstate = = GL_TIMEOUT_EXPIRED ) ; // any errors or success break out of this loop.
}
}
else
# endif
if ( gGL - > m_bHave_GL_NV_fence )
gGL - > glFinishFenceNV ( m_name ) ;
else if ( gGL - > m_bHave_GL_APPLE_fence )
gGL - > glFinishFenceAPPLE ( m_name ) ;
m_done = true ; // for clarity or if they try to Complete twice
}
}
break ;
}
Assert ( m_done ) ;
// reset state for re-use - i.e. you have to call Complete if you want to re-use the object
m_started = m_stopped = m_done = false ;
if ( result ) // caller may pass NULL if not interested in result, for example to clear a fence
{
* result = resultval ;
}
}
// accessors for the started/stopped state
bool CGLMQuery : : IsStarted ( void )
{
return m_started ;
}
bool CGLMQuery : : IsStopped ( void )
{
return m_stopped ;
}