//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================

#include "pch_materialsystem.h"

#define MATSYS_INTERNAL

#include "occlusionquerymgr.h"
#include "imaterialsysteminternal.h"
#include "imatrendercontextinternal.h"

// NOTE: This must be the last file included!!!
#include "tier0/memdbgon.h"


//-----------------------------------------------------------------------------
// Singleton
//-----------------------------------------------------------------------------
static COcclusionQueryMgr s_OcclusionQueryMgr;
COcclusionQueryMgr *g_pOcclusionQueryMgr = &s_OcclusionQueryMgr;


//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
COcclusionQueryMgr::COcclusionQueryMgr()
{
	m_nFrameCount = 0;
}


//-----------------------------------------------------------------------------
// Allocate and delete query objects.
//-----------------------------------------------------------------------------
OcclusionQueryObjectHandle_t COcclusionQueryMgr::CreateOcclusionQueryObject( )
{
	m_Mutex.Lock();
	intp h = m_OcclusionQueryObjects.AddToTail();
	m_Mutex.Unlock();
	return (OcclusionQueryObjectHandle_t)h;
}

void COcclusionQueryMgr::OnCreateOcclusionQueryObject( OcclusionQueryObjectHandle_t h )
{
	for ( int i = 0; i < COUNT_OCCLUSION_QUERY_STACK; i++)
	{
		m_OcclusionQueryObjects[(intp)h].m_QueryHandle[i] = g_pShaderAPI->CreateOcclusionQueryObject( );
	}
}

// Flushes an outstanding query
// HEY - Be very careful using this method - it causes a full pipeline flush/stall!
void COcclusionQueryMgr::FlushQuery( OcclusionQueryObjectHandle_t hOcclusionQuery, int nIndex )
{
	// Flush out any previous queries
    intp h = (intp)hOcclusionQuery;
	if ( m_OcclusionQueryObjects[h].m_bHasBeenIssued[nIndex] )
	{
		ShaderAPIOcclusionQuery_t hQuery = m_OcclusionQueryObjects[h].m_QueryHandle[nIndex];
		
		while ( OCCLUSION_QUERY_RESULT_PENDING == g_pShaderAPI->OcclusionQuery_GetNumPixelsRendered( hQuery, true ) )
			continue;
	}
}

void COcclusionQueryMgr::DestroyOcclusionQueryObject( OcclusionQueryObjectHandle_t hOcclusionQuery )
{
    intp h = (intp)hOcclusionQuery;
	Assert( m_OcclusionQueryObjects.IsValidIndex( h ) );
	if ( m_OcclusionQueryObjects.IsValidIndex( h ) )
	{
		for ( int i = 0; i < COUNT_OCCLUSION_QUERY_STACK; i++)
		{
			if ( m_OcclusionQueryObjects[h].m_QueryHandle[i] != INVALID_SHADERAPI_OCCLUSION_QUERY_HANDLE )
			{
				g_pShaderAPI->DestroyOcclusionQueryObject( m_OcclusionQueryObjects[h].m_QueryHandle[i] );
			}
		}
		m_Mutex.Lock();
		m_OcclusionQueryObjects.Remove( h );
		m_Mutex.Unlock();
	}
}


//-----------------------------------------------------------------------------
// Advance frame
//-----------------------------------------------------------------------------
void COcclusionQueryMgr::AdvanceFrame()
{
	++m_nFrameCount;
}


//-----------------------------------------------------------------------------
// Alt-tab support
// NOTE: This doesn't queue anything up
//-----------------------------------------------------------------------------
void COcclusionQueryMgr::AllocOcclusionQueryObjects( void )
{
	FOR_EACH_LL( m_OcclusionQueryObjects, iterator )
	{
		for ( int i = 0; i < COUNT_OCCLUSION_QUERY_STACK; i++)
		{
			m_OcclusionQueryObjects[iterator].m_QueryHandle[i] = g_pShaderAPI->CreateOcclusionQueryObject();
			m_OcclusionQueryObjects[iterator].m_bHasBeenIssued[i] = false;		// any in-flight queries are never returning
		}
	}
}

void COcclusionQueryMgr::FreeOcclusionQueryObjects( void )
{
	FOR_EACH_LL( m_OcclusionQueryObjects, iterator )
	{
		for ( int i = 0; i < COUNT_OCCLUSION_QUERY_STACK; i++)
		{
			if ( m_OcclusionQueryObjects[iterator].m_QueryHandle[i] != INVALID_SHADERAPI_OCCLUSION_QUERY_HANDLE )
			{
				g_pShaderAPI->DestroyOcclusionQueryObject( m_OcclusionQueryObjects[iterator].m_QueryHandle[i] );
				m_OcclusionQueryObjects[iterator].m_QueryHandle[i] = INVALID_SHADERAPI_OCCLUSION_QUERY_HANDLE;
				m_OcclusionQueryObjects[iterator].m_bHasBeenIssued[i] = false;
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Used to make the handle think it's never had a successful query before
//-----------------------------------------------------------------------------
void COcclusionQueryMgr::ResetOcclusionQueryObject( OcclusionQueryObjectHandle_t hOcclusionQuery )
{
    intp h = (intp)hOcclusionQuery;
	Assert( m_OcclusionQueryObjects.IsValidIndex( h ) );
	if ( m_OcclusionQueryObjects.IsValidIndex( h ) )
	{
		// Forget we've issued any previous queries - there's no need to flush them.
		for ( int i = 0; i < COUNT_OCCLUSION_QUERY_STACK; i++)
		{
			m_OcclusionQueryObjects[h].m_bHasBeenIssued[i] = false;
		}

		m_OcclusionQueryObjects[h].m_LastResult = -1;
		m_OcclusionQueryObjects[h].m_nFrameIssued = -1;
	}
}


//-----------------------------------------------------------------------------
// Bracket drawing with begin and end so that we can get counts next frame.
//-----------------------------------------------------------------------------
void COcclusionQueryMgr::BeginOcclusionQueryDrawing( OcclusionQueryObjectHandle_t hOcclusionQuery )
{
    intp h = (intp)hOcclusionQuery;
	Assert( m_OcclusionQueryObjects.IsValidIndex( h ) );
	if ( m_OcclusionQueryObjects.IsValidIndex( h ) )
	{
		int nCurrent = m_OcclusionQueryObjects[h].m_nCurrentIssue;
		ShaderAPIOcclusionQuery_t hQuery = m_OcclusionQueryObjects[h].m_QueryHandle[nCurrent];
		if ( hQuery != INVALID_SHADERAPI_OCCLUSION_QUERY_HANDLE )
		{
			// If it's been issued, but we haven't gotten a result when we polled last time,
			// try polling one last time, since we can't poll again after we issue again.
			if ( m_OcclusionQueryObjects[h].m_bHasBeenIssued[nCurrent] )
			{
				int nPixels = g_pShaderAPI->OcclusionQuery_GetNumPixelsRendered( hQuery, false );
				if ( ( nPixels == OCCLUSION_QUERY_RESULT_PENDING ) && ( m_OcclusionQueryObjects[h].m_nFrameIssued == m_nFrameCount ) )
				{
					static int s_nWarnCount = 0;
					if ( s_nWarnCount++ < 5 )
					{
						DevWarning( "blocking issue in occlusion queries! Grab brian!\n" );
					}
				}
				while( !OCCLUSION_QUERY_FINISHED( nPixels ) ) 
				{
					// We're going to reuse this query, so issue a flush to force the query results to come back.
					nPixels = g_pShaderAPI->OcclusionQuery_GetNumPixelsRendered( hQuery, true );
				}
				if ( nPixels >= 0 )
				{
					m_OcclusionQueryObjects[h].m_LastResult = nPixels;
				}
				m_OcclusionQueryObjects[h].m_bHasBeenIssued[nCurrent] = false;
			}
			g_pShaderAPI->BeginOcclusionQueryDrawing( hQuery );
		}
	}
}


void COcclusionQueryMgr::EndOcclusionQueryDrawing( OcclusionQueryObjectHandle_t hOcclusionQuery )
{
    intp h = (intp)hOcclusionQuery;
	Assert( m_OcclusionQueryObjects.IsValidIndex( h ) );
	if ( m_OcclusionQueryObjects.IsValidIndex( h ) )
	{
		int nCurrent = m_OcclusionQueryObjects[h].m_nCurrentIssue;
		ShaderAPIOcclusionQuery_t hQuery = m_OcclusionQueryObjects[h].m_QueryHandle[nCurrent];
		if ( hQuery != INVALID_SHADERAPI_OCCLUSION_QUERY_HANDLE )
		{
			g_pShaderAPI->EndOcclusionQueryDrawing( hQuery );

			m_OcclusionQueryObjects[h].m_bHasBeenIssued[nCurrent] = true;
			m_OcclusionQueryObjects[h].m_nFrameIssued = m_nFrameCount;

			nCurrent = ( nCurrent + 1 ) % COUNT_OCCLUSION_QUERY_STACK;
			m_OcclusionQueryObjects[h].m_nCurrentIssue = nCurrent;
		}
	}
}


//-----------------------------------------------------------------------------
// Get the number of pixels rendered between begin and end on an earlier frame.
// Calling this in the same frame is a huge perf hit!
//-----------------------------------------------------------------------------
void COcclusionQueryMgr::OcclusionQuery_IssueNumPixelsRenderedQuery( OcclusionQueryObjectHandle_t hOcclusionQuery )
{
    intp h = (intp)hOcclusionQuery;
	Assert( m_OcclusionQueryObjects.IsValidIndex( h ) );
	if ( m_OcclusionQueryObjects.IsValidIndex( h ) )
	{
		for( int i = 0; i < COUNT_OCCLUSION_QUERY_STACK; i++ )
		{
			int nIndex = ( m_OcclusionQueryObjects[h].m_nCurrentIssue + i ) % COUNT_OCCLUSION_QUERY_STACK;
			ShaderAPIOcclusionQuery_t hQuery = m_OcclusionQueryObjects[h].m_QueryHandle[nIndex];
			if ( hQuery != INVALID_SHADERAPI_OCCLUSION_QUERY_HANDLE && m_OcclusionQueryObjects[h].m_bHasBeenIssued[nIndex] )
			{
				int nPixels = g_pShaderAPI->OcclusionQuery_GetNumPixelsRendered( hQuery );
				if ( nPixels == OCCLUSION_QUERY_RESULT_ERROR )
				{
					// In GL mode, it's possible for queries to fail (say when mat_queue_mode is toggled). In this case, just clear m_bHasBeenIssued and forget we ever issued this query.
					m_OcclusionQueryObjects[h].m_bHasBeenIssued[nIndex] = false;
				}
				else if ( nPixels >= 0 )
				{
					m_OcclusionQueryObjects[h].m_LastResult = nPixels;
					m_OcclusionQueryObjects[h].m_bHasBeenIssued[nIndex] = false;
				}
			}
		}
	}
}

int COcclusionQueryMgr::OcclusionQuery_GetNumPixelsRendered( OcclusionQueryObjectHandle_t h, bool bDoQuery )
{
	if ( bDoQuery )
	{
		OcclusionQuery_IssueNumPixelsRenderedQuery( h );
	}

	int nPixels = m_OcclusionQueryObjects[(intp)h].m_LastResult;
	return nPixels;
}