source-engine/togles/linuxwin/cglmquery.cpp

364 lines
8.9 KiB
C++

//========= 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_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_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;
}