source-engine/togles/linuxwin/glmgr.cpp
2022-11-20 15:00:44 +02:00

5922 lines
161 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//========= 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.
//
// glmgr.cpp
//
//===============================================================================
#include "togles/rendermechanism.h"
#include "tier0/icommandline.h"
#include "tier0/vprof.h"
#include "glmtexinlines.h"
#include "materialsystem/IShader.h"
#include "appframework/ilaunchermgr.h"
#include "convar.h"
#include "glmgr_flush.inl"
#ifdef OSX
#include <OpenGL/OpenGL.h>
#include "intelglmallocworkaround.h"
#endif
// memdbgon -must- be the last include file in a .cpp file.
#include "tier0/memdbgon.h"
// Whether the code should use gl_arb_debug_output. This causes error messages to be streamed, via callback, to the application.
// It is much friendlier to the MTGL driver.
// NOTE: This can be turned off after launch, but it cannot be turned on after launch--it implies a context-creation-time
// behavior.
ConVar gl_debug_output( "gl_debug_output", "1" );
// Whether or not we should batch up our creation and deletion behavior.
ConVar gl_batch_tex_creates( "gl_batch_tex_creates", "0" );
ConVar gl_batch_tex_destroys( "gl_batch_tex_destroys", "0" );
//===============================================================================
// g_nTotalDrawsOrClears is reset to 0 in Present()
uint g_nTotalDrawsOrClears, g_nTotalVBLockBytes, g_nTotalIBLockBytes;
#if GL_TELEMETRY_GPU_ZONES
TelemetryGPUStats_t g_TelemetryGPUStats;
#endif
const int kGLMInitialTexCount = 4096;
const int kGLMReUpTexCount = 1024;
const int kGLMHighWaterUndeleted = 2048;
const int kDeletedTextureDim = 4;
const uint32 g_garbageTextureBits[ 4 * kDeletedTextureDim * kDeletedTextureDim ] = { 0 };
extern void CompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data);
extern void convert_texture( GLenum &internalformat, GLsizei width, GLsizei height, GLenum &format, GLenum &type, void *data );
char g_nullFragmentProgramText [] =
{
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 _gl_FragColor;\n"
"void main()\n"
"{\n"
"_gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n"
"}\n"
};
// make dummy programs for doing texture preload via dummy draw
char g_preloadTexVertexProgramText[] = // Гроб гроб кладбище пидор
{
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 otex;\n"
"void main() \n"
"{\n"
"vec4 pos = vec4( 0.1, 0.1, 0.1, 0.1 );\n"
"vec4 tex = vec4( 0.0, 0.0, 0.0, 0.0 );\n"
"\n"
"gl_Position = pos;\n"
"otex = tex; \n"
"} \n"
};
char g_preload2DTexFragmentProgramText[] =
{
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 _gl_FragColor;\n"
"in vec4 otex;\n"
"//SAMPLERMASK-8000 // may not be needed \n"
"//HIGHWATER-30 // may not be needed \n"
" \n"
"uniform vec4 pc[31]; \n"
"uniform sampler2D sampler15; \n"
" \n"
"void main() \n"
"{ \n"
"vec4 r0; \n"
"r0 = texture( sampler15, otex.xy ); \n"
"_gl_FragColor = r0; //discard; \n"
"} \n"
};
char g_preload3DTexFragmentProgramText[] =
{
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 _gl_FragColor;\n"
"in vec4 otex;\n"
"//SAMPLERMASK-8000 // may not be needed \n"
"//HIGHWATER-30 // may not be needed \n"
" \n"
"precision mediump sampler3D;\n"
"uniform vec4 pc[31]; \n"
"uniform sampler3D sampler15; \n"
" \n"
"void main() \n"
"{ \n"
"vec4 r0; \n"
"r0 = texture( sampler15, otex.xyz ); \n"
"_gl_FragColor = vec4(0,0,0,0); //discard; \n"
"} \n"
};
char g_preloadCubeTexFragmentProgramText[] =
{
"#version 300 es\n"
"precision mediump float;\n"
"in vec4 otex;\n"
"out vec4 _gl_FragColor;\n"
"//SAMPLERMASK-8000 // may not be needed \n"
"//HIGHWATER-30 // may not be needed \n"
" \n"
"uniform vec4 pc[31]; \n"
"uniform samplerCube sampler15; \n"
" \n"
"void main() \n"
"{ \n"
"vec4 r0; \n"
"r0 = texture( sampler15, otex.xyz ); \n"
"_gl_FragColor = r0; //discard; \n"
"} \n"
};
const char* glSourceToString(GLenum source)
{
switch (source)
{
case GL_DEBUG_SOURCE_API_ARB: return "API";
case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: return "WINDOW_SYSTEM";
case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: return "SHADER_COMPILER";
case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: return "THIRD_PARTY";
case GL_DEBUG_SOURCE_APPLICATION_ARB: return "APPLICATION";
case GL_DEBUG_SOURCE_OTHER_ARB: return "OTHER";
default: break;
}
return "UNKNOWN";
}
const char* glTypeToString(GLenum type)
{
switch (type)
{
case GL_DEBUG_TYPE_ERROR_ARB: return "ERROR";
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: return "DEPRECATION";
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: return "UNDEFINED_BEHAVIOR";
case GL_DEBUG_TYPE_PORTABILITY_ARB: return "PORTABILITY";
case GL_DEBUG_TYPE_PERFORMANCE_ARB: return "PERFORMANCE";
case GL_DEBUG_TYPE_OTHER_ARB: return "OTHER";
default: break;
}
return "UNKNOWN";
}
const char* glSeverityToString(GLenum severity)
{
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH_ARB: return "HIGH";
case GL_DEBUG_SEVERITY_MEDIUM_ARB: return "MEDIUM";
case GL_DEBUG_SEVERITY_LOW_ARB: return "LOW";
default: break;
}
return "UNKNOWN";
}
bool g_bDebugOutputBreakpoints = true;
void APIENTRY GL_Debug_Output_Callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
{
const char *sSource = glSourceToString(source),
*sType = glTypeToString(type),
*sSeverity = glSeverityToString(severity);
// According to NVidia, this error is a bug in the driver and not really an error (it's a warning in newer drivers): "Texture X is base level inconsistent. Check texture size"
if ( ( type == GL_DEBUG_TYPE_ERROR_ARB ) && strstr( message, "base level inconsistent" ) )
{
return;
}
if ( gl_debug_output.GetBool() || type == GL_DEBUG_TYPE_ERROR_ARB || type == GL_DEBUG_SEVERITY_MEDIUM_ARB )
{
Msg( "GL: [%s][%s][%s][%d]: %s\n", sSource, sType, sSeverity, id, message );
}
#ifdef WIN32
OutputDebugStringA( message );
#endif
if ( ( type == GL_DEBUG_TYPE_ERROR_ARB ) && ( g_bDebugOutputBreakpoints ) )
{
// DebuggerBreak();
}
}
void GLMDebugPrintf( const char *pMsg, ... )
{
//$ TODO: Should this call Warning()?
//$ TODO: Should replace call these calls with Warning() / Msg() / DevMsg()?
va_list args;
va_start( args, pMsg );
vprintf( pMsg, args );
va_end( args );
}
//===============================================================================
// functions that are dependant on g_pLauncherMgr
inline bool MakeContextCurrent( PseudoGLContextPtr hContext )
{
return g_pLauncherMgr->MakeContextCurrent( hContext );
}
inline PseudoGLContextPtr GetMainContext()
{
return g_pLauncherMgr->GetMainContext();
}
inline PseudoGLContextPtr GetGLContextForWindow( void* windowref )
{
return g_pLauncherMgr->GetGLContextForWindow( windowref );
}
inline void IncrementWindowRefCount()
{
g_pLauncherMgr->IncWindowRefCount();
}
inline void DecrementWindowRefCount()
{
g_pLauncherMgr->DecWindowRefCount();
}
inline void ShowPixels( CShowPixelsParams *params )
{
g_pLauncherMgr->ShowPixels(params);
}
inline void DisplayedSize( uint &width, uint &height )
{
g_pLauncherMgr->DisplayedSize( width, height );
}
inline void GetDesiredPixelFormatAttribsAndRendererInfo( uint **ptrOut, uint *countOut, GLMRendererInfoFields *rendInfoOut )
{
g_pLauncherMgr->GetDesiredPixelFormatAttribsAndRendererInfo( ptrOut, countOut, rendInfoOut );
}
inline void GetStackCrawl( CStackCrawlParams *params )
{
g_pLauncherMgr->GetStackCrawl(params);
}
#if GLMDEBUG
inline void PumpWindowsMessageLoop()
{
g_pLauncherMgr->PumpWindowsMessageLoop();
}
inline int GetEvents( CCocoaEvent *pEvents, int nMaxEventsToReturn, bool debugEvents = false )
{
return g_pLauncherMgr->GetEvents( pEvents, nMaxEventsToReturn, debugEvents );
}
#endif
//===============================================================================
// helper routines for debug
static bool hasnonzeros( float *values, int count )
{
for( int i=0; i<count; i++)
{
if (values[i] != 0.0)
{
return true;
}
}
return false;
}
static void printmat( char *label, int baseSlotNumber, int slots, float *m00 )
{
// print label..
// fetch 4 from row, print as a row
// fetch 4 from column, print as a row
float row[4];
float col[4];
if (hasnonzeros( m00, slots*4) )
{
GLMPRINTF(("-D- %s", label ));
for( int islot=0; islot<4; islot++ ) // we always run this loop til 4, but we special case the printing if there are only 3 submitted
{
// extract row and column floats
for( int slotcol=0; slotcol<4; slotcol++)
{
//copy
row[slotcol] = m00[(islot*4)+slotcol];
// transpose
col[slotcol] = m00[(slotcol*4)+islot];
}
if (slots==4)
{
GLMPRINTF(( "-D- %03d: [ %10.5f %10.5f %10.5f %10.5f ] T=> [ %10.5f %10.5f %10.5f %10.5f ]",
baseSlotNumber+islot,
row[0],row[1],row[2],row[3],
col[0],col[1],col[2],col[3]
));
}
else
{
if (islot<3)
{
GLMPRINTF(( "-D- %03d: [ %10.5f %10.5f %10.5f %10.5f ] T=> [ %10.5f %10.5f %10.5f ]",
baseSlotNumber+islot,
row[0],row[1],row[2],row[3],
col[0],col[1],col[2]
));
}
else
{
GLMPRINTF(( "-D- %03d: T=> [ %10.5f %10.5f %10.5f ]",
baseSlotNumber+islot,
col[0],col[1],col[2]
));
}
}
}
GLMPRINTSTR(("-D-"));
}
else
{
GLMPRINTF(("-D- %s - (all 0.0)", label ));
}
}
static void transform_dp4( float *in4, float *m00, int slots, float *out4 )
{
// m00 points to a column.
// each DP is one column of the matrix ( m00[4*n]
// if we are passed a three slot matrix, this is three columns, the source W plays into all three columns, but we must set the final output W to 1 ?
for( int n=0; n<slots; n++)
{
float col4[4];
col4[0] = m00[(4*n)+0];
col4[1] = m00[(4*n)+1];
col4[2] = m00[(4*n)+2];
col4[3] = m00[(4*n)+3];
out4[n] = 0.0;
for( int inner = 0; inner < 4; inner++ )
{
out4[n] += in4[inner] * col4[inner];
}
}
if (slots==3)
{
out4[3] = 1.0;
}
}
//===============================================================================
//===============================================================================
// GLMgr static methods
GLMgr *g_glmgr = NULL;
void GLMgr::NewGLMgr( void )
{
if (!g_glmgr)
{
#if GLMDEBUG
// check debug mode early in program lifetime
GLMDebugInitialize( true );
#endif
g_glmgr = new GLMgr;
}
}
GLMgr *GLMgr::aGLMgr( void )
{
assert( g_glmgr != NULL);
return g_glmgr;
}
void GLMgr::DelGLMgr( void )
{
if (g_glmgr)
{
delete g_glmgr;
g_glmgr = NULL;
}
}
// GLMgr class methods
GLMgr::GLMgr()
{
}
GLMgr::~GLMgr()
{
}
//===============================================================================
GLMContext *GLMgr::NewContext( IDirect3DDevice9 *pDevice, GLMDisplayParams *params )
{
// this now becomes really simple. We just pass through the params.
return new GLMContext( pDevice, params );
}
void GLMgr::DelContext( GLMContext *context )
{
delete context;
}
void GLMgr::SetCurrentContext( GLMContext *context )
{
#if defined( USE_SDL )
context->m_nCurOwnerThreadId = ThreadGetCurrentId();
if ( !MakeContextCurrent( context->m_ctx ) )
{
// give up
GLMStop();
}
Assert( 0 );
#endif
}
GLMContext *GLMgr::GetCurrentContext( void )
{
#if defined( USE_SDL )
PseudoGLContextPtr context = GetMainContext();
return (GLMContext*) context;
#else
Assert( 0 );
return NULL;
#endif
}
// #define CHECK_THREAD_USAGE 1
//===============================================================================
// GLMContext public methods
void GLMContext::MakeCurrent( bool bRenderThread )
{
tmZone( TELEMETRY_LEVEL0, 0, "GLMContext::MakeCurrent" );
Assert( m_nCurOwnerThreadId == 0 || m_nCurOwnerThreadId == ThreadGetCurrentId() );
#if defined( USE_SDL )
#ifndef CHECK_THREAD_USAGE
if ( bRenderThread )
{
// Msg( "******************************************** %08x Acquiring Context\n", ThreadGetCurrentId() );
m_nCurOwnerThreadId = ThreadGetCurrentId();
bool bSuccess = MakeContextCurrent( m_ctx );
if ( !bSuccess )
{
Assert( 0 );
}
}
#else
uint32 dwThreadId = ThreadGetCurrentId();
if ( bRenderThread || dwThreadId == m_dwRenderThreadId )
{
m_nCurOwnerThreadId = ThreadGetCurrentId();
m_dwRenderThreadId = dwThreadId;
MakeContextCurrent( m_ctx );
m_bIsThreading = true;
}
else if ( !m_bIsThreading )
{
m_nCurOwnerThreadId = ThreadGetCurrentId();
MakeContextCurrent( m_ctx );
}
else
{
Assert( 0 );
}
#endif
#else
Assert( 0 );
#endif
}
void GLMContext::ReleaseCurrent( bool bRenderThread )
{
tmZone( TELEMETRY_LEVEL0, 0, "GLMContext::ReleaseCurrent" );
Assert( m_nCurOwnerThreadId == ThreadGetCurrentId() );
#if defined( USE_SDL )
#ifndef CHECK_THREAD_USAGE
if ( bRenderThread )
{
// Msg( "******************************************** %08x Releasing Context\n", ThreadGetCurrentId() );
m_nCurOwnerThreadId = 0;
m_nThreadOwnershipReleaseCounter++;
MakeContextCurrent( NULL );
}
#else
m_nCurOwnerThreadId = 0;
m_nThreadOwnershipReleaseCounter++;
MakeContextCurrent( NULL );
if ( bRenderThread )
{
m_bIsThreading = false;
}
#endif
#else
Assert( 0 );
#endif
}
// This function forces all GL state to be re-sent to the context. Some state will only be set on the next batch flush.
void GLMContext::ForceFlushStates()
{
// Flush various render states
m_AlphaTestEnable.Flush();
m_AlphaTestFunc.Flush();
m_DepthBias.Flush();
m_ScissorEnable.Flush();
m_ScissorBox.Flush();
m_ViewportBox.Flush();
m_ViewportDepthRange.Flush();
m_ColorMaskSingle.Flush();
m_BlendEnable.Flush();
m_BlendFactor.Flush();
m_BlendEnableSRGB.Flush();
m_DepthTestEnable.Flush();
m_DepthFunc.Flush();
m_DepthMask.Flush();
m_StencilTestEnable.Flush();
m_StencilFunc.Flush();
m_StencilOp.Flush();
m_StencilWriteMask.Flush();
m_ClearColor.Flush();
m_ClearDepth.Flush();
m_ClearStencil.Flush();
m_ClipPlaneEnable.Flush(); // always push clip state
m_ClipPlaneEquation.Flush();
m_CullFaceEnable.Flush();
m_CullFrontFace.Flush();
m_PolygonMode.Flush();
m_AlphaToCoverageEnable.Flush();
m_ColorMaskMultiple.Flush();
m_BlendEquation.Flush();
m_BlendColor.Flush();
// Reset various things so they get reset on the next batch flush
m_activeTexture = -1;
for ( int i = 0; i < GLM_SAMPLER_COUNT; i++ )
{
SetSamplerTex( i, m_samplers[i].m_pBoundTex );
SetSamplerDirty( i );
}
// Attributes/vertex attribs
ClearCurAttribs();
m_lastKnownVertexAttribMask = 0;
m_nNumSetVertexAttributes = 16;
memset( &m_boundVertexAttribs[0], 0xFF, sizeof( m_boundVertexAttribs ) );
for( int index=0; index < kGLMVertexAttributeIndexMax; index++ )
gGL->glDisableVertexAttribArray( index );
// Program
NullProgram();
// FBO
BindFBOToCtx( m_boundReadFBO, GL_READ_FRAMEBUFFER );
BindFBOToCtx( m_boundDrawFBO, GL_DRAW_FRAMEBUFFER );
// Current VB/IB/pinned memory buffers
gGL->glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_nBoundGLBuffer[ kGLMIndexBuffer] );
gGL->glBindBuffer( GL_ARRAY_BUFFER, m_nBoundGLBuffer[ kGLMVertexBuffer] );
}
const GLMRendererInfoFields& GLMContext::Caps( void )
{
return m_caps;
}
void GLMContext::DumpCaps( void )
{
/*
#define dumpfield( fff ) printf( "\n "#fff" : %d", (int) m_caps.fff )
#define dumpfield_hex( fff ) printf( "\n "#fff" : 0x%08x", (int) m_caps.fff )
#define dumpfield_str( fff ) printf( "\n "#fff" : %s", m_caps.fff )
*/
#define dumpfield( fff ) printf( "\n %-30s : %d", #fff, (int) m_caps.fff )
#define dumpfield_hex( fff ) printf( "\n %-30s : 0x%08x", #fff, (int) m_caps.fff )
#define dumpfield_str( fff ) printf( "\n %-30s : %s", #fff, m_caps.fff )
printf("\n-------------------------------- context caps for context %zx", (size_t)this);
dumpfield( m_fullscreen );
dumpfield( m_accelerated );
dumpfield( m_windowed );
dumpfield_hex( m_rendererID );
dumpfield( m_displayMask );
dumpfield( m_bufferModes );
dumpfield( m_colorModes );
dumpfield( m_accumModes );
dumpfield( m_depthModes );
dumpfield( m_stencilModes );
dumpfield( m_maxAuxBuffers );
dumpfield( m_maxSampleBuffers );
dumpfield( m_maxSamples );
dumpfield( m_sampleModes );
dumpfield( m_sampleAlpha );
dumpfield_hex( m_vidMemory );
dumpfield_hex( m_texMemory );
dumpfield_hex( m_pciVendorID );
dumpfield_hex( m_pciDeviceID );
dumpfield_str( m_pciModelString );
dumpfield_str( m_driverInfoString );
printf( "\n m_osComboVersion: 0x%08x (%d.%d.%d)", m_caps.m_osComboVersion, (m_caps.m_osComboVersion>>16)&0xFF, (m_caps.m_osComboVersion>>8)&0xFF, (m_caps.m_osComboVersion)&0xFF );
dumpfield( m_ati );
if (m_caps.m_ati)
{
dumpfield( m_atiR5xx );
dumpfield( m_atiR6xx );
dumpfield( m_atiR7xx );
dumpfield( m_atiR8xx );
dumpfield( m_atiNewer );
}
dumpfield( m_intel );
if (m_caps.m_intel)
{
dumpfield( m_intel95x );
dumpfield( m_intel3100 );
dumpfield( m_intelHD4000 );
}
dumpfield( m_nv );
if (m_caps.m_nv)
{
//dumpfield( m_nvG7x );
dumpfield( m_nvG8x );
dumpfield( m_nvNewer );
}
dumpfield( m_hasGammaWrites );
dumpfield( m_hasMixedAttachmentSizes );
dumpfield( m_hasBGRA );
dumpfield( m_hasNewFullscreenMode );
dumpfield( m_hasNativeClipVertexMode );
dumpfield( m_maxAniso );
dumpfield( m_hasBindableUniforms );
dumpfield( m_maxVertexBindableUniforms );
dumpfield( m_maxFragmentBindableUniforms );
dumpfield( m_maxBindableUniformSize );
dumpfield( m_hasUniformBuffers );
dumpfield( m_hasPerfPackage1 );
dumpfield( m_cantBlitReliably );
dumpfield( m_cantAttachSRGB );
dumpfield( m_cantResolveFlipped );
dumpfield( m_cantResolveScaled );
dumpfield( m_costlyGammaFlips );
dumpfield( m_badDriver1064NV );
dumpfield( m_badDriver108Intel );
printf("\n--------------------------------");
#undef dumpfield
#undef dumpfield_hex
#undef dumpfield_str
}
CGLMTex *GLMContext::NewTex( GLMTexLayoutKey *key, uint levels, const char *debugLabel )
{
// get a layout based on the key
GLMTexLayout *layout = m_texLayoutTable->NewLayoutRef( key );
CGLMTex *tex = new CGLMTex( this, layout, levels, debugLabel );
return tex;
}
void GLMContext::DelTex( CGLMTex * tex )
{
//Queue the texture for deletion in ProcessTextureDeletes
//when we are sure we will hold the context.
m_DeleteTextureQueue.PushItem(tex);
}
void GLMContext::ProcessTextureDeletes()
{
#if GL_TELEMETRY_GPU_ZONES
CScopedGLMPIXEvent glmEvent( "GLMContext::ProcessTextureDeletes" );
#endif
CGLMTex* tex = nullptr;
while ( m_DeleteTextureQueue.PopItem( &tex ) )
{
for( int i = 0; i < GLM_SAMPLER_COUNT; i++)
{
if ( m_samplers[i].m_pBoundTex == tex )
{
BindTexToTMU( NULL, i );
}
}
if ( tex->m_rtAttachCount != 0 )
{
// RG - huh? wtf? TODO: fix this code which seems to be purposely leaking
// leak it and complain - we may have to implement a deferred-delete system for tex like these
GLMDebugPrintf("GLMContext::DelTex: Leaking tex %08x [ %s ] - was attached for drawing at time of delete",tex, tex->m_layout->m_layoutSummary );
#if 0
// can't actually do this yet as the draw calls will tank
FOR_EACH_VEC( m_fboTable, i )
{
CGLMFBO *fbo = m_fboTable[i];
fbo->TexScrub( tex );
}
tex->m_rtAttachCount = 0;
#endif
}
else
{
delete tex;
}
}
}
// push and pop attrib when blit has mixed srgb source and dest?
ConVar gl_radar7954721_workaround_mixed ( "gl_radar7954721_workaround_mixed", "1" );
// push and pop attrib on any blit?
ConVar gl_radar7954721_workaround_all ( "gl_radar7954721_workaround_all", "0" );
// what attrib mask to use ?
ConVar gl_radar7954721_workaround_maskval ( "gl_radar7954721_workaround_maskval", "0" );
enum eBlitFormatClass
{
eColor,
eDepth, // may not get used. not sure..
eDepthStencil
};
uint glAttachFromClass[ 3 ] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_DEPTH_STENCIL_ATTACHMENT };
void glScrubFBO ( GLenum target )
{
gGL->glFramebufferRenderbuffer ( target, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
gGL->glFramebufferRenderbuffer ( target, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
gGL->glFramebufferRenderbuffer ( target, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
gGL->glFramebufferTexture2D ( target, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 );
gGL->glFramebufferTexture2D ( target, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0 );
gGL->glFramebufferTexture2D ( target, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0 );
}
void glAttachRBOtoFBO ( GLenum target, eBlitFormatClass formatClass, uint rboName )
{
switch( formatClass )
{
case eColor:
gGL->glFramebufferRenderbuffer ( target, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboName);
break;
case eDepth:
gGL->glFramebufferRenderbuffer ( target, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboName);
break;
case eDepthStencil:
gGL->glFramebufferRenderbuffer ( target, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboName);
gGL->glFramebufferRenderbuffer ( target, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rboName);
break;
}
}
void glAttachTex2DtoFBO ( GLenum target, eBlitFormatClass formatClass, uint texName, uint texMip )
{
switch( formatClass )
{
case eColor:
gGL->glFramebufferTexture2D ( target, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texName, texMip );
break;
case eDepth:
gGL->glFramebufferTexture2D ( target, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texName, texMip );
break;
case eDepthStencil:
gGL->glFramebufferTexture2D ( target, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texName, texMip );
break;
}
}
ConVar gl_can_resolve_flipped("gl_can_resolve_flipped", "0" );
ConVar gl_cannot_resolve_flipped("gl_cannot_resolve_flipped", "0" );
// these are only consulted if the m_cant_resolve_scaled cap bool is false.
ConVar gl_minify_resolve_mode("gl_minify_resolve_mode", "1" ); // if scaled resolve available, for downscaled resolve blits only (i.e. internal blits)
ConVar gl_magnify_resolve_mode("gl_magnify_resolve_mode", "2" ); // if scaled resolve available, for upscaled resolve blits only
// 0 == old style, two steps
// 1 == faster, one step blit aka XGL_SCALED_RESOLVE_FASTEST_EXT - if available.
// 2 == faster, one step blit aka XGL_SCALED_RESOLVE_NICEST_EXT - if available.
void GLMContext::SaveColorMaskAndSetToDefault()
{
// NVidia's driver doesn't ignore the colormask during blitframebuffer calls, so we need to save/restore it:
// “The bug here is that our driver fails to ignore colormask for BlitFramebuffer calls. This was unclear in the original spec, but we resolved it in Khronos last year (https://cvs.khronos.org/bugzilla/show_bug.cgi?id=7969).”
m_ColorMaskSingle.Read( &m_SavedColorMask, 0 );
GLColorMaskSingle_t newColorMask;
newColorMask.r = newColorMask.g = newColorMask.b = newColorMask.a = -1;
m_ColorMaskSingle.Write( &newColorMask );
}
void GLMContext::RestoreSavedColorMask()
{
m_ColorMaskSingle.Write( &m_SavedColorMask );
}
void GLMContext::Blit2( CGLMTex *srcTex, GLMRect *srcRect, int srcFace, int srcMip, CGLMTex *dstTex, GLMRect *dstRect, int dstFace, int dstMip, uint filter )
{
#if GL_TELEMETRY_GPU_ZONES
CScopedGLMPIXEvent glmPIXEvent( "Blit2" );
g_TelemetryGPUStats.m_nTotalBlit2++;
#endif
SaveColorMaskAndSetToDefault();
Assert( srcFace == 0 );
Assert( dstFace == 0 );
//----------------------------------------------------------------- format assessment
eBlitFormatClass formatClass = eColor;
uint blitMask= 0;
switch( srcTex->m_layout->m_format->m_glDataFormat )
{
case GL_RED: case GL_BGRA: case GL_RGB: case GL_RGBA: case GL_ALPHA: case GL_LUMINANCE: case GL_LUMINANCE_ALPHA:
formatClass = eColor;
blitMask = GL_COLOR_BUFFER_BIT;
break;
case GL_DEPTH_COMPONENT:
formatClass = eDepth;
blitMask = GL_DEPTH_BUFFER_BIT;
break;
case GL_DEPTH_STENCIL:
formatClass = eDepthStencil;
blitMask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
break;
default:
Assert(!"Unsupported format for blit" );
GLMStop();
break;
}
//----------------------------------------------------------------- blit assessment
bool blitResolves = srcTex->m_rboName != 0;
bool blitScales = ((srcRect->xmax - srcRect->xmin) != (dstRect->xmax - dstRect->xmin)) || ((srcRect->ymax - srcRect->ymin) != (dstRect->ymax - dstRect->ymin));
bool blitToBack = (dstTex == NULL);
bool blitFlips = blitToBack; // implicit y-flip upon blit to GL_BACK supplied
//should we support blitFromBack ?
bool srcGamma = srcTex && ((srcTex->m_layout->m_key.m_texFlags & kGLMTexSRGB) != 0);
bool dstGamma = dstTex && ((dstTex->m_layout->m_key.m_texFlags & kGLMTexSRGB) != 0);
//----------------------------------------------------------------- figure out the plan
bool blitTwoStep = false; // think positive
// each subsequent segment here can only set blitTwoStep, not clear it.
// the common case where these get hit is resolve out to presentation
// there may be GL extensions or driver revisions which start doing these safely.
// ideally many blits internally resolve without scaling and can thus go direct without using the scratch tex.
if (blitResolves && (blitFlips||blitToBack)) // flips, blit to back, same thing (for now)
{
if( gl_cannot_resolve_flipped.GetInt() )
{
blitTwoStep = true;
}
else if (!gl_can_resolve_flipped.GetInt())
{
blitTwoStep = blitTwoStep || m_caps.m_cantResolveFlipped; // if neither convar renders an opinion, fall back to the caps to decide if we have to two-step.
}
}
// only consider trying to use the scaling resolve filter,
// if we are confident we are not headed for two step mode already.
if (!blitTwoStep)
{
if (blitResolves && blitScales)
{
if (m_caps.m_cantResolveScaled)
{
// filter is unchanged, two step mode switches on
blitTwoStep = true;
}
else
{
bool blitScalesDown = ((srcRect->xmax - srcRect->xmin) > (dstRect->xmax - dstRect->xmin)) || ((srcRect->ymax - srcRect->ymin) > (dstRect->ymax - dstRect->ymin));
int mode = (blitScalesDown) ? gl_minify_resolve_mode.GetInt() : gl_magnify_resolve_mode.GetInt();
// roughly speaking, resolve blits that minify represent setup for special effects ("copy framebuffer to me")
// resolve blits that magnify are almost always on the final present in the case where remder size < display size
switch( mode )
{
case 0:
default:
// filter is unchanged, two step mode
blitTwoStep = true;
break;
case 1:
// filter goes to fastest, one step mode
blitTwoStep = false;
filter = XGL_SCALED_RESOLVE_FASTEST_EXT;
break;
case 2:
// filter goes to nicest, one step mode
blitTwoStep = false;
filter = XGL_SCALED_RESOLVE_NICEST_EXT;
break;
}
}
}
}
//----------------------------------------------------------------- save old scissor state and disable scissor
GLScissorEnable_t oldsciss,newsciss;
m_ScissorEnable.Read( &oldsciss, 0 );
if (oldsciss.enable)
{
// turn off scissor
newsciss.enable = false;
m_ScissorEnable.Write( &newsciss );
}
//----------------------------------------------------------------- fork in the road, depending on two-step or not
if (blitTwoStep)
{
// a resolve that can't be done directly due to constraints on scaling or flipping.
// bind scratch FBO0 to read, scrub it, attach RBO
BindFBOToCtx ( m_scratchFBO[0], GL_READ_FRAMEBUFFER );
glScrubFBO ( GL_READ_FRAMEBUFFER );
glAttachRBOtoFBO ( GL_READ_FRAMEBUFFER, formatClass, srcTex->m_rboName );
// bind scratch FBO1 to write, scrub it, attach scratch tex
BindFBOToCtx ( m_scratchFBO[1], GL_DRAW_FRAMEBUFFER );
glScrubFBO ( GL_DRAW_FRAMEBUFFER );
glAttachTex2DtoFBO ( GL_DRAW_FRAMEBUFFER, formatClass, srcTex->m_texName, 0 );
// set read and draw buffers appropriately
gGL->glReadBuffer( glAttachFromClass[formatClass] );
gGL->glDrawBuffers( 1, &glAttachFromClass[formatClass] );
// blit#1 - to resolve to scratch
// implicitly means no scaling, thus will be done with NEAREST sampling
GLenum resolveFilter = GL_NEAREST;
gGL->glBlitFramebuffer( 0, 0, srcTex->m_layout->m_key.m_xSize, srcTex->m_layout->m_key.m_ySize,
0, 0, srcTex->m_layout->m_key.m_xSize, srcTex->m_layout->m_key.m_ySize, // same source and dest rect, whole surface
blitMask, resolveFilter );
// FBO1 now holds the interesting content.
// scrub FBO0, bind FBO1 to READ, fall through to next stage of blit where 1 goes onto 0 (or BACK)
glScrubFBO ( GL_READ_FRAMEBUFFER ); // zap FBO0
BindFBOToCtx ( m_scratchFBO[1], GL_READ_FRAMEBUFFER );
srcTex->ForceRBONonDirty();
}
else
{
#if 1
if (srcTex->m_pBlitSrcFBO == NULL)
{
srcTex->m_pBlitSrcFBO = NewFBO();
BindFBOToCtx( srcTex->m_pBlitSrcFBO, GL_READ_FRAMEBUFFER );
if (blitResolves)
{
glAttachRBOtoFBO( GL_READ_FRAMEBUFFER, formatClass, srcTex->m_rboName );
}
else
{
glAttachTex2DtoFBO( GL_READ_FRAMEBUFFER, formatClass, srcTex->m_texName, srcMip );
}
}
else
{
BindFBOToCtx ( srcTex->m_pBlitSrcFBO, GL_READ_FRAMEBUFFER );
// GLMCheckError();
}
#else
// arrange source surface on FBO1 for blit directly to dest (which could be FBO0 or BACK)
BindFBOToCtx( m_scratchFBO[1], GL_READ_FRAMEBUFFER );
glScrubFBO( GL_READ_FRAMEBUFFER );
GLMCheckError();
if (blitResolves)
{
glAttachRBOtoFBO( GL_READ_FRAMEBUFFER, formatClass, srcTex->m_rboName );
}
else
{
glAttachTex2DtoFBO( GL_READ_FRAMEBUFFER, formatClass, srcTex->m_texName, srcMip );
}
#endif
gGL->glReadBuffer( glAttachFromClass[formatClass] );
}
//----------------------------------------------------------------- zero or one blits may have happened above, whichever took place, FBO1 is now on read
bool yflip = false;
if (blitToBack)
{
// backbuffer is special - FBO0 is left out (either scrubbed already, or not used)
BindFBOToCtx( NULL, GL_DRAW_FRAMEBUFFER );
GLenum bufs = GL_BACK;
gGL->glDrawBuffers( 1, &bufs );
yflip = true;
}
else
{
// not going to GL_BACK - use FBO0. set up dest tex or RBO on it. i.e. it's OK to blit from MSAA to MSAA if needed, though unlikely.
Assert( dstTex != NULL );
#if 1
if (dstTex->m_pBlitDstFBO == NULL)
{
dstTex->m_pBlitDstFBO = NewFBO();
BindFBOToCtx( dstTex->m_pBlitDstFBO, GL_DRAW_FRAMEBUFFER );
if (dstTex->m_rboName)
{
glAttachRBOtoFBO( GL_DRAW_FRAMEBUFFER, formatClass, dstTex->m_rboName );
}
else
{
glAttachTex2DtoFBO( GL_DRAW_FRAMEBUFFER, formatClass, dstTex->m_texName, dstMip );
}
}
else
{
BindFBOToCtx( dstTex->m_pBlitDstFBO, GL_DRAW_FRAMEBUFFER );
}
#else
BindFBOToCtx( m_scratchFBO[0], GL_DRAW_FRAMEBUFFER ); GLMCheckError();
glScrubFBO( GL_DRAW_FRAMEBUFFER );
if (dstTex->m_rboName)
{
glAttachRBOtoFBO( GL_DRAW_FRAMEBUFFER, formatClass, dstTex->m_rboName );
}
else
{
glAttachTex2DtoFBO( GL_DRAW_FRAMEBUFFER, formatClass, dstTex->m_texName, dstMip );
}
gGL->glDrawBuffer ( glAttachFromClass[formatClass] ); GLMCheckError();
#endif
}
// final blit
// i think in general, if we are blitting same size, gl_nearest is the right filter to pass.
// this re-steering won't kick in if there is scaling or a special scaled resolve going on.
if (!blitScales)
{
// steer it
filter = GL_NEAREST;
}
// this is blit #1 or #2 depending on what took place above.
if (yflip)
{
gGL->glBlitFramebuffer( srcRect->xmin, srcRect->ymin, srcRect->xmax, srcRect->ymax,
dstRect->xmin, dstRect->ymax, dstRect->xmax, dstRect->ymin, // note dest Y's are flipped
blitMask, filter );
}
else
{
gGL->glBlitFramebuffer( srcRect->xmin, srcRect->ymin, srcRect->xmax, srcRect->ymax,
dstRect->xmin, dstRect->ymin, dstRect->xmax, dstRect->ymax,
blitMask, filter );
}
//----------------------------------------------------------------- scrub READ and maybe DRAW FBO, and unbind
// glScrubFBO ( GL_READ_FRAMEBUFFER );
BindFBOToCtx ( NULL, GL_READ_FRAMEBUFFER );
if (!blitToBack)
{
// glScrubFBO ( GL_DRAW_FRAMEBUFFER );
BindFBOToCtx ( NULL, GL_DRAW_FRAMEBUFFER );
}
//----------------------------------------------------------------- restore GLM's drawing FBO
// restore GLM drawing FBO
BindFBOToCtx( m_drawingFBO, GL_FRAMEBUFFER );
//----------------------------------------------------------------- restore old scissor state
if (oldsciss.enable)
{
m_ScissorEnable.Write( &oldsciss );
}
RestoreSavedColorMask();
}
void GLMContext::BlitTex( CGLMTex *srcTex, GLMRect *srcRect, int srcFace, int srcMip, CGLMTex *dstTex, GLMRect *dstRect, int dstFace, int dstMip, GLenum filter, bool useBlitFB )
{
// This path doesn't work anymore (or did it ever work in the L4D2 Linux branch?)
DXABSTRACT_BREAK_ON_ERROR();
return;
SaveColorMaskAndSetToDefault();
switch( srcTex->m_layout->m_format->m_glDataFormat )
{
case GL_BGRA:
case GL_RGB:
case GL_RGBA:
case GL_ALPHA:
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA:
#if 0
if (GLMKnob("caps-key",NULL) > 0.0)
{
useBlitFB = false;
}
#endif
if ( m_caps.m_cantBlitReliably ) // this is referring to a problem with the x3100..
{
useBlitFB = false;
}
break;
}
if (0)
{
GLMPRINTF(("-D- Blit from %d %d %d %d to %d %d %d %d",
srcRect->xmin, srcRect->ymin, srcRect->xmax, srcRect->ymax,
dstRect->xmin, dstRect->ymin, dstRect->xmax, dstRect->ymax
));
GLMPRINTF(( "-D- src tex layout is %s", srcTex->m_layout->m_layoutSummary ));
GLMPRINTF(( "-D- dst tex layout is %s", dstTex->m_layout->m_layoutSummary ));
}
if (useBlitFB)
{
// state we need to save
// current setting of scissor
// current setting of the drawing fbo (no explicit save, it's in the context)
GLScissorEnable_t oldsciss,newsciss;
m_ScissorEnable.Read( &oldsciss, 0 );
// remember to restore m_drawingFBO at end of effort
// setup
// turn off scissor
newsciss.enable = false;
m_ScissorEnable.Write( &newsciss );
// select which attachment enum we're going to use for the blit
// default to color0, unless it's a depth or stencil flava
Assert( srcTex->m_layout->m_format->m_glDataFormat == dstTex->m_layout->m_format->m_glDataFormat );
EGLMFBOAttachment attachIndex = (EGLMFBOAttachment)0;
GLenum attachIndexGL = 0;
GLuint blitMask = 0;
switch( srcTex->m_layout->m_format->m_glDataFormat )
{
case GL_BGRA:
case GL_RGB:
case GL_RGBA:
case GL_ALPHA:
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA:
attachIndex = kAttColor0;
attachIndexGL = GL_COLOR_ATTACHMENT0;
blitMask = GL_COLOR_BUFFER_BIT;
break;
case GL_DEPTH_COMPONENT:
attachIndex = kAttDepth;
attachIndexGL = GL_DEPTH_ATTACHMENT;
blitMask = GL_DEPTH_BUFFER_BIT;
break;
case GL_DEPTH_STENCIL:
attachIndex = kAttDepthStencil;
attachIndexGL = GL_DEPTH_STENCIL_ATTACHMENT;
blitMask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
break;
default:
Assert(0);
break;
}
// set the read fb, attach read tex at appropriate attach point, set read buffer
BindFBOToCtx( m_blitReadFBO, GL_READ_FRAMEBUFFER );
GLMFBOTexAttachParams attparams;
attparams.m_tex = srcTex;
attparams.m_face = srcFace;
attparams.m_mip = srcMip;
attparams.m_zslice = 0;
m_blitReadFBO->TexAttach( &attparams, attachIndex, GL_READ_FRAMEBUFFER );
gGL->glReadBuffer( attachIndexGL );
// set the write fb and buffer, and attach write tex
BindFBOToCtx( m_blitDrawFBO, GL_DRAW_FRAMEBUFFER );
attparams.m_tex = dstTex;
attparams.m_face = dstFace;
attparams.m_mip = dstMip;
attparams.m_zslice = 0;
m_blitDrawFBO->TexAttach( &attparams, attachIndex, GL_DRAW_FRAMEBUFFER );
gGL->glDrawBuffers( 1, &attachIndexGL );
// do the blit
gGL->glBlitFramebuffer( srcRect->xmin, srcRect->ymin, srcRect->xmax, srcRect->ymax,
dstRect->xmin, dstRect->ymin, dstRect->xmax, dstRect->ymax,
blitMask, filter );
// cleanup
// unset the read fb and buffer, detach read tex
// unset the write fb and buffer, detach write tex
m_blitReadFBO->TexDetach( attachIndex, GL_READ_FRAMEBUFFER );
m_blitDrawFBO->TexDetach( attachIndex, GL_DRAW_FRAMEBUFFER );
// put the original FB back in place (both read and draw)
// this bind will hit both read and draw bindings
BindFBOToCtx( m_drawingFBO, GL_FRAMEBUFFER );
// set the read and write buffers back to... what ? does it matter for anything but copies ? don't worry about it
// restore the scissor state
m_ScissorEnable.Write( &oldsciss );
}
else
{
// textured quad style
// we must attach the dest tex as the color buffer on the blit draw FBO
// so that means we need to re-set the drawing FBO on exit
EGLMFBOAttachment attachIndex = (EGLMFBOAttachment)0;
GLenum attachIndexGL = 0;
switch( srcTex->m_layout->m_format->m_glDataFormat )
{
case GL_BGRA:
case GL_RGB:
case GL_RGBA:
case GL_ALPHA:
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA:
attachIndex = kAttColor0;
attachIndexGL = GL_COLOR_ATTACHMENT0;
break;
default:
Assert(!"Can't blit that format");
break;
}
BindFBOToCtx( m_blitDrawFBO, GL_DRAW_FRAMEBUFFER );
GLMFBOTexAttachParams attparams;
attparams.m_tex = dstTex;
attparams.m_face = dstFace;
attparams.m_mip = dstMip;
attparams.m_zslice = 0;
m_blitDrawFBO->TexAttach( &attparams, attachIndex, GL_DRAW_FRAMEBUFFER );
gGL->glDrawBuffers( 1, &attachIndexGL );
// attempt to just set states directly the way we want them, then use the latched states to repair them afterward.
NullProgram(); // out of program mode
gGL->glDisable ( GL_ALPHA_TEST );
gGL->glDisable ( GL_CULL_FACE );
gGL->glDisable ( GL_POLYGON_OFFSET_FILL );
gGL->glDisable ( GL_SCISSOR_TEST );
gGL->glDisable ( GL_CLIP_PLANE0 );
gGL->glDisable ( GL_CLIP_PLANE1 );
gGL->glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
gGL->glDisable ( GL_BLEND );
gGL->glDepthMask ( GL_FALSE );
gGL->glDisable ( GL_DEPTH_TEST );
gGL->glDisable ( GL_STENCIL_TEST );
gGL->glStencilMask ( GL_FALSE );
// now do the unlit textured quad...
gGL->glActiveTexture( GL_TEXTURE0 );
gGL->glBindTexture( GL_TEXTURE_2D, srcTex->m_texName );
gGL->glEnable(GL_TEXTURE_2D);
// immediate mode is fine
#if 0 // Does it needed?
const float topv = 1.0;
const float botv = 0.0;
const float verts[] = {-1.f, -1.f, 1.f, -1.f, 1.f, 1.f, -1.f, 1.f};
const float verts_tex[] = {0.f, botv, 1.f, botv, 1.f, topv, 0.f, topv};
gGL->glVertexPointer(2, GL_FLOAT, 0, verts);
gGL->glTexCoordPointer(2, GL_FLOAT, 0, verts_tex);
gGL->glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
gGL->glDisableClientState(GL_VERTEX_ARRAY);
gGL->glDisableClientState(GL_TEXTURE_COORD_ARRAY);
#endif
gGL->glBindTexture( GL_TEXTURE_2D, 0 );
gGL->glDisable(GL_TEXTURE_2D);
BindTexToTMU( m_samplers[0].m_pBoundTex, 0 );
// leave active program empty - flush draw states will fix
// then restore states using the scoreboard
m_AlphaTestEnable.Flush();
m_AlphaToCoverageEnable.Flush();
m_CullFaceEnable.Flush();
m_DepthBias.Flush();
m_ScissorEnable.Flush();
m_ClipPlaneEnable.FlushIndex( 0 );
m_ClipPlaneEnable.FlushIndex( 1 );
m_ColorMaskSingle.Flush();
m_BlendEnable.Flush();
m_DepthMask.Flush();
m_DepthTestEnable.Flush();
m_StencilWriteMask.Flush();
m_StencilTestEnable.Flush();
// unset the write fb and buffer, detach write tex
m_blitDrawFBO->TexDetach( attachIndex, GL_DRAW_FRAMEBUFFER );
// put the original FB back in place (both read and draw)
BindFBOToCtx( m_drawingFBO, GL_FRAMEBUFFER );
}
RestoreSavedColorMask();
}
void GLMContext::ResolveTex( CGLMTex *tex, bool forceDirty )
{
#if GL_TELEMETRY_GPU_ZONES
CScopedGLMPIXEvent glmPIXEvent( "ResolveTex" );
g_TelemetryGPUStats.m_nTotalResolveTex++;
#endif
// only run resolve if it's (a) possible and (b) dirty or force-dirtied
if ( ( tex->m_rboName ) && ( tex->IsRBODirty() || forceDirty ) )
{
// state we need to save
// current setting of scissor
// current setting of the drawing fbo (no explicit save, it's in the context)
GLScissorEnable_t oldsciss,newsciss;
m_ScissorEnable.Read( &oldsciss, 0 );
// remember to restore m_drawingFBO at end of effort
// setup
// turn off scissor
newsciss.enable = false;
m_ScissorEnable.Write( &newsciss );
// select which attachment enum we're going to use for the blit
// default to color0, unless it's a depth or stencil flava
// for resolve, only handle a modest subset of the possible formats
EGLMFBOAttachment attachIndex = (EGLMFBOAttachment)0;
GLenum attachIndexGL = 0;
GLuint blitMask = 0;
switch( tex->m_layout->m_format->m_glDataFormat )
{
case GL_BGRA:
case GL_RGB:
case GL_RGBA:
// case GL_ALPHA:
// case GL_LUMINANCE:
// case GL_LUMINANCE_ALPHA:
attachIndex = kAttColor0;
attachIndexGL = GL_COLOR_ATTACHMENT0;
blitMask = GL_COLOR_BUFFER_BIT;
break;
// case GL_DEPTH_COMPONENT:
// attachIndex = kAttDepth;
// attachIndexGL = GL_DEPTH_ATTACHMENT;
// blitMask = GL_DEPTH_BUFFER_BIT;
// break;
case GL_DEPTH_STENCIL:
attachIndex = kAttDepthStencil;
attachIndexGL = GL_DEPTH_STENCIL_ATTACHMENT;
blitMask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
break;
default:
Assert(!"Unsupported format for MSAA resolve" );
break;
}
// set the read fb, attach read RBO at appropriate attach point, set read buffer
BindFBOToCtx( m_blitReadFBO, GL_READ_FRAMEBUFFER );
// going to avoid the TexAttach / TexDetach calls due to potential confusion, implement it directly here
//-----------------------------------------------------------------------------------
// put tex->m_rboName on the read FB's attachment
if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT)
{
// you have to attach it both places...
// http://www.opengl.org/wiki/GL_EXT_framebuffer_object
// bind the RBO to the GL_RENDERBUFFER target - is this extraneous ?
//glBindRenderbufferEXT( GL_RENDERBUFFER, tex->m_rboName );
// attach the GL_RENDERBUFFER target to the depth and stencil attach points
gGL->glFramebufferRenderbuffer( GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, tex->m_rboName);
gGL->glFramebufferRenderbuffer( GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, tex->m_rboName);
// no need to leave the RBO hanging on
//glBindRenderbufferEXT( GL_RENDERBUFFER, 0 );
}
else
{
//glBindRenderbufferEXT( GL_RENDERBUFFER, tex->m_rboName );
gGL->glFramebufferRenderbuffer( GL_READ_FRAMEBUFFER, attachIndexGL, GL_RENDERBUFFER, tex->m_rboName);
//glBindRenderbufferEXT( GL_RENDERBUFFER, 0 );
}
gGL->glReadBuffer( attachIndexGL );
//-----------------------------------------------------------------------------------
// put tex->m_texName on the draw FBO attachment
// set the write fb and buffer, and attach write tex
BindFBOToCtx( m_blitDrawFBO, GL_DRAW_FRAMEBUFFER );
// regular path - attaching a texture2d
if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT)
{
gGL->glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, tex->m_texName, 0 );
gGL->glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tex->m_texName, 0 );
}
else
{
gGL->glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, attachIndexGL, GL_TEXTURE_2D, tex->m_texName, 0 );
}
gGL->glDrawBuffers( 1, &attachIndexGL );
//-----------------------------------------------------------------------------------
// blit
gGL->glBlitFramebuffer( 0, 0, tex->m_layout->m_key.m_xSize, tex->m_layout->m_key.m_ySize,
0, 0, tex->m_layout->m_key.m_xSize, tex->m_layout->m_key.m_ySize,
blitMask, GL_NEAREST );
// or should it be GL_LINEAR? does it matter ?
//-----------------------------------------------------------------------------------
// cleanup
//-----------------------------------------------------------------------------------
// unset the read fb and buffer, detach read RBO
//glBindRenderbufferEXT( GL_RENDERBUFFER, 0 );
if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT)
{
// detach the GL_RENDERBUFFER target from the depth and stencil attach points
gGL->glFramebufferRenderbuffer( GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
gGL->glFramebufferRenderbuffer( GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
}
else
{
gGL->glFramebufferRenderbuffer( GL_READ_FRAMEBUFFER, attachIndexGL, GL_RENDERBUFFER, 0);
}
//-----------------------------------------------------------------------------------
// unset the write fb and buffer, detach write tex
if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT)
{
gGL->glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0 );
gGL->glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0 );
}
else
{
gGL->glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, attachIndexGL, GL_TEXTURE_2D, 0, 0 );
}
// put the original FB back in place (both read and draw)
// this bind will hit both read and draw bindings
BindFBOToCtx( m_drawingFBO, GL_FRAMEBUFFER );
// set the read and write buffers back to... what ? does it matter for anything but copies ? don't worry about it
// restore the scissor state
m_ScissorEnable.Write( &oldsciss );
// mark the RBO clean on the resolved tex
tex->ForceRBONonDirty();
}
}
void GLMContext::PreloadTex( CGLMTex *tex, bool force )
{
// if conditions allow (i.e. a drawing surface is active)
// bind the texture on TMU 15
// set up a dummy program to sample it but not write (use 'discard')
// draw a teeny little triangle that won't generate a lot of fragments
if (!m_pairCache)
return;
if (!m_drawingFBO)
return;
if (tex->m_texPreloaded && !force) // only do one preload unless forced to re-do
{
//printf("\nnot-preloading %s", tex->m_debugLabel ? tex->m_debugLabel : "(unknown)");
return;
}
//printf("\npreloading %s", tex->m_debugLabel ? tex->m_debugLabel : "(unknown)");
CGLMProgram *vp = m_preloadTexVertexProgram;
CGLMProgram *fp = NULL;
switch(tex->m_layout->m_key.m_texGLTarget)
{
case GL_TEXTURE_2D: fp = m_preload2DTexFragmentProgram;
break;
case GL_TEXTURE_3D: fp = m_preload3DTexFragmentProgram;
break;
case GL_TEXTURE_CUBE_MAP: fp = m_preloadCubeTexFragmentProgram;
break;
}
if (!fp)
return;
CGLMShaderPair *preloadPair = m_pairCache->SelectShaderPair( vp, fp, 0 );
if (!preloadPair)
return;
if ( !preloadPair->m_valid )
{
if ( !preloadPair->ValidateProgramPair() )
return;
}
gGL->glUseProgram( (GLuint)preloadPair->m_program );
m_pBoundPair = preloadPair;
m_bDirtyPrograms = true;
// almost ready to draw...
//int tmuForPreload = 15;
// shut down all the generic attribute arrays on the detention level - next real draw will activate them again
m_lastKnownVertexAttribMask = 0;
m_nNumSetVertexAttributes = 16;
memset( &m_boundVertexAttribs[0], 0xFF, sizeof( m_boundVertexAttribs ) );
// Force the next flush to reset the attributes.
ClearCurAttribs();
for( int index=0; index < kGLMVertexAttributeIndexMax; index++ )
{
gGL->glDisableVertexAttribArray( index );
}
// bind texture and sampling params
CGLMTex *pPrevTex = m_samplers[15].m_pBoundTex;
#ifndef OSX // 10.6
if ( m_bUseSamplerObjects )
{
gGL->glBindSampler( 15, 0 );
}
#endif // !OSX
BindTexToTMU( tex, 15 );
// unbind vertex/index buffers
BindBufferToCtx( kGLMVertexBuffer, NULL );
BindBufferToCtx( kGLMIndexBuffer, NULL );
// draw
static float posns[] = { 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f };
static unsigned short indices[] = { 0, 1, 2 };
gGL->glEnableVertexAttribArray( 0 );
gGL->glVertexAttribPointer( 0, 3, GL_FLOAT, 0, 0, posns );
gGL->glDrawRangeElements( GL_TRIANGLES, 0, 2, 3, GL_UNSIGNED_SHORT, indices);
gGL->glDisableVertexAttribArray( 0 );
SetSamplerDirty( 15 );
BindTexToTMU( pPrevTex, 15 );
tex->m_texPreloaded = true;
}
CGLMFBO *GLMContext::NewFBO( void )
{
GLM_FUNC;
CGLMFBO *fbo = new CGLMFBO( this );
m_fboTable.AddToTail( fbo );
return fbo;
}
void GLMContext::DelFBO( CGLMFBO *fbo )
{
GLM_FUNC;
if (m_drawingFBO == fbo)
{
m_drawingFBO = NULL; //poof!
}
if (m_boundReadFBO == fbo )
{
BindFBOToCtx( NULL, GL_READ_FRAMEBUFFER );
m_boundReadFBO = NULL;
}
if (m_boundDrawFBO == fbo )
{
BindFBOToCtx( NULL, GL_DRAW_FRAMEBUFFER );
m_boundDrawFBO = NULL;
}
int idx = m_fboTable.Find( fbo );
Assert( idx >= 0 );
if ( idx >= 0 )
{
m_fboTable.FastRemove( idx );
}
delete fbo;
}
//===============================================================================
CGLMProgram *GLMContext::NewProgram( EGLMProgramType type, char *progString, const char *pShaderName )
{
//hushed GLM_FUNC;
CGLMProgram *prog = new CGLMProgram( this, type );
prog->SetProgramText( progString );
prog->SetShaderName( pShaderName );
prog->CompileActiveSources();
return prog;
}
void GLMContext::DelProgram( CGLMProgram *pProg )
{
GLM_FUNC;
if ( m_drawingProgram[ pProg->m_type ] == pProg )
{
SetProgram( pProg->m_type, ( pProg->m_type == kGLMFragmentProgram ) ? m_pNullFragmentProgram : NULL );
}
// make sure to eliminate any cached pairs using this shader
bool purgeResult = m_pairCache->PurgePairsWithShader( pProg );
(void)purgeResult;
Assert( !purgeResult ); // very unlikely to trigger
NullProgram();
delete pProg;
}
void GLMContext::NullProgram( void )
{
gGL->glUseProgram( 0 );
m_pBoundPair = NULL;
m_bDirtyPrograms = true;
}
void GLMContext::SetDrawingLang( EGLMProgramLang lang, bool immediate )
{
if ( !m_caps.m_hasDualShaders ) return; // ignore attempts to change language when -glmdualshaders is not engaged
m_drawingLangAtFrameStart = lang;
if (immediate)
{
NullProgram();
m_drawingLang = m_drawingLangAtFrameStart;
}
}
void GLMContext::LinkShaderPair( CGLMProgram *vp, CGLMProgram *fp )
{
if ( (m_pairCache) && (m_drawingLang==kGLMGLSL) && (vp) && (fp) )
{
CGLMShaderPair *pair = m_pairCache->SelectShaderPair( vp, fp, 0 );
(void)pair;
Assert( pair != NULL );
NullProgram(); // clear out any binds that were done - next draw will set it right
}
}
void GLMContext::ValidateShaderPair( CGLMProgram *vp, CGLMProgram *fp )
{
if ((m_pairCache) && (m_drawingLang == kGLMGLSL) && (vp) && (fp))
{
CGLMShaderPair *pair = m_pairCache->SelectShaderPair( vp, fp, 0 );
Assert( pair != NULL );
pair->ValidateProgramPair();
NullProgram(); // clear out any binds that were done - next draw will set it right
}
}
void GLMContext::ClearShaderPairCache( void )
{
if (m_pairCache)
{
NullProgram();
m_pairCache->Purge(); // bye bye all linked pairs
NullProgram();
}
}
void GLMContext::QueryShaderPair( int index, GLMShaderPairInfo *infoOut )
{
if (m_pairCache)
{
m_pairCache->QueryShaderPair( index, infoOut );
}
else
{
memset( infoOut, 0, sizeof( *infoOut ) );
infoOut->m_status = -1;
}
}
CGLMBuffer *GLMContext::NewBuffer( EGLMBufferType type, uint size, uint options )
{
//hushed GLM_FUNC;
CGLMBuffer *prog = new CGLMBuffer( this, type, size, options );
return prog;
}
void GLMContext::DelBuffer( CGLMBuffer *buff )
{
GLM_FUNC;
for( int index = 0; index < kGLMVertexAttributeIndexMax; index++ )
{
if ( m_drawVertexSetup.m_attrs[index].m_pBuffer == buff )
{
// just clear the enable mask - this will force all the attrs to get re-sent on next sync
m_drawVertexSetup.m_attrMask = 0;
}
}
BindGLBufferToCtx( buff->m_buffGLTarget, NULL, false );
delete buff;
}
GLMVertexSetup g_blank_setup;
void GLMContext::Clear( bool color, unsigned long colorValue, bool depth, float depthValue, bool stencil, unsigned int stencilValue, GLScissorBox_t *box )
{
GLM_FUNC;
++m_nBatchCounter;
#if GLMDEBUG
GLMDebugHookInfo info;
memset( &info, 0, sizeof(info) );
info.m_caller = eClear;
do
{
#endif
uint mask = 0;
GLClearColor_t clearcol;
GLClearDepth_t cleardep = { depthValue };
GLClearStencil_t clearsten = { (GLint)stencilValue };
// depth write mask must be saved&restored
GLDepthMask_t olddepthmask;
GLDepthMask_t newdepthmask = { true };
// stencil write mask must be saved and restored
GLStencilWriteMask_t oldstenmask;
GLStencilWriteMask_t newstenmask = { (GLint)0xFFFFFFFF };
GLColorMaskSingle_t oldcolormask;
GLColorMaskSingle_t newcolormask = { -1,-1,-1,-1 }; // D3D clears do not honor color mask, so force it
if (color)
{
// #define D3DCOLOR_ARGB(a,r,g,b) ((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff)))
clearcol.r = ((colorValue >> 16) & 0xFF) / 255.0f; //R
clearcol.g = ((colorValue >> 8) & 0xFF) / 255.0f; //G
clearcol.b = ((colorValue ) & 0xFF) / 255.0f; //B
clearcol.a = ((colorValue >> 24) & 0xFF) / 255.0f; //A
m_ClearColor.Write( &clearcol ); // no check, no wait
mask |= GL_COLOR_BUFFER_BIT;
// save and set color mask
m_ColorMaskSingle.Read( &oldcolormask, 0 );
m_ColorMaskSingle.Write( &newcolormask );
}
if (depth)
{
// get old depth write mask
m_DepthMask.Read( &olddepthmask, 0 );
m_DepthMask.Write( &newdepthmask );
m_ClearDepth.Write( &cleardep ); // no check, no wait
mask |= GL_DEPTH_BUFFER_BIT;
}
if (stencil)
{
m_ClearStencil.Write( &clearsten ); // no check, no wait
mask |= GL_STENCIL_BUFFER_BIT;
// save and set sten mask
m_StencilWriteMask.Read( &oldstenmask, 0 );
m_StencilWriteMask.Write( &newstenmask );
}
bool subrect = (box != NULL);
GLScissorEnable_t scissorEnableSave;
GLScissorEnable_t scissorEnableNew = { true };
GLScissorBox_t scissorBoxSave;
GLScissorBox_t scissorBoxNew;
if (subrect)
{
// save current scissorbox and enable
m_ScissorEnable.Read( &scissorEnableSave, 0 );
m_ScissorBox.Read( &scissorBoxSave, 0 );
if(0)
{
// calc new scissorbox as intersection against *box
// max of the mins
scissorBoxNew.x = MAX(scissorBoxSave.x, box->x);
scissorBoxNew.y = MAX(scissorBoxSave.y, box->y);
// min of the maxes
scissorBoxNew.width = ( MIN(scissorBoxSave.x+scissorBoxSave.width, box->x+box->width)) - scissorBoxNew.x;
// height is just min of the max y's, minus the new base Y
scissorBoxNew.height = ( MIN(scissorBoxSave.y+scissorBoxSave.height, box->y+box->height)) - scissorBoxNew.y;
}
else
{
// ignore old scissor box completely.
scissorBoxNew = *box;
}
// set new box and enable
m_ScissorEnable.Write( &scissorEnableNew );
m_ScissorBox.Write( &scissorBoxNew );
}
gGL->glClear( mask );
if (subrect)
{
// put old scissor box and enable back
m_ScissorEnable.Write( &scissorEnableSave );
m_ScissorBox.Write( &scissorBoxSave );
}
if (depth)
{
// put old depth write mask
m_DepthMask.Write( &olddepthmask );
}
if (color)
{
// put old color write mask
m_ColorMaskSingle.Write( &oldcolormask );
}
if (stencil)
{
// put old sten mask
m_StencilWriteMask.Write( &oldstenmask );
}
#if GLMDEBUG
DebugHook( &info );
} while (info.m_loop);
#endif
}
// stolen from glmgrbasics.cpp
extern "C" uint GetCurrentKeyModifiers( void );
enum ECarbonModKeyIndex
{
EcmdKeyBit = 8, /* command key down?*/
EshiftKeyBit = 9, /* shift key down?*/
EalphaLockBit = 10, /* alpha lock down?*/
EoptionKeyBit = 11, /* option key down?*/
EcontrolKeyBit = 12 /* control key down?*/
};
enum ECarbonModKeyMask
{
EcmdKey = 1 << EcmdKeyBit,
EshiftKey = 1 << EshiftKeyBit,
EalphaLock = 1 << EalphaLockBit,
EoptionKey = 1 << EoptionKeyBit,
EcontrolKey = 1 << EcontrolKeyBit
};
static ConVar gl_flushpaircache ("gl_flushpaircache", "0");
static ConVar gl_paircachestats ("gl_paircachestats", "0");
static ConVar gl_mtglflush_at_tof ("gl_mtglflush_at_tof", "0");
static ConVar gl_texlayoutstats ("gl_texlayoutstats", "0" );
void GLMContext::BeginFrame( void )
{
GLM_FUNC;
m_debugFrameIndex++;
// check for lang change at TOF
if (m_caps.m_hasDualShaders)
{
if (m_drawingLang != m_drawingLangAtFrameStart)
{
// language change. unbind everything..
NullProgram();
m_drawingLang = m_drawingLangAtFrameStart;
}
}
// scrub some critical shock absorbers
for( int i=0; i< 16; i++)
{
gGL->glDisableVertexAttribArray( i ); // enable GLSL attribute- this is just client state - will be turned back off
}
m_lastKnownVertexAttribMask = 0;
m_nNumSetVertexAttributes = 0;
//FIXME should we also zap the m_lastKnownAttribs array ? (worst case it just sets them all again on first batch)
BindBufferToCtx( kGLMVertexBuffer, NULL, true );
BindBufferToCtx( kGLMIndexBuffer, NULL, true );
if (gl_flushpaircache.GetInt())
{
// do the flush and then set back to zero
ClearShaderPairCache();
printf("\n\n##### shader pair cache cleared\n\n");
gl_flushpaircache.SetValue( 0 );
}
if (gl_paircachestats.GetInt())
{
// do the flush and then set back to zero
m_pairCache->DumpStats();
gl_paircachestats.SetValue( 0 );
}
if (gl_texlayoutstats.GetInt())
{
m_texLayoutTable->DumpStats();
gl_texlayoutstats.SetValue( 0 );
}
if (gl_mtglflush_at_tof.GetInt())
{
gGL->glFlush(); // TOF flush - skip this if benchmarking, enable it if human playing (smoothness)
}
#if GLMDEBUG
// init debug hook information
GLMDebugHookInfo info;
memset( &info, 0, sizeof(info) );
info.m_caller = eBeginFrame;
do
{
DebugHook( &info );
} while (info.m_loop);
#endif
}
void GLMContext::EndFrame( void )
{
GLM_FUNC;
#if GLMDEBUG
// init debug hook information
GLMDebugHookInfo info;
memset( &info, 0, sizeof(info) );
info.m_caller = eEndFrame;
do
{
DebugHook( &info );
} while (info.m_loop);
#endif
}
//===============================================================================
CGLMQuery *GLMContext::NewQuery( GLMQueryParams *params )
{
CGLMQuery *query = new CGLMQuery( this, params );
return query;
}
void GLMContext::DelQuery( CGLMQuery *query )
{
// may want to do some finish/
delete query;
}
static ConVar mat_vsync( "mat_vsync", "0", 0, "Force sync to vertical retrace", true, 0.0, true, 1.0 );
//===============================================================================
ConVar glm_nullrefresh_capslock( "glm_nullrefresh_capslock", "0" );
ConVar glm_literefresh_capslock( "glm_literefresh_capslock", "0" );
extern ConVar gl_blitmode;
void GLMContext::Present( CGLMTex *tex )
{
GLM_FUNC;
{
#if GL_TELEMETRY_GPU_ZONES
CScopedGLMPIXEvent glmPIXEvent( "GLMContext::Present" );
g_TelemetryGPUStats.m_nTotalPresent++;
#endif
ProcessTextureDeletes();
bool newRefreshMode = false;
// two ways to go:
// old school, do the resolve, had the tex down to cocoamgr to actually blit.
// that way is required if you are not in one-context mode (10.5.8)
if ( (gl_blitmode.GetInt() != 0) )
{
newRefreshMode = true;
}
// this is the path whether full screen or windowed... we always blit.
CShowPixelsParams showparams;
memset( &showparams, 0, sizeof(showparams) );
showparams.m_srcTexName = tex->m_texName;
showparams.m_width = tex->m_layout->m_key.m_xSize;
showparams.m_height = tex->m_layout->m_key.m_ySize;
showparams.m_vsyncEnable = m_displayParams.m_vsyncEnable = mat_vsync.GetBool();
showparams.m_fsEnable = m_displayParams.m_fsEnable;
showparams.m_useBlit = m_caps.m_hasFramebufferBlit;
// we call showpixels once with the "only sync view" arg set, so we know what the latest surface size is, before trying to do our own blit !
showparams.m_onlySyncView = true;
ShowPixels(&showparams); // doesn't actually show anything, just syncs window/fs state (would make a useful separate call)
showparams.m_onlySyncView = false;
bool refresh = true;
#ifdef OSX
if ( (glm_nullrefresh_capslock.GetInt()) && (GetCurrentKeyModifiers() & EalphaLock) )
{
refresh = false;
}
#endif
static int counter;
counter ++;
#ifdef OSX
if ( (glm_literefresh_capslock.GetInt()) && (GetCurrentKeyModifiers() & EalphaLock) && (counter & 127) )
{
// just show every 128th frame
refresh = false;
}
#endif
if (refresh)
{
if (newRefreshMode)
{
// blit to GL_BACK done here, not in CocoaMgr, this lets us do resolve directly if conditions are right
GLMRect srcRect, dstRect;
uint dstWidth,dstHeight;
DisplayedSize( dstWidth,dstHeight );
srcRect.xmin = 0;
srcRect.ymin = 0;
srcRect.xmax = showparams.m_width;
srcRect.ymax = showparams.m_height;
dstRect.xmin = 0;
dstRect.ymin = 0;
dstRect.xmax = dstWidth;
dstRect.ymax = dstHeight;
// do not ask for LINEAR if blit is unscaled
// NULL means targeting GL_BACK. Blit2 will break it down into two steps if needed, and will handle resolve, scale, flip.
bool blitScales = (showparams.m_width != static_cast<int>(dstWidth)) || (showparams.m_height != static_cast<int>(dstHeight));
Blit2( tex, &srcRect, 0,0,
NULL, &dstRect, 0,0,
blitScales ? GL_LINEAR : GL_NEAREST );
// we set showparams.m_noBlit, and just let CocoaMgr handle the swap (flushbuffer / page flip)
showparams.m_noBlit = true;
BindFBOToCtx( NULL, GL_FRAMEBUFFER );
}
else
{
ResolveTex( tex, true ); // dxabstract used to do this unconditionally.we still do if new refresh mode doesn't engage.
BindFBOToCtx( NULL, GL_FRAMEBUFFER );
// showparams.m_noBlit is left set to 0. CocoaMgr does the blit.
}
ShowPixels(&showparams);
}
// put the original FB back in place (both read and draw)
// this bind will hit both read and draw bindings
BindFBOToCtx( m_drawingFBO, GL_FRAMEBUFFER );
// put em back !!
m_ScissorEnable.Flush();
m_ScissorBox.Flush();
m_ViewportBox.Flush();
}
m_nCurFrame++;
#if GL_BATCH_PERF_ANALYSIS
tmMessage( TELEMETRY_LEVEL2, TMMF_ICON_EXCLAMATION, "VS Uniform Calls: %u, VS Uniforms: %u|VS Uniform Bone Calls: %u, VS Bone Uniforms: %u|PS Uniform Calls: %u, PS Uniforms: %u", m_nTotalVSUniformCalls, m_nTotalVSUniformsSet, m_nTotalVSUniformBoneCalls, m_nTotalVSUniformsBoneSet, m_nTotalPSUniformCalls, m_nTotalPSUniformsSet );
m_nTotalVSUniformCalls = 0, m_nTotalVSUniformBoneCalls = 0, m_nTotalVSUniformsSet = 0, m_nTotalVSUniformsBoneSet = 0, m_nTotalPSUniformCalls = 0, m_nTotalPSUniformsSet = 0;
#endif
}
//===============================================================================
// GLMContext protected methods
// a naive implementation of this would just clear-drawable on the context at entry,
// and then capture and set fullscreen if requested.
// however that would glitch thescreen every time the user changed resolution while staying in full screen.
// but in windowed mode there's really not much to do in here. Yeah, this routine centers around obtaining
// drawables for fullscreen mode, and/or dropping those drawables if we're going back to windowed.
// um, are we expected to re-make the standard surfaces (color, depthstencil) if the res changes? is that now this routine's job ?
// so, kick it off with an assessment of whather we were FS previously or not.
// if there was no prior display params latched, then it wasn't.
// changes in here take place immediately. If you want to defer display changes then that's going to be a different method.
// common assumption is that there will be two places that call this: context create and the implementation of the DX9 Reset method.
// in either case the client code is aware of what it signed up for.
bool GLMContext::SetDisplayParams( GLMDisplayParams *params )
{
m_displayParams = *params; // latch em
m_displayParamsValid = true;
return true;
}
ConVar gl_can_query_fast("gl_can_query_fast", "0");
static uint gPersistentBufferSize[kGLMNumBufferTypes] =
{
2 * 1024 * 1024, // kGLMVertexBuffer
1 * 1024 * 1024, // kGLMIndexBuffer
0, // kGLMUniformBuffer
0, // kGLMPixelBuffer
};
GLMContext::GLMContext( IDirect3DDevice9 *pDevice, GLMDisplayParams *params )
{
m_nNumDirtySamplers = 0;
if( gGL->m_nDriverProvider == cGLDriverProviderARM )
m_bUseSamplerObjects = true;
else
m_bUseSamplerObjects = false;
if ( CommandLine()->CheckParm( "-gl_enablesamplerobjects" ) )
m_bUseSamplerObjects = true;
// Try to get some more free memory by relying on driver host copies instead of ours.
// In some cases the driver will be able to discard their own host copy and rely on GPU
// memory, reducing memory usage.
// Sadly, we have to enable tex client storage for srgb decoding. This should only happen
// on Macs w/ OSX 10.6.
m_bTexClientStorage = !gGL->m_bHave_GL_EXT_texture_sRGB_decode;
if ( CommandLine()->CheckParm( "-gl_texclientstorage" ) )
m_bTexClientStorage = true;
GLMDebugPrintf( "GL sampler object usage: %s\n", m_bUseSamplerObjects ? "ENABLED" : "DISABLED" );
m_nCurOwnerThreadId = ThreadGetCurrentId();
m_nThreadOwnershipReleaseCounter = 0;
m_pDevice = pDevice;
m_nCurFrame = 0;
m_nBatchCounter = 0;
ClearCurAttribs();
m_nCurPersistentBuffer = 0;
if ( gGL->m_bHave_GL_EXT_buffer_storage )
{
for ( uint lpType = 0; lpType < kGLMNumBufferTypes; ++lpType )
{
for ( uint lpNum = 0; lpNum < cNumPersistentBuffers; ++lpNum )
{
m_persistentBuffer[lpNum][lpType].Init( (EGLMBufferType)lpType, gPersistentBufferSize[lpType] );
}
}
}
m_bUseBoneUniformBuffers = true;
if (CommandLine()->CheckParm("-disableboneuniformbuffers"))
{
m_bUseBoneUniformBuffers = false;
}
m_nMaxUsedVertexProgramConstantsHint = 256;
// flag our copy of display params as blank
m_displayParamsValid = false;
// peek at any CLI options
m_slowAssertEnable = CommandLine()->FindParm("-glmassertslow") != 0;
m_slowSpewEnable = CommandLine()->FindParm("-glmspewslow") != 0;
m_checkglErrorsAfterEveryBatch = CommandLine()->FindParm("-glcheckerrors") != 0;
m_slowCheckEnable = m_slowAssertEnable || m_slowSpewEnable || m_checkglErrorsAfterEveryBatch;
m_drawingLangAtFrameStart = m_drawingLang = kGLMGLSL; // default to GLSL
// this affects FlushDrawStates which will route program bindings, uniform delivery, sampler setup, and enables accordingly.
if ( CommandLine()->FindParm("-glslmode") )
{
m_drawingLangAtFrameStart = m_drawingLang = kGLMGLSL;
}
if ( CommandLine()->FindParm("-arbmode") && !CommandLine()->FindParm("-glslcontrolflow") )
{
m_drawingLangAtFrameStart = m_drawingLang = kGLMARB;
}
// proceed with rest of init
m_dwRenderThreadId = 0;
m_bIsThreading = false;
m_nsctx = NULL;
m_ctx = NULL;
int *selAttribs = NULL;
uint selWords = 0;
memset( &m_caps, 0, sizeof( m_caps ) );
GetDesiredPixelFormatAttribsAndRendererInfo( (uint**)&selAttribs, &selWords, &m_caps );
uint selBytes = selWords * sizeof( uint ); selBytes;
#if defined( USE_SDL )
m_ctx = (SDL_GLContext)GetGLContextForWindow( params ? (void*)params->m_focusWindow : NULL );
MakeCurrent( true );
#else
#error
#endif
IncrementWindowRefCount();
// If we're using GL_ARB_debug_output, go ahead and setup the callback here.
if ( CommandLine()->FindParm( "-gl_debug" ) )
{
//#if GLMDEBUG
// Turning this on is a perf loss, but it ensures that you can (at least) swap to the other
// threads to see what call is currently being made.
// Note that if the driver is in multithreaded mode, you can put it back into singlethreaded mode
// and get a real stack for the offending gl call.
gGL->glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
#ifdef WIN32
// This happens early enough during init that DevMsg() does nothing.
OutputDebugStringA( "GLMContext::GLMContext: GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB enabled!\n" );
#else
printf( "GLMContext::GLMContext: GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB enabled!\n" );
#endif
// TODO(nillerusr): rewrite me!!!
// This should be there if we get in here--make sure.
gGL->glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, (const GLuint *)NULL, GL_TRUE);
// Gonna filter these out, they're "chatty".
gGL->glDebugMessageControl(GL_DEBUG_SOURCE_API_ARB, GL_DEBUG_TYPE_OTHER_ARB, GL_DEBUG_SEVERITY_LOW_ARB, 0, (const GLuint *)NULL, GL_FALSE);
gGL->glDebugMessageCallback(GL_Debug_Output_Callback, (void*)NULL);
GLMDebugPrintf( "GLMContext::GLMContext: Debug output (gl_arb_debug_output) enabled!\n" );
//#endif
}
if (CommandLine()->FindParm("-glmspewcaps"))
{
DumpCaps();
}
SetDisplayParams( params );
m_texLayoutTable = new CGLMTexLayoutTable;
#ifndef OSX
if ( m_bUseSamplerObjects )
{
memset( m_samplerObjectHash, 0, sizeof( m_samplerObjectHash ) );
m_nSamplerObjectHashNumEntries = 0;
for ( uint i = 0; i < cSamplerObjectHashSize; ++i )
{
gGL->glGenSamplers( 1, &m_samplerObjectHash[i].m_samplerObject );
}
}
#endif // !OSX
memset( m_samplers, 0, sizeof( m_samplers ) );
for( int i=0; i< GLM_SAMPLER_COUNT; i++)
{
GLMTexSamplingParams &params = m_samplers[i].m_samp;
params.m_packed.m_addressU = D3DTADDRESS_WRAP;
params.m_packed.m_addressV = D3DTADDRESS_WRAP;
params.m_packed.m_addressW = D3DTADDRESS_WRAP;
params.m_packed.m_minFilter = D3DTEXF_POINT;
params.m_packed.m_magFilter = D3DTEXF_POINT;
params.m_packed.m_mipFilter = D3DTEXF_NONE;
params.m_packed.m_maxAniso = 1;
params.m_packed.m_isValid = true;
params.m_packed.m_compareMode = 0;
}
MarkAllSamplersDirty();
m_activeTexture = -1;
m_texLocks.EnsureCapacity( 16 ); // should be sufficient
// FIXME need a texture tracking table so we can reliably delete CGLMTex objects at context teardown
m_boundReadFBO = NULL;
m_boundDrawFBO = NULL;
m_drawingFBO = NULL;
memset( m_drawingProgram, 0, sizeof( m_drawingProgram ) );
m_bDirtyPrograms = true;
memset( m_programParamsF , 0, sizeof( m_programParamsF ) );
memset( m_programParamsB , 0, sizeof( m_programParamsB ) );
memset( m_programParamsI , 0, sizeof( m_programParamsI ) );
for (uint i = 0; i < ARRAYSIZE(m_programParamsF); i++)
{
m_programParamsF[i].m_firstDirtySlotNonBone = 256;
m_programParamsF[i].m_dirtySlotHighWaterNonBone = 0;
m_programParamsF[i].m_dirtySlotHighWaterBone = 0;
}
m_paramWriteMode = eParamWriteDirtySlotRange; // default to fastest mode
if (CommandLine()->FindParm("-glmwriteallslots")) m_paramWriteMode = eParamWriteAllSlots;
if (CommandLine()->FindParm("-glmwriteshaderslots")) m_paramWriteMode = eParamWriteShaderSlots;
if (CommandLine()->FindParm("-glmwriteshaderslotsoptional")) m_paramWriteMode = eParamWriteShaderSlotsOptional;
if (CommandLine()->FindParm("-glmwritedirtyslotrange")) m_paramWriteMode = eParamWriteDirtySlotRange;
m_attribWriteMode = eAttribWriteDirty;
if (CommandLine()->FindParm("-glmwriteallattribs")) m_attribWriteMode = eAttribWriteAll;
if (CommandLine()->FindParm("-glmwritedirtyattribs")) m_attribWriteMode = eAttribWriteDirty;
m_pairCache = new CGLMShaderPairCache( this );
m_pBoundPair = NULL;
m_fragDataMask = 0;
memset( m_nBoundGLBuffer, 0xFF, sizeof( m_nBoundGLBuffer ) );
memset( m_boundVertexAttribs, 0xFF, sizeof(m_boundVertexAttribs) );
m_lastKnownVertexAttribMask = 0;
m_nNumSetVertexAttributes = 16;
// make a null program for use when client asks for NULL FP
m_pNullFragmentProgram = NewProgram(kGLMFragmentProgram, g_nullFragmentProgramText, "null" );
SetProgram( kGLMFragmentProgram, m_pNullFragmentProgram );
// make dummy programs for doing texture preload via dummy draw
m_preloadTexVertexProgram = NewProgram(kGLMVertexProgram, g_preloadTexVertexProgramText, "preloadTex" );
m_preload2DTexFragmentProgram = NewProgram(kGLMFragmentProgram, g_preload2DTexFragmentProgramText, "preload2DTex" );
m_preload3DTexFragmentProgram = NewProgram(kGLMFragmentProgram, g_preload3DTexFragmentProgramText, "preload3DTex" );
m_preloadCubeTexFragmentProgram = NewProgram(kGLMFragmentProgram, g_preloadCubeTexFragmentProgramText, "preloadCube" );
//memset( &m_drawVertexSetup, 0, sizeof(m_drawVertexSetup) );
SetVertexAttributes( NULL ); // will set up all the entries in m_drawVertexSetup
m_debugFontTex = NULL;
// debug state
m_debugFrameIndex = -1;
#if GLMDEBUG
// #######################################################################################
// DebugHook state - we could set these to more interesting values in response to a CLI arg like "startpaused" or something if desired
//m_paused = false;
m_holdFrameBegin = -1;
m_holdFrameEnd = -1;
m_holdBatch = m_holdBatchFrame = -1;
m_debugDelayEnable = false;
m_debugDelay = 1<<19; // ~0.5 sec delay
m_autoClearColor = m_autoClearDepth = m_autoClearStencil = false;
m_autoClearColorValues[0] = 0.0; //red
m_autoClearColorValues[1] = 1.0; //green
m_autoClearColorValues[2] = 0.0; //blue
m_autoClearColorValues[3] = 1.0; //alpha
m_selKnobIndex = 0;
m_selKnobMinValue = -10.0f;
m_selKnobMaxValue = 10.0f;
m_selKnobIncrement = 1/256.0f;
// #######################################################################################
#endif
// make two scratch FBO's for blit purposes
m_blitReadFBO = NewFBO();
m_blitDrawFBO = NewFBO();
for( int i=0; i<kGLMScratchFBOCount; i++)
{
m_scratchFBO[i] = NewFBO();
}
// Create a PBO that we can use to fill textures with bogus data asyncronously.
m_nBoundGLBuffer[ kGLMPixelBuffer ] = 0;
gGL->glGenBuffers( 1, &m_destroyPBO );
gGL->glBindBuffer( GL_PIXEL_UNPACK_BUFFER, m_destroyPBO );
gGL->glBufferData( GL_PIXEL_UNPACK_BUFFER, sizeof( g_garbageTextureBits ), g_garbageTextureBits, GL_STREAM_DRAW );
gGL->glBindBuffer( GL_PIXEL_UNPACK_BUFFER, m_nBoundGLBuffer[ kGLMPixelBuffer ] );
// Create a bunch of texture names for us to use forever and ever ramen.
FillTexCache( false, kGLMInitialTexCount );
#ifdef OSX
bool new_mtgl = m_caps.m_hasPerfPackage1; // i.e. 10.6.4 plus new driver
if ( CommandLine()->FindParm("-glmenablemtgl2") )
{
new_mtgl = true;
}
if ( CommandLine()->FindParm("-glmdisablemtgl2") )
{
new_mtgl = false;
}
bool mtgl_on = params->m_mtgl;
if (CommandLine()->FindParm("-glmenablemtgl"))
{
mtgl_on = true;
}
if (CommandLine()->FindParm("-glmdisablemtgl"))
{
mtgl_on = false;
}
CGLError result = (CGLError)0;
if (mtgl_on)
{
bool ready = false;
CGLContextObj context = GetCGLContextFromNSGL(m_ctx);
if (new_mtgl)
{
// afterburner
CGLContextEnable kCGLCPGCDMPEngine = ((CGLContextEnable)1314);
result = CGLEnable( context, kCGLCPGCDMPEngine );
if (!result)
{
ready = true; // succeeded - no need to try non-MTGL
printf("\nMTGL detected.\n");
}
else
{
printf("\nMTGL *not* detected, falling back.\n");
}
}
if (!ready)
{
// try old MTGL
result = CGLEnable( context, kCGLCEMPEngine );
if (!result)
{
printf("\nMTGL has been detected.\n");
ready = true; // succeeded - no need to try non-MTGL
}
}
}
/*
if ( m_caps.m_badDriver108Intel )
{
// this way we have something to look for in terminal spew if users report issues related to this in the future.
printf( "\nEnabling GLSL compiler `malloc' workaround.\n" );
if ( !IntelGLMallocWorkaround::Get()->Enable() )
{
Warning( "Unable to enable OSX 10.8 / Intel HD4000 workaround, there might be crashes.\n" );
}
}
*/
#endif
// also, set the remote convar "gl_can_query_fast" to 1 if perf package present, else 0.
gl_can_query_fast.SetValue( m_caps.m_hasPerfPackage1?1:0 );
#if GL_BATCH_PERF_ANALYSIS
m_nTotalVSUniformCalls = 0;
m_nTotalVSUniformBoneCalls = 0;
m_nTotalVSUniformsSet = 0;
m_nTotalVSUniformsBoneSet = 0;
m_nTotalPSUniformCalls = 0;
m_nTotalPSUniformsSet = 0;
#endif
// See g_D3DRS_INFO_packed in dxabstract.cpp; dithering is a non-managed
// piece of state that we consider off by default. However it is actually
// enabled by default in the GL spec, so account for that here.
// See: https://bugs.freedesktop.org/show_bug.cgi?id=74700
gGL->glDisable( GL_DITHER );
}
void GLMContext::Reset()
{
}
GLMContext::~GLMContext ()
{
if (m_debugFontTex)
{
DelTex( m_debugFontTex );
m_debugFontTex = NULL;
}
ProcessTextureDeletes();
if ( m_pNullFragmentProgram )
{
DelProgram( m_pNullFragmentProgram );
m_pNullFragmentProgram = NULL;
}
// walk m_fboTable and free them up..
FOR_EACH_VEC( m_fboTable, i )
{
CGLMFBO *fbo = m_fboTable[i];
DelFBO( fbo );
}
m_fboTable.SetSize( 0 );
if (m_pairCache)
{
delete m_pairCache;
m_pairCache = NULL;
}
// we need a m_texTable I think..
// m_texLayoutTable can be scrubbed once we know that all the tex are freed
gGL->glDeleteBuffers( 1, &m_destroyPBO );
PurgeTexCache();
DecrementWindowRefCount();
}
// This method must call SelectTMU()/glActiveTexture() (it's expected as a side effect).
// This method is no longer called from any performance sensitive code paths.
void GLMContext::BindTexToTMU( CGLMTex *pTex, int tmu )
{
#if GLMDEBUG
GLM_FUNC;
#endif
GLMPRINTF(("--- GLMContext::BindTexToTMU tex %p GL name %d -> TMU %d ", pTex, pTex ? pTex->m_texName : -1, tmu ));
CheckCurrent();
SelectTMU( tmu );
if ( !pTex )
{
gGL->glBindTexture( GL_TEXTURE_2D, 0 );
gGL->glBindTexture( GL_TEXTURE_3D, 0 );
gGL->glBindTexture( GL_TEXTURE_CUBE_MAP, 0 );
}
else
{
const GLenum texGLTarget = pTex->m_texGLTarget;
if ( texGLTarget != GL_TEXTURE_2D ) gGL->glBindTexture( GL_TEXTURE_2D, 0 );
if ( texGLTarget != GL_TEXTURE_3D ) gGL->glBindTexture( GL_TEXTURE_3D, 0 );
if ( texGLTarget != GL_TEXTURE_CUBE_MAP ) gGL->glBindTexture( GL_TEXTURE_CUBE_MAP, 0 );
gGL->glBindTexture( texGLTarget, pTex->m_texName );
}
m_samplers[tmu].m_pBoundTex = pTex;
}
void GLMContext::BindFBOToCtx( CGLMFBO *fbo, GLenum bindPoint )
{
#if GLMDEBUG
GLM_FUNC;
#endif
GLMPRINTF(( "--- GLMContext::BindFBOToCtx fbo %p, GL name %d", fbo, (fbo) ? fbo->m_name : -1 ));
CheckCurrent();
if ( bindPoint == GL_FRAMEBUFFER )
{
gGL->glBindFramebuffer( GL_FRAMEBUFFER, fbo ? fbo->m_name : 0 );
m_boundReadFBO = fbo;
m_boundDrawFBO = fbo;
return;
}
bool targetRead = (bindPoint==GL_READ_FRAMEBUFFER);
bool targetDraw = (bindPoint==GL_DRAW_FRAMEBUFFER);
if (targetRead)
{
if (fbo) // you can pass NULL to go back to no-FBO
{
gGL->glBindFramebuffer( GL_READ_FRAMEBUFFER, fbo->m_name );
m_boundReadFBO = fbo;
//dontcare fbo->m_bound = true;
}
else
{
gGL->glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 );
m_boundReadFBO = NULL;
}
}
if (targetDraw)
{
if (fbo) // you can pass NULL to go back to no-FBO
{
gGL->glBindFramebuffer( GL_DRAW_FRAMEBUFFER, fbo->m_name );
m_boundDrawFBO = fbo;
//dontcare fbo->m_bound = true;
}
else
{
gGL->glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
m_boundDrawFBO = NULL;
}
}
}
void GLMContext::BindBufferToCtx( EGLMBufferType type, CGLMBuffer *pBuff, bool bForce )
{
#if GLMDEBUG
GLM_FUNC;
#endif
GLMPRINTF(( "--- GLMContext::BindBufferToCtx buff %p, GL name %d", pBuff, (pBuff) ? pBuff->m_nHandle : -1 ));
CheckCurrent();
GLuint nGLName = pBuff ? pBuff->GetHandle() : 0;
if ( !bForce )
{
if ( m_nBoundGLBuffer[type] == nGLName )
return;
}
GLenum target = 0;
switch( type )
{
case kGLMVertexBuffer: target = GL_ARRAY_BUFFER; break;
case kGLMIndexBuffer: target = GL_ELEMENT_ARRAY_BUFFER; break;
case kGLMUniformBuffer: target = GL_UNIFORM_BUFFER; break;
case kGLMPixelBuffer: target = GL_PIXEL_UNPACK_BUFFER; break;
default: Assert(!"Unknown buffer type" );
}
Assert( !pBuff || ( pBuff->m_buffGLTarget == target ) );
m_nBoundGLBuffer[type] = nGLName;
gGL->glBindBuffer( target, nGLName );
}
GLuint GLMContext::CreateTex( GLenum texBind, GLenum internalFormat )
{
GLM_FUNC;
// If we're not doing batch create, just return one here.
if ( !gl_batch_tex_creates.GetBool() )
{
GLuint tex = 0;
gGL->glGenTextures( 1, &tex );
return tex;
}
FOR_EACH_VEC( m_availableTextures, i )
{
TextureEntry_t& tex = m_availableTextures[ i ];
if ( ( tex.m_nTexBind == GL_NONE || tex.m_nTexBind == texBind )
&& ( tex.m_nInternalFormat == GL_NONE || tex.m_nInternalFormat == internalFormat ) )
{
// Hit!
GLuint retVal = tex.m_nTexName;
m_availableTextures.Remove( i );
return retVal;
}
}
if ( m_availableTextures.Count() >= kGLMHighWaterUndeleted )
{
PurgeTexCache();
}
return FillTexCache( true, kGLMReUpTexCount );
}
void GLMContext::CleanupTex( GLenum texBind, GLMTexLayout* pLayout, GLuint tex )
{
// If the total
if ( pLayout->m_storageTotalSize <= ( kDeletedTextureDim * kDeletedTextureDim * sizeof( uint32 ) ) )
return;
const GLuint oldPBO = m_nBoundGLBuffer[ kGLMPixelBuffer ];
const GLuint oldTex = ( m_samplers[ m_activeTexture ].m_pBoundTex != NULL ) ? m_samplers[ m_activeTexture ].m_pBoundTex->GetTexName() : 0;
gGL->glBindBuffer( GL_PIXEL_UNPACK_BUFFER, m_destroyPBO );
gGL->glBindTexture( texBind, tex );
// Clear out old data.
for ( int i = 0; i < pLayout->m_mipCount; ++i )
{
int mipDim = ( i == 0 ) ? kDeletedTextureDim : 0;
if ( pLayout->m_format->m_chunkSize != 1 )
{
const int chunks = ( mipDim + ( pLayout->m_format->m_chunkSize - 1 ) ) / pLayout->m_format->m_chunkSize;
const int dataSize = ( chunks * chunks ) * pLayout->m_format->m_bytesPerSquareChunk;
Assert( dataSize <= ( sizeof( uint32) * ARRAYSIZE( g_garbageTextureBits ) ) );
CompressedTexImage2D( texBind, i, pLayout->m_format->m_glIntFormat, mipDim, mipDim, 0, dataSize, NULL );
}
else
{
convert_texture( pLayout->m_format->m_glIntFormat, mipDim, mipDim, pLayout->m_format->m_glDataFormat, pLayout->m_format->m_glDataType, NULL );
gGL->glTexImage2D( texBind, i, pLayout->m_format->m_glIntFormat, mipDim, mipDim, 0, pLayout->m_format->m_glDataFormat, pLayout->m_format->m_glDataType, NULL );
}
}
gGL->glBindTexture( texBind, oldTex );
gGL->glBindBuffer( GL_PIXEL_UNPACK_BUFFER, oldPBO );
}
void GLMContext::DestroyTex( GLenum texBind, GLMTexLayout* pLayout, GLuint tex )
{
GLM_FUNC;
// Code only handles 2D for now.
if ( texBind != GL_TEXTURE_2D || !gl_batch_tex_destroys.GetBool() )
{
gGL->glDeleteTextures( 1, &tex );
return;
}
CleanupTex( texBind, pLayout, tex );
TextureEntry_t entry;
entry.m_nTexBind = texBind;
entry.m_nInternalFormat = pLayout->m_format->m_glIntFormat;
entry.m_nTexName = tex;
m_availableTextures.AddToTail( entry );
}
GLuint GLMContext::FillTexCache( bool holdOne, int newTextures )
{
// If we aren't doing batch creates, then don't fill the cache.
if ( !gl_batch_tex_creates.GetBool() )
return 0;
// If we have to hit the name table, might as well hit it a bunch because this causes
// serialization either way--at least we can do it less often.
GLuint* textures = (GLuint*) stackalloc( newTextures * sizeof( GLuint ) );
gGL->glGenTextures( newTextures, textures );
Assert( textures[ 0 ] );
TextureEntry_t entry;
entry.m_nTexBind = GL_NONE;
entry.m_nInternalFormat = GL_NONE;
// We may return 0, so skip adding it here.
for ( int i = 1; i < newTextures; ++i )
{
Assert( textures[ i ] );
if ( textures[ i ] )
{
// We still add these to the tail because we'd prefer to reuse old textures (rather
// than these new ones).
entry.m_nTexName = textures[ i ];
m_availableTextures.AddToTail( entry );
}
}
if ( holdOne )
return textures[ 0 ];
// If not, stick that last one in the list and return 0.
entry.m_nTexName = textures[ 0 ];
m_availableTextures.AddToTail( entry );
return 0;
}
void GLMContext::PurgeTexCache()
{
GLM_FUNC;
int textureCount = m_availableTextures.Count();
if ( textureCount == 0 )
return;
GLuint* textures = (GLuint*) stackalloc( textureCount * sizeof( GLuint ) );
FOR_EACH_VEC( m_availableTextures, i )
{
TextureEntry_t& tex = m_availableTextures[ i ];
textures[ i ] = tex.m_nTexName;
}
gGL->glDeleteTextures( textureCount, textures );
m_availableTextures.RemoveAll();
}
#ifdef OSX
// As far as I can tell this stuff is only useful under OSX.
ConVar gl_can_mix_shader_gammas( "gl_can_mix_shader_gammas", 0 );
ConVar gl_cannot_mix_shader_gammas( "gl_cannot_mix_shader_gammas", 0 );
#endif
// ConVar param_write_mode("param_write_mode", "0");
void GLMContext::MarkAllSamplersDirty()
{
m_nNumDirtySamplers = GLM_SAMPLER_COUNT;
for (uint i = 0; i < GLM_SAMPLER_COUNT; i++)
{
m_nDirtySamplerFlags[i] = 0;
m_nDirtySamplers[i] = (uint8)i;
}
}
void GLMContext::FlushDrawStatesNoShaders( )
{
Assert( ( m_drawingFBO == m_boundDrawFBO ) && ( m_drawingFBO == m_boundReadFBO ) ); // this check MUST succeed
GLM_FUNC;
GL_BATCH_PERF( m_FlushStats.m_nTotalBatchFlushes++; )
NullProgram();
}
#if GLMDEBUG
enum EGLMDebugDumpOptions
{
eDumpBatchInfo,
eDumpSurfaceInfo,
eDumpStackCrawl,
eDumpShaderLinks,
// eDumpShaderText, // we never use this one
eDumpShaderParameters,
eDumpTextureSetup,
eDumpVertexAttribSetup,
eDumpVertexData,
eOpenShadersForEdit
};
enum EGLMVertDumpMode
{
// options that affect eDumpVertexData above
eDumpVertsNoTransformDump,
eDumpVertsTransformedByViewProj,
eDumpVertsTransformedByModelViewProj,
eDumpVertsTransformedByBoneZeroThenViewProj,
eDumpVertsTransformedByBonesThenViewProj,
eLastDumpVertsMode
};
char *g_vertDumpModeNames[] =
{
"noTransformDump",
"transformedByViewProj",
"transformedByModelViewProj",
"transformedByBoneZeroThenViewProj",
"transformedByBonesThenViewProj"
};
static void CopyTilEOL( char *dst, char *src, int dstSize )
{
dstSize--;
int i=0;
while ( (i<dstSize) && (src[i] != 0) && (src[i] != '\n') && (src[i] != '\r') )
{
dst[i] = src[i];
i++;
}
dst[i] = 0;
}
static uint g_maxVertsToDumpLog2 = 4;
static uint g_maxFramesToCrawl = 20; // usually enough. Not enough? change it..
extern char sg_pPIXName[128];
// min is eDumpVertsNormal, max is the one before eLastDumpVertsMode
static enum EGLMVertDumpMode g_vertDumpMode = eDumpVertsNoTransformDump;
void GLMContext::DebugDump( GLMDebugHookInfo *info, uint options, uint vertDumpMode )
{
int oldIndent = GLMGetIndent();
GLMSetIndent(0);
CGLMProgram *vp = m_drawingProgram[kGLMVertexProgram];
CGLMProgram *fp = m_drawingProgram[kGLMFragmentProgram];
bool is_draw = (info->m_caller==eDrawElements);
const char *batchtype = is_draw ? "draw" : "clear";
if (options & (1<<eDumpBatchInfo))
{
GLMPRINTF(("-D- %s === %s %d ======================================================== %s %d frame %d", sg_pPIXName, batchtype, m_nBatchCounter, batchtype, m_nBatchCounter, m_debugFrameIndex ));
}
if (options & (1<<eDumpSurfaceInfo))
{
GLMPRINTF(("-D-" ));
GLMPRINTF(("-D- surface info:"));
GLMPRINTF(("-D- drawing FBO: %8x bound draw-FBO: %8x (%s)", m_drawingFBO, m_boundDrawFBO, (m_drawingFBO==m_boundDrawFBO) ? "in sync" : "desync!" ));
CGLMFBO *fbo = m_boundDrawFBO;
for( int i=0; i<kAttCount; i++)
{
CGLMTex *tex = fbo->m_attach[i].m_tex;
if (tex)
{
GLMPRINTF(("-D- bound FBO (%8x) attachment %d = tex %8x (GL %d) (%s)", fbo, i, tex, tex->m_texName, tex->m_layout->m_layoutSummary ));
}
else
{
// warning if no depthstencil attachment
switch(i)
{
case kAttDepth:
case kAttStencil:
case kAttDepthStencil:
GLMPRINTF(("-D- bound FBO (%8x) attachment %d = NULL, warning!", fbo, i ));
break;
}
}
}
}
if (options & (1<<eDumpStackCrawl))
{
CStackCrawlParams cp;
memset( &cp, 0, sizeof(cp) );
cp.m_frameLimit = g_maxFramesToCrawl;
GetStackCrawl(&cp);
GLMPRINTF(("-D-" ));
GLMPRINTF(("-D- stack crawl"));
for( uint i=0; i< cp.m_frameCount; i++)
{
GLMPRINTF(("-D-\t%s", cp.m_crawlNames[i] ));
}
}
if ( (options & (1<<eDumpShaderLinks)) && is_draw)
{
// we want to print out - GL name, pathname to disk copy if editable, extra credit would include the summary translation line
// so grep for "#// trans#"
char attribtemp[1000];
char transtemp[1000];
if (vp)
{
char *attribmap = strstr(vp->m_text, "#//ATTRIBMAP");
if (attribmap)
{
CopyTilEOL( attribtemp, attribmap, sizeof(attribtemp) );
}
else
{
strcpy( attribtemp, "no attrib map" );
}
char *trans = strstr(vp->m_text, "#// trans#");
if (trans)
{
CopyTilEOL( transtemp, trans, sizeof(transtemp) );
}
else
{
strcpy( transtemp, "no translation info" );
}
char *linkpath = "no file link";
#if GLMDEBUG
linkpath = vp->m_editable->m_mirror->m_path;
#endif
GLMPRINTF(("-D-"));
GLMPRINTF(("-D- ARBVP || GL %d || Path %s ", vp->m_descs[kGLMARB].m_object.arb, linkpath ));
GLMPRINTF(("-D- Attribs %s", attribtemp ));
GLMPRINTF(("-D- Trans %s", transtemp ));
/*
if ( (options & (1<<eDumpShaderText)) && is_draw )
{
GLMPRINTF(("-D-"));
GLMPRINTF(("-D- VP text " ));
GLMPRINTTEXT(vp->m_string, eDebugDump ));
}
*/
}
else
{
GLMPRINTF(("-D- VP (none)" ));
}
if (fp)
{
char *trans = strstr(fp->m_text, "#// trans#");
if (trans)
{
CopyTilEOL( transtemp, trans, sizeof(transtemp) );
}
else
{
strcpy( transtemp, "no translation info" );
}
char *linkpath = "no file link";
#if GLMDEBUG
linkpath = fp->m_editable->m_mirror->m_path;
#endif
GLMPRINTF(("-D-"));
GLMPRINTF(("-D- FP || GL %d || Path %s ", fp->m_descs[kGLMARB].m_object.arb, linkpath ));
GLMPRINTF(("-D- Trans %s", transtemp ));
/*
if ( (options & (1<<eDumpShaderText)) && is_draw )
{
GLMPRINTF(("-D-"));
GLMPRINTF(("-D- FP text " ));
GLMPRINTTEXT((fp->m_string, eDebugDump));
}
*/
}
else
{
GLMPRINTF(("-D- FP (none)" ));
}
}
if ( (options & (1<<eDumpShaderParameters)) && is_draw )
{
GLMPRINTF(("-D-"));
GLMPRINTF(("-D- VP parameters" ));
char *label = "";
//int labelcounter = 0;
static int vmaskranges[] = { /*18,47,*/ -1,-1 };
//float transposeTemp; // row, column for printing
int slotIndex = 0;
int upperSlotLimit = 61;
// take a peek at the vertex attrib setup. If it has an attribute for bone weights, then raise the shader param dump limit to 256.
bool usesSkinning = false;
GLMVertexSetup *pSetup = &m_drawVertexSetup;
for( int index=0; index < kGLMVertexAttributeIndexMax; index++ )
{
usesSkinning |= (pSetup->m_attrMask & (1<<index)) && ((pSetup->m_vtxAttribMap[index]>>4)== D3DDECLUSAGE_BLENDWEIGHT);
}
if (usesSkinning)
{
upperSlotLimit = 256;
}
while( slotIndex < upperSlotLimit )
{
// if slot index is in a masked range, skip it
// if slot index is the start of a matrix, label it, print it, skip ahead 4 slots
for( int maski=0; vmaskranges[maski] >=0; maski+=2)
{
if ( (slotIndex >= vmaskranges[maski]) && (slotIndex <= vmaskranges[maski+1]) )
{
// that index is masked. set to one past end of range, print a blank line for clarity
slotIndex = vmaskranges[maski+1]+1;
GLMPrintStr("-D- .....");
}
}
if (slotIndex < upperSlotLimit)
{
float *values = &m_programParamsF[ kGLMVertexProgram ].m_values[slotIndex][0];
switch( slotIndex )
{
case 4:
printmat( "MODELVIEWPROJ", slotIndex, 4, values );
slotIndex += 4;
break;
case 8:
printmat( "VIEWPROJ", slotIndex, 4, values );
slotIndex += 4;
break;
default:
if (slotIndex>=58)
{
// bone
char bonelabel[100];
sprintf(bonelabel, "MODEL_BONE%-2d", (slotIndex-58)/3 );
printmat( bonelabel, slotIndex, 3, values );
slotIndex += 3;
}
else
{
// just print the one slot
GLMPRINTF(("-D- %03d: [ %10.5f %10.5f %10.5f %10.5f ] %s", slotIndex, values[0], values[1], values[2], values[3], label ));
slotIndex++;
}
break;
}
}
}
// VP stage still, if in GLSL mode, find the bound pair and see if it has live i0, b0-b3 uniforms
if (m_pBoundPair) // should only be non-NULL in GLSL mode
{
#if 0
if (m_pBoundPair->m_locVertexBool0>=0)
{
GLMPRINTF(("-D- GLSL 'b0': %d", m_programParamsB[kGLMVertexProgram].m_values[0] ));
}
if (m_pBoundPair->m_locVertexBool1>=0)
{
GLMPRINTF(("-D- GLSL 'b1': %d", m_programParamsB[kGLMVertexProgram].m_values[1] ));
}
if (m_pBoundPair->m_locVertexBool2>=0)
{
GLMPRINTF(("-D- GLSL 'b2': %d", m_programParamsB[kGLMVertexProgram].m_values[2] ));
}
if (m_pBoundPair->m_locVertexBool3>=0)
{
GLMPRINTF(("-D- GLSL 'b3': %d", m_programParamsB[kGLMVertexProgram].m_values[3] ));
}
if (m_pBoundPair->m_locVertexInteger0>=0)
{
GLMPRINTF(("-D- GLSL 'i0': %d", m_programParamsI[kGLMVertexProgram].m_values[0][0] ));
}
#endif
}
GLMPRINTF(("-D-"));
GLMPRINTF(("-D- FP parameters " ));
static int fmaskranges[] = { 40,41, -1,-1 };
slotIndex = 0;
label = "";
while(slotIndex < 40)
{
// if slot index is in a masked range, skip it
// if slot index is the start of a matrix, label it, print it, skip ahead 4 slots
for( int maski=0; fmaskranges[maski] >=0; maski+=2)
{
if ( (slotIndex >= fmaskranges[maski]) && (slotIndex <= fmaskranges[maski+1]) )
{
// that index is masked. set to one past end of range, print a blank line for clarity
slotIndex = fmaskranges[maski+1]+1;
GLMPrintStr("-D- .....");
}
}
if (slotIndex < 40)
{
float *values = &m_programParamsF[ kGLMFragmentProgram ].m_values[slotIndex][0];
switch( slotIndex )
{
case 0: label = "g_EnvmapTint"; break;
case 1: label = "g_DiffuseModulation"; break;
case 2: label = "g_EnvmapContrast_ShadowTweaks"; break;
case 3: label = "g_EnvmapSaturation_SelfIllumMask (xyz, and w)"; break;
case 4: label = "g_SelfIllumTint_and_BlendFactor (xyz, and w)"; break;
case 12: label = "g_ShaderControls"; break;
case 13: label = "g_DepthFeatheringConstants"; break;
case 20: label = "g_EyePos"; break;
case 21: label = "g_FogParams"; break;
case 22: label = "g_FlashlightAttenuationFactors"; break;
case 23: label = "g_FlashlightPos"; break;
case 24: label = "g_FlashlightWorldToTexture"; break;
case 28: label = "cFlashlightColor"; break;
case 29: label = "g_LinearFogColor"; break;
case 30: label = "cLightScale"; break;
case 31: label = "cFlashlightScreenScale"; break;
default:
label = "";
break;
}
GLMPRINTF(("-D- %03d: [ %10.5f %10.5f %10.5f %10.5f ] %s", slotIndex, values[0], values[1], values[2], values[3], label ));
slotIndex ++;
}
}
if (m_pBoundPair->m_locFragmentFakeSRGBEnable)
{
GLMPRINTF(("-D- GLSL 'flEnableSRGBWrite': %f", m_pBoundPair->m_fakeSRGBEnableValue ));
}
}
if ( (options & (1<<eDumpTextureSetup)) && is_draw )
{
GLMPRINTF(( "-D-" ));
GLMPRINTF(( "-D- Texture / Sampler setup" ));
GLMPRINTF(( "-D- TODO" ));
#if 0
for( int i=0; i<GLM_SAMPLER_COUNT; i++ )
{
if (m_samplers[i].m_pBoundTex)
{
GLMTexSamplingParams *samp = &m_samplers[i].m_samp;
GLMPRINTF(( "-D-" ));
GLMPRINTF(("-D- Sampler %-2d tex %08x layout %s", i, m_samplers[i].m_pBoundTex, m_samplers[i].m_pBoundTex->m_layout->m_layoutSummary ));
GLMPRINTF(("-D- addressMode[ %s %s %s ]",
GLMDecode( eGL_ENUM, samp->m_addressModes[0] ),
GLMDecode( eGL_ENUM, samp->m_addressModes[1] ),
GLMDecode( eGL_ENUM, samp->m_addressModes[2] )
));
GLMPRINTF(("-D- magFilter [ %s ]", GLMDecode( eGL_ENUM, samp->m_magFilter ) ));
GLMPRINTF(("-D- minFilter [ %s ]", GLMDecode( eGL_ENUM, samp->m_minFilter ) ));
GLMPRINTF(("-D- srgb [ %s ]", samp->m_srgb ? "T" : "F" ));
GLMPRINTF(("-D- shadowFilter [ %s ]", samp->m_compareMode == GL_COMPARE_R_TO_TEXTURE_ARB ? "T" : "F" ));
// add more as needed later..
}
}
#endif
}
if ( (options & (1<<eDumpVertexAttribSetup)) && is_draw )
{
GLMVertexSetup *pSetup = &m_drawVertexSetup;
uint nRelevantMask = pSetup->m_attrMask;
for( int index=0; index < kGLMVertexAttributeIndexMax; index++ )
{
uint mask = 1<<index;
if (nRelevantMask & mask)
{
GLMVertexAttributeDesc *setdesc = &pSetup->m_attrs[index];
char sizestr[100];
if (setdesc->m_nCompCount < 32)
{
sprintf( sizestr, "%d", setdesc->m_nCompCount);
}
else
{
strcpy( sizestr, GLMDecode( eGL_ENUM, setdesc->m_nCompCount ) );
}
if (pSetup->m_vtxAttribMap[index] != 0xBB)
{
GLMPRINTF(("-D- attr=%-2d decl=$%s%1d stride=%-2d offset=%-3d buf=%08x size=%s type=%s normalized=%s ",
index,
GLMDecode(eD3D_VTXDECLUSAGE, pSetup->m_vtxAttribMap[index]>>4 ),
pSetup->m_vtxAttribMap[index]&0x0F,
setdesc->m_stride,
setdesc->m_offset,
setdesc->m_pBuffer,
sizestr,
GLMDecode( eGL_ENUM, setdesc->m_datatype),
setdesc->m_normalized?"Y":"N"
));
}
else
{
// the attrib map is referencing an attribute that is not wired up in the vertex setup...
DebuggerBreak();
}
}
}
}
if ( (options & (1<<eDumpVertexData)) && is_draw )
{
GLMVertexSetup *pSetup = &m_drawVertexSetup;
int start = info->m_drawStart;
int end = info->m_drawEnd;
int endLimit = start + (1<<g_maxVertsToDumpLog2);
int realEnd = MIN( end, endLimit );
// vertex data
GLMPRINTF(("-D-"));
GLMPRINTF(("-D- Vertex Data : %d of %d verts (index %d through %d)", realEnd-start, end-start, start, realEnd-1));
for( int vtxIndex=-1; vtxIndex < realEnd; vtxIndex++ ) // vtxIndex will jump from -1 to start after first spin, not necessarily to 0
{
char buf[64000];
char *mark = buf;
// index -1 is the first run through the loop, we just print a header
// iterate attrs
if (vtxIndex>=0)
{
mark += sprintf(mark, "-D- %04d: ", vtxIndex );
}
// for transform dumping, we latch values as we spot them
float vtxPos[4];
int vtxBoneIndices[4]; // only three get used
float vtxBoneWeights[4]; // only three get used and index 2 is synthesized from 0 and 1
vtxPos[0] = vtxPos[1] = vtxPos[2] = 0.0;
vtxPos[3] = 1.0;
vtxBoneIndices[0] = vtxBoneIndices[1] = vtxBoneIndices[2] = vtxBoneIndices[3] = 0;
vtxBoneWeights[0] = vtxBoneWeights[1] = vtxBoneWeights[2] = vtxBoneWeights[3] = 0.0;
for( int attr = 0; attr < kGLMVertexAttributeIndexMax; attr++ )
{
if (pSetup->m_attrMask & (1<<attr) )
{
GLMVertexAttributeDesc *desc = &pSetup->m_attrs[ attr ];
// print that attribute.
// on OSX, VB's never move unless resized. You can peek at them when unmapped. Safe enough for debug..
char *bufferBase = (char*)desc->m_pBuffer->m_pLastMappedAddress;
uint stride = desc->m_stride;
uint fieldoffset = desc->m_offset;
uint baseoffset = vtxIndex * stride;
char *attrBase = bufferBase + baseoffset + fieldoffset;
uint usage = pSetup->m_vtxAttribMap[attr]>>4;
uint usageindex = pSetup->m_vtxAttribMap[attr]&0x0F;
if (vtxIndex <0)
{
mark += sprintf(mark, "[%s%1d @ offs=%04d / strd %03d] ", GLMDecode(eD3D_VTXDECLUSAGE, usage ), usageindex, fieldoffset, stride );
}
else
{
mark += sprintf(mark, "[%s%1d ", GLMDecode(eD3D_VTXDECLUSAGE, usage ), usageindex );
if (desc->m_nCompCount<32)
{
for( uint which = 0; which < desc->m_nCompCount; which++ )
{
static char *fieldname = "xyzw";
switch( desc->m_datatype )
{
case GL_FLOAT:
{
float *floatbase = (float*)attrBase;
mark += sprintf(mark, (usage != D3DDECLUSAGE_TEXCOORD) ? "%c%7.3f " : "%c%.3f", fieldname[which], floatbase[which] );
if (usage==D3DDECLUSAGE_POSITION)
{
if (which<4)
{
// latch pos
vtxPos[which] = floatbase[which];
}
}
if (usage==D3DDECLUSAGE_BLENDWEIGHT)
{
if (which<4)
{
// latch weight
vtxBoneWeights[which] = floatbase[which];
}
}
}
break;
case GL_UNSIGNED_BYTE:
{
unsigned char *unchbase = (unsigned char*)attrBase;
mark += sprintf(mark, "%c$%02X ", fieldname[which], unchbase[which] );
}
break;
default:
// hold off on other formats for now
mark += sprintf(mark, "%c????? ", fieldname[which] );
break;
}
}
}
else // special path for BGRA bytes which are expressed in GL by setting the *size* to GL_BGRA (gross large enum)
{
switch(desc->m_nCompCount)
{
case GL_BGRA: // byte reversed color
{
for( int which = 0; which < 4; which++ )
{
static const char *fieldname = "BGRA";
switch( desc->m_datatype )
{
case GL_UNSIGNED_BYTE:
{
unsigned char *unchbase = (unsigned char*)attrBase;
mark += sprintf(mark, "%c$%02X ", fieldname[which], unchbase[which] );
if (usage==D3DDECLUSAGE_BLENDINDICES)
{
if (which<4)
{
// latch index
vtxBoneIndices[which] = unchbase[which]; // ignoring the component reverse which BGRA would inflict, but we also ignore it below so it matches up.
}
}
}
break;
default:
DebuggerBreak();
break;
}
}
}
break;
}
}
mark += sprintf(mark, "] " );
}
}
}
GLMPrintStr( buf, eDebugDump );
if (vtxIndex >=0)
{
// if transform dumping requested, and we've reached the actual vert dump phase, do it
float vtxout[4];
char *translabel = NULL; // NULL means no print...
switch( g_vertDumpMode )
{
case eDumpVertsNoTransformDump: break;
case eDumpVertsTransformedByViewProj: // viewproj is slot 8
{
float *viewproj = &m_programParamsF[ kGLMVertexProgram ].m_values[8][0];
transform_dp4( vtxPos, viewproj, 4, vtxout );
translabel = "post-viewproj";
}
break;
case eDumpVertsTransformedByModelViewProj: // modelviewproj is slot 4
{
float *modelviewproj = &m_programParamsF[ kGLMVertexProgram ].m_values[4][0];
transform_dp4( vtxPos, modelviewproj, 4, vtxout );
translabel = "post-modelviewproj";
}
break;
case eDumpVertsTransformedByBoneZeroThenViewProj:
{
float postbone[4];
postbone[3] = 1.0;
float *bonemat = &m_programParamsF[ kGLMVertexProgram ].m_values[58][0];
transform_dp4( vtxPos, bonemat, 3, postbone );
float *viewproj = &m_programParamsF[ kGLMVertexProgram ].m_values[8][0]; // viewproj is slot 8
transform_dp4( postbone, viewproj, 4, vtxout );
translabel = "post-bone0-viewproj";
}
break;
case eDumpVertsTransformedByBonesThenViewProj:
{
//float bone[4][4]; // [bone index][bone member] // members are adjacent
vtxout[0] = vtxout[1] = vtxout[2] = vtxout[3] = 0;
// unpack the third weight
vtxBoneWeights[2] = 1.0 - (vtxBoneWeights[0] + vtxBoneWeights[1]);
for( int ibone=0; ibone<3; ibone++ )
{
int boneindex = vtxBoneIndices[ ibone ];
float *bonemat = &m_programParamsF[ kGLMVertexProgram ].m_values[58+(boneindex*3)][0];
float boneweight = vtxBoneWeights[ibone];
float postbonevtx[4];
transform_dp4( vtxPos, bonemat, 3, postbonevtx );
// add weighted sum into output
for( int which=0; which<4; which++ )
{
vtxout[which] += boneweight * postbonevtx[which];
}
}
// fix W ? do we care ? check shaders to see what they do...
translabel = "post-skin3bone-viewproj";
}
break;
}
if(translabel)
{
// for extra credit, do the perspective divide and viewport
GLMPRINTF(("-D- %-24s: [ %7.4f %7.4f %7.4f %7.4f ]", translabel, vtxout[0],vtxout[1],vtxout[2],vtxout[3] ));
GLMPRINTF(("-D-" ));
}
}
if (vtxIndex<0)
{
vtxIndex = start-1; // for printing of the data (note it will be incremented at bottom of loop, so bias down by 1)
}
else
{ // no more < and > around vert dump lines
//mark += sprintf(mark, "" );
}
}
}
if (options & (1<<eOpenShadersForEdit) )
{
#if GLMDEBUG
if (m_drawingProgram[ kGLMVertexProgram ])
{
m_drawingProgram[ kGLMVertexProgram ]->m_editable->OpenInEditor();
}
if (m_drawingProgram[ kGLMFragmentProgram ])
{
m_drawingProgram[ kGLMFragmentProgram ]->m_editable->OpenInEditor();
}
#endif
}
/*
if (options & (1<<))
{
}
*/
// trailer line
GLMPRINTF(("-D- ===================================================================================== end %s %d frame %d", batchtype, m_nBatchCounter, m_debugFrameIndex ));
GLMSetIndent(oldIndent);
}
// here is the table that binds knob numbers to names. change at will.
char *g_knobnames[] =
{
/*0*/ "dummy",
/*1*/ "FB-SRGB",
#if 0
/*1*/ "tex-U0-bias", // src left
/*2*/ "tex-V0-bias", // src upper
/*3*/ "tex-U1-bias", // src right
/*4*/ "tex-V1-bias", // src bottom
/*5*/ "pos-X0-bias", // dst left
/*6*/ "pos-Y0-bias", // dst upper
/*7*/ "pos-X1-bias", // dst right
/*8*/ "pos-Y1-bias", // dst bottom
#endif
};
int g_knobcount = sizeof( g_knobnames ) / sizeof( g_knobnames[0] );
void GLMContext::DebugHook( GLMDebugHookInfo *info )
{
// FIXME: This has seriously bitrotted.
return;
bool debughook = false;
// debug hook is called after an action has taken place.
// that would be the initial action, or a repeat.
// if paused, we stay inside this function until return.
// when returning, we inform the caller if it should repeat its last action or continue.
// there is no global pause state. The rest of the app runs at the best speed it can.
// initial stuff we do unconditionally
// increment iteration
info->m_iteration++; // can be thought of as "number of times the caller's action has now occurred - starting at 1"
// now set initial state guess for the info block (outcome may change below)
info->m_loop = false;
// check prior hold-conditions to see if any of them hit.
// note we disarm each trigger once the hold has occurred (one-shot style)
switch( info->m_caller )
{
case eBeginFrame:
if (debughook) GLMPRINTF(("-D- Caller: BeginFrame" ));
if ( (m_holdFrameBegin>=0) && (m_holdFrameBegin==m_debugFrameIndex) ) // did we hit a frame breakpoint?
{
if (debughook) GLMPRINTF(("-D- BeginFrame trigger match, clearing m_holdFrameBegin, hold=true" ));
m_holdFrameBegin = -1;
info->m_holding = true;
}
break;
case eClear:
if (debughook) GLMPRINTF(("-D- Caller: Clear" ));
if ( (m_holdBatch>=0) && (m_holdBatchFrame>=0) && ((int)m_holdBatch==(int)m_nBatchCounter) && ((int)m_holdBatchFrame==(int)m_debugFrameIndex) )
{
if (debughook) GLMPRINTF(("-D- Clear trigger match, clearing m_holdBatch&Frame, hold=true" ));
m_holdBatch = m_holdBatchFrame = -1;
info->m_holding = true;
}
break;
case eDrawElements:
if (debughook) GLMPRINTF(( (info->m_caller==eClear) ? "-D- Caller: Clear" : "-D- Caller: Draw" ));
if ( (m_holdBatch>=0) && (m_holdBatchFrame>=0) && ((int)m_holdBatch==(int)m_nBatchCounter) && ((int)m_holdBatchFrame==(int)m_debugFrameIndex) )
{
if (debughook) GLMPRINTF(("-D- Draw trigger match, clearing m_holdBatch&Frame, hold=true" ));
m_holdBatch = m_holdBatchFrame = -1;
info->m_holding = true;
}
break;
case eEndFrame:
if (debughook) GLMPRINTF(("-D- Caller: EndFrame" ));
// check for any expired batch hold req
if ( (m_holdBatch>=0) && (m_holdBatchFrame>=0) && (m_holdBatchFrame==m_debugFrameIndex) )
{
// you tried to say 'next batch', but there wasn't one in this frame.
// target first batch of next frame instead
if (debughook) GLMPRINTF(("-D- EndFrame noticed an expired draw hold trigger, rolling to next frame, hold=false"));
m_holdBatch = 0;
m_holdBatchFrame++;
info->m_holding = false;
}
// now check for an explicit hold on end of this frame..
if ( (m_holdFrameEnd>=0) && (m_holdFrameEnd==m_debugFrameIndex) )
{
if (debughook) GLMPRINTF(("-D- EndFrame trigger match, clearing m_holdFrameEnd, hold=true" ));
m_holdFrameEnd = -1;
info->m_holding = true;
}
break;
}
// spin until event queue is empty *and* hold is false
int evtcount=0;
bool refresh = info->m_holding || m_debugDelayEnable; // only refresh once per initial visit (if paused!) or follow up event input
int breakToDebugger = 0;
// 1 = break to GDB
// 2 = break to OpenGL Profiler if attached
do
{
if (refresh)
{
if (debughook) GLMPRINTF(("-D- pushing pixels" ));
DebugPresent(); // show pixels
uint minidumpOptions = (1<<eDumpBatchInfo) /* | (1<<eDumpSurfaceInfo) */;
DebugDump( info, minidumpOptions, g_vertDumpMode );
ThreadSleep( 10000 / 1000 ); // lil sleep
refresh = false;
}
bool eventCheck = true; // event pull will be skipped if we detect a shader edit being done
// keep editable shaders in sync
#if GLMDEBUG
bool redrawBatch = false;
if (m_drawingProgram[ kGLMVertexProgram ])
{
if( m_drawingProgram[ kGLMVertexProgram ]->SyncWithEditable() )
{
redrawBatch = true;
}
}
if (m_drawingProgram[ kGLMFragmentProgram ])
{
if( m_drawingProgram[ kGLMFragmentProgram ]->SyncWithEditable() )
{
redrawBatch = true;
}
}
if (redrawBatch)
{
// act as if user pressed the option-\ key
if (m_drawingLang == kGLMGLSL)
{
// if GLSL mode, force relink - and refresh the pair cache as needed
if (m_pBoundPair)
{
// fix it in place
m_pBoundPair->RefreshProgramPair();
}
}
// TODO - need to retest this whole path
FlushDrawStates( 0, 0, 0 ); // this is key, because the linked shader pair may have changed (note call to PurgePairsWithShader in cglmprogram.cpp)
GLMPRINTF(("-- Shader changed, re-running batch" ));
m_holdBatch = m_nBatchCounter;
m_holdBatchFrame = m_debugFrameIndex;
m_debugDelayEnable = false;
info->m_holding = false;
info->m_loop = true;
eventCheck = false;
}
#endif
if(eventCheck)
{
PumpWindowsMessageLoop();
CCocoaEvent evt;
evtcount = GetEvents( &evt, 1, true ); // asking for debug events only.
if (evtcount)
{
// print it
if (debughook) GLMPRINTF(("-D- Received debug key '%c' with modifiers %x", evt.m_UnicodeKeyUnmodified, evt.m_ModifierKeyMask ));
// flag for refresh if we spin again
refresh = 1;
switch(evt.m_UnicodeKeyUnmodified)
{
case ' ': // toggle pause
// clear all the holds to be sure
m_holdFrameBegin = m_holdFrameEnd = m_holdBatch = m_holdBatchFrame = -1;
info->m_holding = !info->m_holding;
if (!info->m_holding)
{
m_debugDelayEnable = false; // coming out of pause means no slow mo
}
GLMPRINTF((info->m_holding ? "-D- Paused." : "-D- Unpaused." ));
break;
case 'f': // frame advance
GLMPRINTF(("-D- Command: next frame" ));
m_holdFrameBegin = m_debugFrameIndex+1; // stop at top of next numbered frame
m_debugDelayEnable = false; // get there fast
info->m_holding = false;
break;
case ']': // ahead 1 batch
case '}': // ahead ten batches
{
int delta = evt.m_UnicodeKeyUnmodified == ']' ? 1 : 10;
m_holdBatch = m_nBatchCounter+delta;
m_holdBatchFrame = m_debugFrameIndex;
m_debugDelayEnable = false; // get there fast
info->m_holding = false;
GLMPRINTF(("-D- Command: advance %d batches to %d", delta, m_holdBatch ));
}
break;
case '[': // back one batch
case '{': // back 10 batches
{
int delta = evt.m_UnicodeKeyUnmodified == '[' ? -1 : -10;
m_holdBatch = m_nBatchCounter + delta;
if (m_holdBatch<0)
{
m_holdBatch = 0;
}
m_holdBatchFrame = m_debugFrameIndex+1; // next frame, but prev batch #
m_debugDelayEnable = false; // get there fast
info->m_holding = false;
GLMPRINTF(("-D- Command: rewind %d batches to %d", delta, m_holdBatch ));
}
break;
case '\\': // batch rerun
m_holdBatch = m_nBatchCounter;
m_holdBatchFrame = m_debugFrameIndex;
m_debugDelayEnable = false;
info->m_holding = false;
info->m_loop = true;
GLMPRINTF(("-D- Command: re-run batch %d", m_holdBatch ));
break;
case 'c': // toggle auto color clear
m_autoClearColor = !m_autoClearColor;
GLMPRINTF((m_autoClearColor ? "-D- Auto color clear ON" : "-D- Auto color clear OFF" ));
break;
case 's': // toggle auto stencil clear
m_autoClearStencil = !m_autoClearStencil;
GLMPRINTF((m_autoClearStencil ? "-D- Auto stencil clear ON" : "-D- Auto stencil clear OFF" ));
break;
case 'd': // toggle auto depth clear
m_autoClearDepth = !m_autoClearDepth;
GLMPRINTF((m_autoClearDepth ? "-D- Auto depth clear ON" : "-D- Auto depth clear OFF" ));
break;
case '.': // break to debugger or insta-quit
if (evt.m_ModifierKeyMask & (1<<eControlKey))
{
GLMPRINTF(( "-D- INSTA QUIT! (TM) (PAT PEND)" ));
abort();
}
else
{
GLMPRINTF(( "-D- Breaking to debugger" ));
breakToDebugger = 1;
info->m_holding = true;
info->m_loop = true; // so when you come back from debugger, you get another spin (i.e. you enter paused mode)
}
break;
case 'g': // break to OGLP and enable OGLP logging of spew
if (GLMDetectOGLP()) // if this comes back true, there will be a breakpoint set on glColor4sv.
{
uint channelMask = GLMDetectAvailableChannels(); // will re-assert whether spew goes to OGLP log
if (channelMask & (1<<eGLProfiler))
{
GLMDebugChannelMask(&channelMask);
breakToDebugger = 2;
info->m_holding = true;
info->m_loop = true; // so when you come back from debugger, you get another spin (i.e. you enter paused mode)
}
}
break;
case '_': // toggle slow mo
m_debugDelayEnable = !m_debugDelayEnable;
break;
case '-': // go slower
if (m_debugDelayEnable)
{
// already in slow mo, so lower speed
m_debugDelay <<= 1; // double delay
if (m_debugDelay > (1<<24))
{
m_debugDelay = (1<<24);
}
}
else
{
// enter slow mo
m_debugDelayEnable = true;
}
break;
case '=': // go faster
if (m_debugDelayEnable)
{
// already in slow mo, so raise speed
m_debugDelay >>= 1; // halve delay
if (m_debugDelay < (1<<17))
{
m_debugDelay = (1<<17);
}
}
else
{
// enter slow mo
m_debugDelayEnable = true;
}
break;
case 'v':
// open vs in editor (foreground pop)
#if GLMDEBUG
if (m_drawingProgram[ kGLMVertexProgram ])
{
m_drawingProgram[ kGLMVertexProgram ]->m_editable->OpenInEditor( true );
}
#endif
break;
case 'p':
// open fs/ps in editor (foreground pop)
#if GLMDEBUG
if (m_drawingProgram[ kGLMFragmentProgram ])
{
m_drawingProgram[ kGLMFragmentProgram ]->m_editable->OpenInEditor( true );
}
#endif
break;
case '<': // dump fewer verts
case '>': // dump more verts
{
int delta = (evt.m_UnicodeKeyUnmodified=='>') ? 1 : -1;
g_maxVertsToDumpLog2 = MIN( MAX( g_maxVertsToDumpLog2+delta, 0 ), 16 );
// just re-dump the verts
DebugDump( info, 1<<eDumpVertexData, g_vertDumpMode );
}
break;
case 'x': // adjust transform dump mode
{
int newmode = g_vertDumpMode+1;
if (newmode >= eLastDumpVertsMode)
{
// wrap
newmode = eDumpVertsNoTransformDump;
}
g_vertDumpMode = (EGLMVertDumpMode)newmode;
GLMPRINTF(("-D- New vert dump mode is %s", g_vertDumpModeNames[g_vertDumpMode] ));
}
break;
case 'u': // more crawl
{
CStackCrawlParams cp;
memset( &cp, 0, sizeof(cp) );
cp.m_frameLimit = kMaxCrawlFrames;
GetStackCrawl(&cp);
GLMPRINTF(("-D-" ));
GLMPRINTF(("-D- extended stack crawl:"));
for( uint i=0; i< cp.m_frameCount; i++)
{
GLMPRINTF(("-D-\t%s", cp.m_crawlNames[i] ));
}
}
break;
case 'q':
DebugDump( info, 0xFFFFFFFF, g_vertDumpMode );
break;
case 'H':
case 'h':
{
// toggle drawing language. hold down shift key to do it immediately.
if (m_caps.m_hasDualShaders)
{
bool immediate;
immediate = evt.m_UnicodeKeyUnmodified == 'H'; // (evt.m_ModifierKeyMask & (1<<eShiftKey)) != 0;
if (m_drawingLang==kGLMARB)
{
GLMPRINTF(( "-D- Setting GLSL language mode %s.", immediate ? "immediately" : "for next frame start" ));
SetDrawingLang( kGLMGLSL, immediate );
}
else
{
GLMPRINTF(( "-D- Setting ARB language mode %s.", immediate ? "immediately" : "for next frame start" ));
SetDrawingLang( kGLMARB, immediate );
}
refresh = immediate;
}
else
{
GLMPRINTF(("You can't change shader languages unless you launch with -glmdualshaders enabled"));
}
}
break;
// ======================================================== debug knobs. change these as needed to troubleshoot stuff
// keys to select a knob
// or, toggle a debug flavor, if control is being held down
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
{
if (evt.m_ModifierKeyMask & (1<<eControlKey))
{
// '0' toggles the all-channels on or off
int flavorSelect = evt.m_UnicodeKeyUnmodified - '0';
if ( (flavorSelect >=0) && (flavorSelect<eFlavorCount) )
{
uint mask = GLMDebugFlavorMask();
mask ^= (1<<flavorSelect);
GLMDebugFlavorMask(&mask);
}
}
else
{
// knob selection
m_selKnobIndex = evt.m_UnicodeKeyUnmodified - '0';
GLMPRINTF(("-D- Knob # %d (%s) selected.", m_selKnobIndex, g_knobnames[ m_selKnobIndex ] ));
m_selKnobIncrement = (m_selKnobIndex<5) ? (1.0f / 2048.0f) : (1.0 / 256.0f);
ThreadSleep( 500000 / 1000 );
}
refresh = false;
}
break;
// keys to adjust or zero a knob
case 't': // toggle
{
if (m_selKnobIndex < g_knobcount)
{
GLMKnobToggle( g_knobnames[ m_selKnobIndex ] );
}
}
break;
case 'l': // less
case 'm': // more
case 'z': // zero
{
if (m_selKnobIndex < g_knobcount)
{
float val = GLMKnob( g_knobnames[ m_selKnobIndex ], NULL );
if (evt.m_UnicodeKeyUnmodified == 'l')
{
// minus (less)
val -= m_selKnobIncrement;
if (val < m_selKnobMinValue)
{
val = m_selKnobMinValue;
}
// send new value back to the knob
GLMKnob( g_knobnames[ m_selKnobIndex ], &val );
}
if (evt.m_UnicodeKeyUnmodified == 'm')
{
// plus (more)
val += m_selKnobIncrement;
if (val > m_selKnobMaxValue)
{
val = m_selKnobMaxValue;
}
// send new value back to the knob
GLMKnob( g_knobnames[ m_selKnobIndex ], &val );
}
if (evt.m_UnicodeKeyUnmodified == 'z')
{
// zero
val = 0.0f;
// send new value back to the knob
GLMKnob( g_knobnames[ m_selKnobIndex ], &val );
}
GLMPRINTF(("-D- Knob # %d (%s) set to %f (%f/1024.0)", m_selKnobIndex, g_knobnames[ m_selKnobIndex ], val, val * 1024.0 ));
ThreadSleep( 500000 / 1000 );
refresh = false;
}
}
break;
}
}
}
} while( ((evtcount>0) || info->m_holding) && (!breakToDebugger) );
if (m_debugDelayEnable)
{
ThreadSleep( m_debugDelay / 1000 );
}
if (breakToDebugger)
{
switch (breakToDebugger)
{
case 1:
DebuggerBreak();
break;
case 2:
// What the fuck?
break;
}
// re-flush all GLM states so you can fiddle with them in the debugger. then run the batch again and spin..
ForceFlushStates();
}
}
void GLMContext::DebugPresent( void )
{
CGLMTex *drawBufferTex = m_drawingFBO->m_attach[kAttColor0].m_tex;
gGL->glFinish();
Present( drawBufferTex );
}
void GLMContext::DebugClear( void )
{
// get old clear color
GLClearColor_t clearcol_orig;
m_ClearColor.Read( &clearcol_orig,0 );
// new clear color
GLClearColor_t clearcol;
clearcol.r = m_autoClearColorValues[0];
clearcol.g = m_autoClearColorValues[1];
clearcol.b = m_autoClearColorValues[2];
clearcol.a = m_autoClearColorValues[3];
m_ClearColor.Write( &clearcol ); // don't check, don't defer
uint mask = 0;
if (m_autoClearColor) mask |= GL_COLOR_BUFFER_BIT;
if (m_autoClearDepth) mask |= GL_DEPTH_BUFFER_BIT;
if (m_autoClearStencil) mask |= GL_STENCIL_BUFFER_BIT;
gGL->glClear( mask );
gGL->glFinish();
// put old color back
m_ClearColor.Write( &clearcol_orig ); // don't check, don't defer
}
#endif
void GLMContext::CheckNative( void )
{
// note that this is available in release. We don't use GLMPRINTF for that reason.
// note we do not get called unless either slow-batch asserting or logging is enabled.
#ifdef OSX
bool gpuProcessing;
GLint fragmentGPUProcessing, vertexGPUProcessing;
CGLGetParameter (CGLGetCurrentContext(), kCGLCPGPUFragmentProcessing, &fragmentGPUProcessing);
CGLGetParameter(CGLGetCurrentContext(), kCGLCPGPUVertexProcessing, &vertexGPUProcessing);
// spews then asserts.
// that way you can enable both, get log output on a pair if it's slow, and then the debugger will pop.
if(m_slowSpewEnable)
{
if ( !vertexGPUProcessing )
{
m_drawingProgram[ kGLMVertexProgram ]->LogSlow( m_drawingLang );
}
if ( !fragmentGPUProcessing )
{
m_drawingProgram[ kGLMFragmentProgram ]->LogSlow( m_drawingLang );
}
}
if(m_slowAssertEnable)
{
if ( !vertexGPUProcessing || !fragmentGPUProcessing)
{
Assert( !"slow batch" );
}
}
#else
//Assert( !"impl GLMContext::CheckNative()" );
if (m_checkglErrorsAfterEveryBatch)
{
// This is slow, and somewhat redundant (-gldebugoutput uses the GL_ARB_debug_output extension, which can be at least asynchronous), but having a straightforward backup can be useful.
// This is useful for callstack purposes - GL_ARB_debug_output may break in a different thread that the thread triggering the GL error.
//gGL->glFlush();
GLenum errorcode = (GLenum)gGL->glGetError();
if ( errorcode != GL_NO_ERROR )
{
const char *decodedStr = GLMDecode( eGL_ERROR, errorcode );
char buf[512];
V_snprintf( buf, sizeof( buf), "\nGL ERROR! %08x = '%s'\n", errorcode, decodedStr );
// Make sure the dev sees something, because these errors can happen early enough that DevMsg() does nothing.
#ifdef WIN32
OutputDebugStringA( buf );
#else
printf( "%s", buf );
#endif
}
}
#endif
}
// debug font
void GLMContext::GenDebugFontTex( void )
{
if(!m_debugFontTex)
{
// make a 128x128 RGBA texture
GLMTexLayoutKey key;
memset( &key, 0, sizeof(key) );
key.m_texGLTarget = GL_TEXTURE_2D;
key.m_xSize = 128;
key.m_ySize = 128;
key.m_zSize = 1;
key.m_texFormat = D3DFMT_A8R8G8B8;
key.m_texFlags = 0;
m_debugFontTex = NewTex( &key, 1, "GLM debug font" );
//-----------------------------------------------------
GLMTexLockParams lockreq;
lockreq.m_tex = m_debugFontTex;
lockreq.m_face = 0;
lockreq.m_mip = 0;
GLMTexLayoutSlice *slice = &m_debugFontTex->m_layout->m_slices[ lockreq.m_tex->CalcSliceIndex( lockreq.m_face, lockreq.m_mip ) ];
lockreq.m_region.xmin = lockreq.m_region.ymin = lockreq.m_region.zmin = 0;
lockreq.m_region.xmax = slice->m_xSize;
lockreq.m_region.ymax = slice->m_ySize;
lockreq.m_region.zmax = slice->m_zSize;
lockreq.m_readback = false;
char *lockAddress;
int yStride;
int zStride;
m_debugFontTex->Lock( &lockreq, &lockAddress, &yStride, &zStride );
//-----------------------------------------------------
// fetch elements of font data and make texels... we're doing the whole slab so we don't really need the stride info
uint32 *destTexelPtr = (uint32 *)lockAddress;
for( int index = 0; index < 16384; index++ )
{
if (g_glmDebugFontMap[index] == ' ')
{
// clear
*destTexelPtr = 0x00000000;
}
else
{
// opaque white (drawing code can modulate if desired)
*destTexelPtr = 0xFFFFFFFF;
}
destTexelPtr++;
}
//-----------------------------------------------------
GLMTexLockParams unlockreq;
unlockreq.m_tex = m_debugFontTex;
unlockreq.m_face = 0;
unlockreq.m_mip = 0;
// region need not matter for unlocks
unlockreq.m_region.xmin = unlockreq.m_region.ymin = unlockreq.m_region.zmin = 0;
unlockreq.m_region.xmax = unlockreq.m_region.ymax = unlockreq.m_region.zmax = 0;
unlockreq.m_readback = false;
m_debugFontTex->Unlock( &unlockreq );
//-----------------------------------------------------
// change up the tex sampling on this texture to be "nearest" not linear
//-----------------------------------------------------
// don't leave texture bound on the TMU
BindTexToTMU( NULL, 0 );
// also make the index and vertex buffers for use - up to 1K indices and 1K verts
uint indexBufferSize = 1024*2;
m_debugFontIndices = NewBuffer(kGLMIndexBuffer, indexBufferSize, 0); // two byte indices
// we go ahead and lock it now, and fill it with indices 0-1023.
char *indices = NULL;
GLMBuffLockParams idxLock;
idxLock.m_nOffset = 0;
idxLock.m_nSize = indexBufferSize;
idxLock.m_bNoOverwrite = false;
idxLock.m_bDiscard = true;
m_debugFontIndices->Lock( &idxLock, &indices );
for( int i=0; i<1024; i++)
{
unsigned short *idxPtr = &((unsigned short*)indices)[i];
*idxPtr = i;
}
m_debugFontIndices->Unlock();
m_debugFontVertices = NewBuffer(kGLMVertexBuffer, 1024 * 128, 0); // up to 128 bytes per vert
}
}
#define MAX_DEBUG_CHARS 256
struct GLMDebugTextVertex
{
float x,y,z;
float u,v;
char rgba[4];
};
void GLMContext::DrawDebugText( float x, float y, float z, float drawCharWidth, float drawCharHeight, char *string )
{
if (!m_debugFontTex)
{
GenDebugFontTex();
}
// setup needed to draw text
// we're assuming that +x goes left to right on screen, no billboarding math in here
// and that +y goes bottom up
// caller knows projection / rectangle so it gets to decide vertex spacing
// debug font must be bound to TMU 0
// texturing enabled
// alpha blending enabled
// generate a quad per character
// characters are 6px wide by 11 px high.
// upper left character in tex is 0x20
// y axis will need to be flipped for display
// for any character in 0x20 - 0x7F - here are the needed UV's
// leftU = ((character % 16) * 6.0f / 128.0f)
// rightU = lowU + (6.0 / 128.0);
// topV = ((character - 0x20) * 11.0f / 128.0f)
// bottomV = lowV + (11.0f / 128.0f)
int stringlen = strlen( string );
if (stringlen > MAX_DEBUG_CHARS)
{
stringlen = MAX_DEBUG_CHARS;
}
// lock
char *vertices = NULL;
GLMBuffLockParams vtxLock;
vtxLock.m_nOffset = 0;
vtxLock.m_nSize = 1024 * stringlen;
vtxLock.m_bNoOverwrite = false;
vtxLock.m_bDiscard = false;
m_debugFontVertices->Lock( &vtxLock, &vertices );
GLMDebugTextVertex *vtx = (GLMDebugTextVertex*)vertices;
GLMDebugTextVertex *vtxOutPtr = vtx;
for( int charindex = 0; charindex < stringlen; charindex++ )
{
float leftU,rightU,topV,bottomV;
int character = (int)string[charindex];
character -= 0x20;
if ( (character<0) || (character > 0x7F) )
{
character = '*' - 0x20;
}
leftU = ((character & 0x0F) * 6.0f ) / 128.0f;
rightU = leftU + (6.0f / 128.0f);
topV = ((character >> 4) * 11.0f ) / 128.0f;
bottomV = topV + (11.0f / 128.0f);
float posx,posy,posz;
posx = x + (drawCharWidth * (float)charindex);
posy = y;
posz = z;
// generate four verts
// first vert will be upper left of displayed quad (low X, high Y) then we go clockwise
for( int quadvert = 0; quadvert < 4; quadvert++ )
{
bool isTop = (quadvert <2); // verts 0 and 1
bool isLeft = (quadvert & 1) == (quadvert >> 1); // verts 0 and 3
vtxOutPtr->x = posx + (isLeft ? 0.0f : drawCharWidth);
vtxOutPtr->y = posy + (isTop ? drawCharHeight : 0.0f);
vtxOutPtr->z = posz;
vtxOutPtr->u = isLeft ? leftU : rightU;
vtxOutPtr->v = isTop ? topV : bottomV;
vtxOutPtr++;
}
}
// verts are done.
// unlock...
m_debugFontVertices->Unlock();
// make a vertex setup
GLMVertexSetup vertSetup;
// position, color, tc = 0, 3, 8
vertSetup.m_attrMask = (1<<kGLMGenericAttr00) | (1<<kGLMGenericAttr03) | (1<<kGLMGenericAttr08);
vertSetup.m_attrs[kGLMGenericAttr00].m_pBuffer = m_debugFontVertices;
vertSetup.m_attrs[kGLMGenericAttr00].m_nCompCount = 3; // 3 floats
vertSetup.m_attrs[kGLMGenericAttr00].m_datatype = GL_FLOAT;
vertSetup.m_attrs[kGLMGenericAttr00].m_stride = sizeof(GLMDebugTextVertex);
vertSetup.m_attrs[kGLMGenericAttr00].m_offset = offsetof(GLMDebugTextVertex, x);
vertSetup.m_attrs[kGLMGenericAttr00].m_normalized= false;
vertSetup.m_attrs[kGLMGenericAttr03].m_pBuffer = m_debugFontVertices;
vertSetup.m_attrs[kGLMGenericAttr03].m_nCompCount = 4; // four bytes
vertSetup.m_attrs[kGLMGenericAttr03].m_datatype = GL_UNSIGNED_BYTE;
vertSetup.m_attrs[kGLMGenericAttr03].m_stride = sizeof(GLMDebugTextVertex);
vertSetup.m_attrs[kGLMGenericAttr03].m_offset = offsetof(GLMDebugTextVertex, rgba);
vertSetup.m_attrs[kGLMGenericAttr03].m_normalized= true;
vertSetup.m_attrs[kGLMGenericAttr08].m_pBuffer = m_debugFontVertices;
vertSetup.m_attrs[kGLMGenericAttr08].m_nCompCount = 2; // 2 floats
vertSetup.m_attrs[kGLMGenericAttr08].m_datatype = GL_FLOAT;
vertSetup.m_attrs[kGLMGenericAttr08].m_stride = sizeof(GLMDebugTextVertex);
vertSetup.m_attrs[kGLMGenericAttr08].m_offset = offsetof(GLMDebugTextVertex, u);
vertSetup.m_attrs[kGLMGenericAttr03].m_normalized= false;
// bind texture and draw it..
CGLMTex *pPrevTex = m_samplers[0].m_pBoundTex;
BindTexToTMU( m_debugFontTex, 0 );
SelectTMU(0); // somewhat redundant
gGL->glDisable( GL_DEPTH_TEST );
gGL->glEnable(GL_TEXTURE_2D);
SetVertexAttributes( &vertSetup );
gGL->glDrawArrays( GL_QUADS, 0, stringlen * 4 );
SetVertexAttributes( NULL );
gGL->glDisable(GL_TEXTURE_2D);
BindTexToTMU( pPrevTex, 0 );
}
//===============================================================================
void GLMgrSelfTests( void )
{
return; // until such time as the tests are revised or axed
GLMDisplayParams glmParams;
glmParams.m_fsEnable = false;
glmParams.m_vsyncEnable = false; // "The runtime updates the window client area immediately and might do so more
glmParams.m_backBufferWidth = 1024;
glmParams.m_backBufferHeight = 768;
glmParams.m_backBufferFormat = D3DFMT_A8R8G8B8;
glmParams.m_multiSampleCount = 2;
glmParams.m_enableAutoDepthStencil = true;
glmParams.m_autoDepthStencilFormat = D3DFMT_D24S8;
glmParams.m_fsRefreshHz = 60;
glmParams.m_mtgl = true;
glmParams.m_focusWindow = 0;
// make a new context on renderer 0.
GLMContext *ctx = GLMgr::aGLMgr()->NewContext( NULL, &glmParams ); ////FIXME you can't make contexts this way any more.
if (!ctx)
{
DebuggerBreak(); // no go
return;
}
// make a test object based on that context.
//int alltests[] = {0,1,2,3, -1};
//int newtests[] = {3, -1};
int twotests[] = {2, -1};
//int notests[] = {-1};
int *testlist = twotests;
GLMTestParams params;
memset( &params, 0, sizeof(params) );
params.m_ctx = ctx;
params.m_testList = testlist;
params.m_glErrToDebugger = true;
params.m_glErrToConsole = true;
params.m_intlErrToDebugger = true;
params.m_intlErrToConsole = true;
params.m_frameCount = 1000;
GLMTester testobj( &params );
testobj.RunTests( );
GLMgr::aGLMgr()->DelContext( ctx );
}
void GLMContext::SetDefaultStates( void )
{
GLM_FUNC;
CheckCurrent();
m_AlphaTestEnable.Default();
m_AlphaTestFunc.Default();
m_AlphaToCoverageEnable.Default();
m_CullFaceEnable.Default();
m_CullFrontFace.Default();
m_PolygonMode.Default();
m_DepthBias.Default();
m_ClipPlaneEnable.Default();
m_ClipPlaneEquation.Default();
m_ScissorEnable.Default();
m_ScissorBox.Default();
m_ViewportBox.Default();
m_ViewportDepthRange.Default();
m_ColorMaskSingle.Default();
m_ColorMaskMultiple.Default();
m_BlendEnable.Default();
m_BlendFactor.Default();
m_BlendEquation.Default();
m_BlendColor.Default();
//m_BlendEnableSRGB.Default(); // this isn't useful until there is an FBO bound - in fact it will trip a GL error.
m_DepthTestEnable.Default();
m_DepthFunc.Default();
m_DepthMask.Default();
m_StencilTestEnable.Default();
m_StencilFunc.Default();
m_StencilOp.Default();
m_StencilWriteMask.Default();
m_ClearColor.Default();
m_ClearDepth.Default();
m_ClearStencil.Default();
}
void GLMContext::VerifyStates ( void )
{
GLM_FUNC;
CheckCurrent();
// bare bones sanity check, head over to the debugger if our sense of the current context state is not correct
// we should only want to call this after a flush or the checks will flunk.
if( m_AlphaTestEnable.Check() ) GLMStop();
if( m_AlphaTestFunc.Check() ) GLMStop();
if( m_AlphaToCoverageEnable.Check() ) GLMStop();
if( m_CullFaceEnable.Check() ) GLMStop();
if( m_CullFrontFace.Check() ) GLMStop();
if( m_PolygonMode.Check() ) GLMStop();
if( m_DepthBias.Check() ) GLMStop();
if( m_ClipPlaneEnable.Check() ) GLMStop();
//if( m_ClipPlaneEquation.Check() ) GLMStop();
if( m_ScissorEnable.Check() ) GLMStop();
if( m_ScissorBox.Check() ) GLMStop();
if( m_ViewportBox.Check() ) GLMStop();
if( m_ViewportDepthRange.Check() ) GLMStop();
if( m_ColorMaskSingle.Check() ) GLMStop();
if( m_ColorMaskMultiple.Check() ) GLMStop();
if( m_BlendEnable.Check() ) GLMStop();
if( m_BlendFactor.Check() ) GLMStop();
if( m_BlendEquation.Check() ) GLMStop();
if( m_BlendColor.Check() ) GLMStop();
// only do this as caps permit
if (m_caps.m_hasGammaWrites)
{
if( m_BlendEnableSRGB.Check() ) GLMStop();
}
if( m_DepthTestEnable.Check() ) GLMStop();
if( m_DepthFunc.Check() ) GLMStop();
if( m_DepthMask.Check() ) GLMStop();
if( m_StencilTestEnable.Check() ) GLMStop();
if( m_StencilFunc.Check() ) GLMStop();
if( m_StencilOp.Check() ) GLMStop();
if( m_StencilWriteMask.Check() ) GLMStop();
if( m_ClearColor.Check() ) GLMStop();
if( m_ClearDepth.Check() ) GLMStop();
if( m_ClearStencil.Check() ) GLMStop();
}
static inline uint GetDataTypeSizeInBytes( GLenum dataType )
{
switch ( dataType )
{
case GL_BYTE:
case GL_UNSIGNED_BYTE:
return 1;
case GL_SHORT:
case GL_UNSIGNED_SHORT:
case GL_HALF_FLOAT:
return 2;
case GL_INT:
case GL_FLOAT:
return 4;
default:
Assert( 0 );
break;
}
return 0;
}
#if 1 //ifndef OSX
void GLMContext::DrawRangeElementsNonInline( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, uint baseVertex, CGLMBuffer *pIndexBuf )
{
#if GLMDEBUG
GLM_FUNC;
#else
//tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d-%d count:%d mode:%d type:%d", __FUNCTION__, start, end, count, mode, type );
#endif
++m_nBatchCounter;
SetIndexBuffer( pIndexBuf );
void *indicesActual = (void*)indices;
if ( pIndexBuf->m_bPseudo )
{
// you have to pass actual address, not offset
indicesActual = (void*)( (intp)indicesActual + (intp)pIndexBuf->m_pPseudoBuf );
}
if (pIndexBuf->m_bUsingPersistentBuffer)
{
indicesActual = (void*)( (intp)indicesActual + (intp)pIndexBuf->m_nPersistentBufferStartOffset );
}
#if GL_ENABLE_INDEX_VERIFICATION
// Obviously only for debugging.
if ( !pIndexBuf->IsSpanValid( (uint)indices, count * GetDataTypeSizeInBytes( type ) ) )
{
// The consumption range crosses more than one lock span, or the lock is trying to consume a bad IB range.
DXABSTRACT_BREAK_ON_ERROR();
}
if ( ( type == GL_UNSIGNED_SHORT ) && ( pIndexBuf->m_bPseudo ) )
{
Assert( start <= end );
for ( int i = 0; i < count; i++)
{
uint n = ((const uint16*)indicesActual)[i];
if ( ( n < start ) || ( n > end ) )
{
DXABSTRACT_BREAK_ON_ERROR();
}
}
unsigned char *pVertexShaderAttribMap = m_pDevice->m_vertexShader->m_vtxAttribMap;
const int nMaxVertexAttributesToCheck = m_drawingProgram[ kGLMVertexProgram ]->m_maxVertexAttrs;
IDirect3DVertexDeclaration9 *pVertDecl = m_pDevice->m_pVertDecl;
const uint8 *pVertexAttribDescToStreamIndex = pVertDecl->m_VertexAttribDescToStreamIndex;
// FIXME: Having to duplicate all this flush logic is terrible here
for( int nMask = 1, nIndex = 0; nIndex < nMaxVertexAttributesToCheck; ++nIndex, nMask <<= 1 )
{
uint8 vertexShaderAttrib = pVertexShaderAttribMap[ nIndex ];
uint nDeclIndex = pVertexAttribDescToStreamIndex[vertexShaderAttrib];
if ( nDeclIndex == 0xFF )
continue;
D3DVERTEXELEMENT9_GL *pDeclElem = &pVertDecl->m_elements[nDeclIndex];
Assert( ( ( vertexShaderAttrib >> 4 ) == pDeclElem->m_dxdecl.Usage ) && ( ( vertexShaderAttrib & 0x0F ) == pDeclElem->m_dxdecl.UsageIndex) );
const uint nStreamIndex = pDeclElem->m_dxdecl.Stream;
const D3DStreamDesc *pStream = &m_pDevice->m_streams[ nStreamIndex ];
CGLMBuffer *pBuf = m_pDevice->m_vtx_buffers[ nStreamIndex ];
if ( pBuf == m_pDevice->m_pDummy_vtx_buffer )
continue;
Assert( pStream->m_vtxBuffer->m_vtxBuffer == pBuf );
int nBufOffset = pDeclElem->m_gldecl.m_offset + pStream->m_offset;
Assert( nBufOffset >= 0 );
Assert( nBufOffset < (int)pBuf->m_nSize );
uint nBufSize = pStream->m_vtxBuffer->m_vtxBuffer->m_nSize;
uint nDataTypeSize = GetDataTypeSizeInBytes( pDeclElem->m_gldecl.m_datatype );
uint nActualStride = pStream->m_stride ? pStream->m_stride : nDataTypeSize;
uint nStart = nBufOffset + ( start + baseVertex ) * nActualStride;
uint nEnd = nBufOffset + ( end + baseVertex ) * nActualStride + nDataTypeSize;
if ( nEnd > nBufSize )
{
DXABSTRACT_BREAK_ON_ERROR();
}
if ( !pStream->m_vtxBuffer->m_vtxBuffer->IsSpanValid( nStart, nEnd - nStart ) )
{
// The draw is trying to consume a range of the bound VB that hasn't been set to valid data!
DXABSTRACT_BREAK_ON_ERROR();
}
}
}
#endif
Assert( m_drawingLang == kGLMGLSL );
if ( m_pBoundPair )
{
gGL->glDrawRangeElementsBaseVertex( mode, start, end, count, type, indicesActual, baseVertex );
#if GLMDEBUG
if ( m_slowCheckEnable )
{
CheckNative();
}
#endif
}
}
#else
// support for OSX 10.6 (no support for glDrawRangeElementsBaseVertex)
void GLMContext::DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, CGLMBuffer *pIndexBuf)
{
GLM_FUNC;
// CheckCurrent();
++m_nBatchCounter; // batch index increments unconditionally on entry
SetIndexBuffer( pIndexBuf );
void *indicesActual = (void*)indices;
if ( pIndexBuf->m_bPseudo )
{
// you have to pass actual address, not offset
indicesActual = (void*)( (int)indicesActual + (int)pIndexBuf->m_pPseudoBuf );
}
if (pIndexBuf->m_bUsingPersistentBuffer)
{
indicesActual = (void*)( (int)indicesActual + (int)pIndexBuf->m_nPersistentBufferStartOffset );
}
#if GLMDEBUG
// init debug hook information
GLMDebugHookInfo info;
memset(&info, 0, sizeof(info));
info.m_caller = eDrawElements;
// relay parameters we're operating under
info.m_drawMode = mode;
info.m_drawStart = start;
info.m_drawEnd = end;
info.m_drawCount = count;
info.m_drawType = type;
info.m_drawIndices = indices;
do
{
// obey global options re pre-draw clear
if (m_autoClearColor || m_autoClearDepth || m_autoClearStencil)
{
GLMPRINTF(("-- DrawRangeElements auto clear"));
this->DebugClear();
}
// always sync with editable shader text prior to draw
#if GLMDEBUG
// TODO - fixup OSX 10.6 m_boundProg not used in this version togl (m_pBoundPair)
//FIXME disengage this path if context is in GLSL mode..
// it will need fixes to get the shader pair re-linked etc if edits happen anyway.
if (m_boundProgram[kGLMVertexProgram])
{
m_boundProgram[kGLMVertexProgram]->SyncWithEditable();
}
else
{
AssertOnce(!"drawing with no vertex program bound");
}
if (m_boundProgram[kGLMFragmentProgram])
{
m_boundProgram[kGLMFragmentProgram]->SyncWithEditable();
}
else
{
AssertOnce(!"drawing with no fragment program bound");
}
#endif
// do the drawing
if ( m_pBoundPair )
{
gGL->glDrawRangeElements(mode, start, end, count, type, indicesActual);
// GLMCheckError();
if (m_slowCheckEnable)
{
CheckNative();
}
}
this->DebugHook(&info);
} while (info.m_loop);
#else
if ( m_pBoundPair )
{
gGL->glDrawRangeElements(mode, start, end, count, type, indicesActual);
#if GLMDEBUG
if ( m_slowCheckEnable )
{
CheckNative();
}
#endif
}
#endif
}
#endif // !OSX
#if 0
// helper function to do enable or disable in one step
void glSetEnable( GLenum which, bool enable )
{
if (enable)
gGL->glEnable(which);
else
gGL->glDisable(which);
}
// helper function for int vs enum clarity
void glGetEnumv( GLenum which, GLenum *dst )
{
gGL->glGetIntegerv( which, (int*)dst );
}
#endif
//===============================================================================
GLMTester::GLMTester(GLMTestParams *params)
{
m_params = *params;
m_drawFBO = NULL;
m_drawColorTex = NULL;
m_drawDepthTex = NULL;
}
GLMTester::~GLMTester()
{
}
void GLMTester::StdSetup( void )
{
GLMContext *ctx = m_params.m_ctx;
m_drawWidth = 1024;
m_drawHeight = 768;
// make an FBO to draw into and activate it. no depth buffer yet
m_drawFBO = ctx->NewFBO();
// make color buffer texture
GLMTexLayoutKey colorkey;
//CGLMTex *colortex;
memset( &colorkey, 0, sizeof(colorkey) );
colorkey.m_texGLTarget = GL_TEXTURE_2D;
colorkey.m_xSize = m_drawWidth;
colorkey.m_ySize = m_drawHeight;
colorkey.m_zSize = 1;
colorkey.m_texFormat = D3DFMT_A8R8G8B8;
colorkey.m_texFlags = kGLMTexRenderable;
m_drawColorTex = ctx->NewTex( &colorkey );
// do not leave that texture bound on the TMU
ctx->BindTexToTMU(NULL, 0 );
// attach color to FBO
GLMFBOTexAttachParams colorParams;
memset( &colorParams, 0, sizeof(colorParams) );
colorParams.m_tex = m_drawColorTex;
colorParams.m_face = 0;
colorParams.m_mip = 0;
colorParams.m_zslice= 0; // for clarity..
m_drawFBO->TexAttach( &colorParams, kAttColor0 );
// check it.
bool ready = m_drawFBO->IsReady();
InternalError( !ready, "drawing FBO no go");
// bind it
ctx->BindFBOToCtx( m_drawFBO, GL_FRAMEBUFFER );
gGL->glViewport(0, 0, (GLsizei) m_drawWidth, (GLsizei) m_drawHeight );
CheckGLError("stdsetup viewport");
gGL->glScissor( 0,0, (GLsizei) m_drawWidth, (GLsizei) m_drawHeight );
CheckGLError("stdsetup scissor");
//gGL->glOrtho( -1,1, -1,1, -1,1 );
CheckGLError("stdsetup ortho");
// activate debug font
ctx->GenDebugFontTex();
}
void GLMTester::StdCleanup( void )
{
GLMContext *ctx = m_params.m_ctx;
// unbind
ctx->BindFBOToCtx( NULL, GL_FRAMEBUFFER );
// del FBO
if (m_drawFBO)
{
ctx->DelFBO( m_drawFBO );
m_drawFBO = NULL;
}
// del tex
if (m_drawColorTex)
{
ctx->DelTex( m_drawColorTex );
m_drawColorTex = NULL;
}
if (m_drawDepthTex)
{
ctx->DelTex( m_drawDepthTex );
m_drawDepthTex = NULL;
}
}
void GLMTester::Clear( void )
{
GLMContext *ctx = m_params.m_ctx;
ctx->MakeCurrent();
gGL->glViewport(0, 0, (GLsizei) m_drawWidth, (GLsizei) m_drawHeight );
gGL->glScissor( 0,0, (GLsizei) m_drawWidth, (GLsizei) m_drawHeight );
//gGL->glOrtho( -1,1, -1,1, -1,1 );
CheckGLError("clearing viewport");
// clear to black
gGL->glClearColor(0.0f, 0.0f, 0.0, 1.0f);
CheckGLError("clearing color");
gGL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
CheckGLError("clearing");
//glFinish();
//CheckGLError("clear finish");
}
void GLMTester::Present( int seed )
{
GLMContext *ctx = m_params.m_ctx;
ctx->Present( m_drawColorTex );
}
void GLMTester::CheckGLError( const char *comment )
{
return;
char errbuf[1024];
//borrowed from GLMCheckError.. slightly different
if (!comment)
{
comment = "";
}
GLenum errorcode = (GLenum)gGL->glGetError();
GLenum errorcode2 = 0;
if ( errorcode != GL_NO_ERROR )
{
const char *decodedStr = GLMDecode( eGL_ERROR, errorcode );
const char *decodedStr2 = "";
if ( errorcode == GL_INVALID_FRAMEBUFFER_OPERATION )
{
// dig up the more detailed FBO status
errorcode2 = gGL->glCheckFramebufferStatus( GL_FRAMEBUFFER );
decodedStr2 = GLMDecode( eGL_ERROR, errorcode2 );
sprintf( errbuf, "\n%s - GL Error %08x/%08x = '%s / %s'\n", comment, errorcode, errorcode2, decodedStr, decodedStr2 );
}
else
{
sprintf( errbuf, "\n%s - GL Error %08x = '%s'\n", comment, errorcode, decodedStr );
}
if ( m_params.m_glErrToConsole )
{
printf("%s", errbuf );
}
if ( m_params.m_glErrToDebugger )
{
DebuggerBreak();
}
}
}
void GLMTester::InternalError( int errcode, char *comment )
{
if (errcode)
{
if (m_params.m_intlErrToConsole)
{
printf("%s - error %d\n", comment, errcode );
}
if (m_params.m_intlErrToDebugger)
{
DebuggerBreak();
}
}
}
void GLMTester::RunTests( void )
{
int *testList = m_params.m_testList;
while( (*testList >=0) && (*testList < 20) )
{
RunOneTest( *testList++ );
}
}
void GLMTester::RunOneTest( int testindex )
{
// this might be better with 'ptmf' style
switch(testindex)
{
case 0: Test0(); break;
case 1: Test1(); break;
case 2: Test2(); break;
case 3: Test3(); break;
default:
DebuggerBreak(); // unrecognized
}
}
// #####################################################################################################################
// some fixed lists which may be useful to all tests
D3DFORMAT g_drawTexFormatsGLMT[] = // -1 terminated
{
D3DFMT_A8R8G8B8,
D3DFMT_A4R4G4B4,
D3DFMT_X8R8G8B8,
D3DFMT_X1R5G5B5,
D3DFMT_A1R5G5B5,
D3DFMT_L8,
D3DFMT_A8L8,
D3DFMT_R8G8B8,
D3DFMT_A8,
D3DFMT_R5G6B5,
D3DFMT_DXT1,
D3DFMT_DXT3,
D3DFMT_DXT5,
D3DFMT_A32B32G32R32F,
D3DFMT_A16B16G16R16,
(D3DFORMAT)-1
};
D3DFORMAT g_fboColorTexFormatsGLMT[] = // -1 terminated
{
D3DFMT_A8R8G8B8,
//D3DFMT_A4R4G4B4, //unsupported
D3DFMT_X8R8G8B8,
D3DFMT_X1R5G5B5,
//D3DFMT_A1R5G5B5, //unsupported
D3DFMT_A16B16G16R16F,
D3DFMT_A32B32G32R32F,
D3DFMT_R5G6B5,
(D3DFORMAT)-1
};
D3DFORMAT g_fboDepthTexFormatsGLMT[] = // -1 terminated, but note 0 for "no depth" mode
{
(D3DFORMAT)0,
D3DFMT_D16,
D3DFMT_D24X8,
D3DFMT_D24S8,
(D3DFORMAT)-1
};
// #####################################################################################################################
void GLMTester::Test0( void )
{
// make and delete a bunch of textures.
// lock and unlock them.
// use various combos of -
// √texel format
// √2D | 3D | cube map
// √mipped / not
// √POT / NPOT
// large / small / square / rect
// square / rect
GLMContext *ctx = m_params.m_ctx;
ctx->MakeCurrent();
CUtlVector< CGLMTex* > testTextures; // will hold all the built textures
// test stage loop
// 0 is creation
// 1 is lock/unlock
// 2 is deletion
for( int teststage = 0; teststage < 3; teststage++)
{
int innerindex = 0; // increment at stage switch
// format loop
for( D3DFORMAT *fmtPtr = g_drawTexFormatsGLMT; *fmtPtr != ((D3DFORMAT)-1); fmtPtr++ )
{
// form loop
GLenum forms[] = { GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP, (GLenum)-1 };
for( GLenum *formPtr = forms; *formPtr != ((GLenum)-1); formPtr++ )
{
// mip loop
for( int mipped = 0; mipped < 2; mipped++ )
{
// large / square / pot loop
// &4 == large &2 == square &1 == POT
// NOTE you *have to be square* for cube maps.
for( int aspect = 0; aspect < 8; aspect++ )
{
switch( teststage )
{
case 0:
{
GLMTexLayoutKey key;
memset( &key, 0, sizeof(key) );
key.m_texGLTarget = *formPtr;
key.m_texFormat = *fmtPtr;
if (mipped)
key.m_texFlags |= kGLMTexMipped;
// assume big, square, POT, and 3D, then adjust as needed
key.m_xSize = key.m_ySize = key.m_zSize = 256;
if ( !(aspect&4) ) // big or little ?
{
// little
key.m_xSize >>= 2;
key.m_ySize >>= 2;
key.m_zSize >>= 2;
}
if ( key.m_texGLTarget != GL_TEXTURE_CUBE_MAP )
{
if ( !(aspect & 2) ) // square or rect?
{
// rect
key.m_ySize >>= 1;
key.m_zSize >>= 2;
}
}
if ( !(aspect&1) ) // POT or NPOT?
{
// NPOT
key.m_xSize += 56;
key.m_ySize += 56;
key.m_zSize += 56;
}
// 2D, 3D, cube map ?
if (key.m_texGLTarget!=GL_TEXTURE_3D)
{
// 2D or cube map: flatten Z extent to one texel
key.m_zSize = 1;
}
else
{
// 3D: knock down Z quite a bit so our test case does not run out of RAM
key.m_zSize >>= 3;
if (!key.m_zSize)
{
key.m_zSize = 1;
}
}
CGLMTex *newtex = ctx->NewTex( &key );
CheckGLError( "tex create test");
InternalError( newtex==NULL, "tex create test" );
testTextures.AddToTail( newtex );
printf("\n[%5d] created tex %s",innerindex,newtex->m_layout->m_layoutSummary );
}
break;
case 1:
{
CGLMTex *ptex = testTextures[innerindex];
for( int face=0; face <ptex->m_layout->m_faceCount; face++)
{
for( int mip=0; mip <ptex->m_layout->m_mipCount; mip++)
{
GLMTexLockParams lockreq;
lockreq.m_tex = ptex;
lockreq.m_face = face;
lockreq.m_mip = mip;
GLMTexLayoutSlice *slice = &ptex->m_layout->m_slices[ ptex->CalcSliceIndex( face, mip ) ];
lockreq.m_region.xmin = lockreq.m_region.ymin = lockreq.m_region.zmin = 0;
lockreq.m_region.xmax = slice->m_xSize;
lockreq.m_region.ymax = slice->m_ySize;
lockreq.m_region.zmax = slice->m_zSize;
char *lockAddress;
int yStride;
int zStride;
ptex->Lock( &lockreq, &lockAddress, &yStride, &zStride );
CheckGLError( "tex lock test");
InternalError( lockAddress==NULL, "null lock address");
// write some texels of this flavor:
// red 75% green 40% blue 15% alpha 80%
GLMGenTexelParams gtp;
gtp.m_format = ptex->m_layout->m_format->m_d3dFormat;
gtp.m_dest = lockAddress;
gtp.m_chunkCount = (slice->m_xSize * slice->m_ySize * slice->m_zSize) / (ptex->m_layout->m_format->m_chunkSize * ptex->m_layout->m_format->m_chunkSize);
gtp.m_byteCountLimit = slice->m_storageSize;
gtp.r = 0.75;
gtp.g = 0.40;
gtp.b = 0.15;
gtp.a = 0.80;
GLMGenTexels( &gtp );
InternalError( gtp.m_bytesWritten != gtp.m_byteCountLimit, "byte count mismatch from GLMGenTexels" );
}
}
for( int face=0; face <ptex->m_layout->m_faceCount; face++)
{
for( int mip=0; mip <ptex->m_layout->m_mipCount; mip++)
{
GLMTexLockParams unlockreq;
unlockreq.m_tex = ptex;
unlockreq.m_face = face;
unlockreq.m_mip = mip;
// region need not matter for unlocks
unlockreq.m_region.xmin = unlockreq.m_region.ymin = unlockreq.m_region.zmin = 0;
unlockreq.m_region.xmax = unlockreq.m_region.ymax = unlockreq.m_region.zmax = 0;
//char *lockAddress;
//int yStride;
//int zStride;
ptex->Unlock( &unlockreq );
CheckGLError( "tex unlock test");
}
}
printf("\n[%5d] locked/wrote/unlocked tex %s",innerindex, ptex->m_layout->m_layoutSummary );
}
break;
case 2:
{
CGLMTex *dtex = testTextures[innerindex];
printf("\n[%5d] deleting tex %s",innerindex, dtex->m_layout->m_layoutSummary );
ctx->DelTex( dtex );
CheckGLError( "tex delete test");
}
break;
} // end stage switch
innerindex++;
} // end aspect loop
} // end mip loop
} // end form loop
} // end format loop
} // end stage loop
}
// #####################################################################################################################
void GLMTester::Test1( void )
{
// FBO exercises
GLMContext *ctx = m_params.m_ctx;
ctx->MakeCurrent();
// FBO color format loop
for( D3DFORMAT *colorFmtPtr = g_fboColorTexFormatsGLMT; *colorFmtPtr != ((D3DFORMAT)-1); colorFmtPtr++ )
{
// FBO depth format loop
for( D3DFORMAT *depthFmtPtr = g_fboDepthTexFormatsGLMT; *depthFmtPtr != ((D3DFORMAT)-1); depthFmtPtr++ )
{
// mip loop
for( int mipped = 0; mipped < 2; mipped++ )
{
GLenum forms[] = { GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP, (GLenum)-1 };
// form loop
for( GLenum *formPtr = forms; *formPtr != ((GLenum)-1); formPtr++ )
{
//=============================================== make an FBO
CGLMFBO *fbo = ctx->NewFBO();
//=============================================== make a color texture
GLMTexLayoutKey colorkey;
memset( &colorkey, 0, sizeof(colorkey) );
switch(*formPtr)
{
case GL_TEXTURE_2D:
colorkey.m_texGLTarget = GL_TEXTURE_2D;
colorkey.m_xSize = 800;
colorkey.m_ySize = 600;
colorkey.m_zSize = 1;
break;
case GL_TEXTURE_3D:
colorkey.m_texGLTarget = GL_TEXTURE_3D;
colorkey.m_xSize = 800;
colorkey.m_ySize = 600;
colorkey.m_zSize = 32;
break;
case GL_TEXTURE_CUBE_MAP:
colorkey.m_texGLTarget = GL_TEXTURE_CUBE_MAP;
colorkey.m_xSize = 800;
colorkey.m_ySize = 800; // heh, cube maps have to have square sides...
colorkey.m_zSize = 1;
break;
}
colorkey.m_texFormat = *colorFmtPtr;
colorkey.m_texFlags = kGLMTexRenderable;
// decide if we want mips
if (mipped)
{
colorkey.m_texFlags |= kGLMTexMipped;
}
CGLMTex *colorTex = ctx->NewTex( &colorkey );
// Note that GLM will notice the renderable flag, and force texels to be written
// so the FBO will be complete
//=============================================== attach color
GLMFBOTexAttachParams colorParams;
memset( &colorParams, 0, sizeof(colorParams) );
colorParams.m_tex = colorTex;
colorParams.m_face = (colorkey.m_texGLTarget == GL_TEXTURE_CUBE_MAP) ? 2 : 0; // just steer to an alternate face as a test
colorParams.m_mip = (colorkey.m_texFlags & kGLMTexMipped) ? 2 : 0; // pick non-base mip slice
colorParams.m_zslice= (colorkey.m_texGLTarget == GL_TEXTURE_3D) ? 3 : 0; // just steer to an alternate slice as a test;
fbo->TexAttach( &colorParams, kAttColor0 );
//=============================================== optional depth tex
CGLMTex *depthTex = NULL;
if (*depthFmtPtr > 0 )
{
GLMTexLayoutKey depthkey;
memset( &depthkey, 0, sizeof(depthkey) );
depthkey.m_texGLTarget = GL_TEXTURE_2D;
depthkey.m_xSize = colorkey.m_xSize >> colorParams.m_mip; // scale depth tex to match color tex
depthkey.m_ySize = colorkey.m_ySize >> colorParams.m_mip;
depthkey.m_zSize = 1;
depthkey.m_texFormat = *depthFmtPtr;
depthkey.m_texFlags = kGLMTexRenderable | kGLMTexIsDepth; // no mips.
if (depthkey.m_texFormat==D3DFMT_D24S8)
{
depthkey.m_texFlags |= kGLMTexIsStencil;
}
depthTex = ctx->NewTex( &depthkey );
//=============================================== attach depth
GLMFBOTexAttachParams depthParams;
memset( &depthParams, 0, sizeof(depthParams) );
depthParams.m_tex = depthTex;
depthParams.m_face = 0;
depthParams.m_mip = 0;
depthParams.m_zslice= 0;
EGLMFBOAttachment depthAttachIndex = (depthkey.m_texFlags & kGLMTexIsStencil) ? kAttDepthStencil : kAttDepth;
fbo->TexAttach( &depthParams, depthAttachIndex );
}
printf("\n FBO:\n color tex %s\n depth tex %s",
colorTex->m_layout->m_layoutSummary,
depthTex ? depthTex->m_layout->m_layoutSummary : "none"
);
// see if FBO is happy
bool ready = fbo->IsReady();
printf("\n -> %s\n", ready ? "pass" : "fail" );
// unbind
ctx->BindFBOToCtx( NULL, GL_FRAMEBUFFER );
// del FBO
ctx->DelFBO(fbo);
// del texes
ctx->DelTex( colorTex );
if (depthTex) ctx->DelTex( depthTex );
} // end form loop
} // end mip loop
} // end depth loop
} // end color loop
}
// #####################################################################################################################
static int selftest2_seed = 0; // inc this every run to force main thread to teardown/reset display view
void GLMTester::Test2( void )
{
GLMContext *ctx = m_params.m_ctx;
ctx->MakeCurrent();
StdSetup(); // default test case drawing setup
// draw stuff (loop...)
for( int i=0; i<m_params.m_frameCount; i++)
{
// ramping shades of blue...
GLfloat clear_color[4] = { 0.50f, 0.05f, ((float)(i%100)) / 100.0f, 1.0f };
gGL->glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);
CheckGLError("test2 clear color");
gGL->glClear(GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT+GL_STENCIL_BUFFER_BIT);
CheckGLError("test2 clearing");
// try out debug text
for( int j=0; j<16; j++)
{
char text[256];
sprintf(text, "The quick brown fox jumped over the lazy dog %d times", i );
float theta = ( (i*0.10f) + (j * 6.28f) ) / 16.0f;
float posx = cos(theta) * 0.5;
float posy = sin(theta) * 0.5;
float charwidth = 6.0 * (2.0 / 1024.0);
float charheight = 11.0 * (2.0 / 768.0);
ctx->DrawDebugText( posx, posy, 0.0f, charwidth, charheight, text );
}
gGL->glFinish();
CheckGLError("test2 finish");
Present( selftest2_seed );
}
StdCleanup();
selftest2_seed++;
}
// #####################################################################################################################
static char g_testVertexProgram01 [] =
{
"!!ARBvp1.0 \n"
"TEMP vertexClip; \n"
"DP4 vertexClip.x, state.matrix.mvp.row[0], vertex.position; \n"
"DP4 vertexClip.y, state.matrix.mvp.row[1], vertex.position; \n"
"DP4 vertexClip.z, state.matrix.mvp.row[2], vertex.position; \n"
"DP4 vertexClip.w, state.matrix.mvp.row[3], vertex.position; \n"
"ADD vertexClip.y, vertexClip.x, vertexClip.y; \n"
"MOV result.position, vertexClip; \n"
"MOV result.color, vertex.color; \n"
"MOV result.texcoord[0], vertex.texcoord; \n"
"END \n"
};
static char g_testFragmentProgram01 [] =
{
"!!ARBfp1.0 \n"
"TEMP color; \n"
"MUL color, fragment.texcoord[0].y, 2.0; \n"
"ADD color, 1.0, -color; \n"
"ABS color, color; \n"
"ADD result.color, 1.0, -color; \n"
"MOV result.color.a, 1.0; \n"
"END \n"
};
// generic attrib versions..
static char g_testVertexProgram01_GA [] =
{
"!!ARBvp1.0 \n"
"TEMP vertexClip; \n"
"DP4 vertexClip.x, state.matrix.mvp.row[0], vertex.attrib[0]; \n"
"DP4 vertexClip.y, state.matrix.mvp.row[1], vertex.attrib[0]; \n"
"DP4 vertexClip.z, state.matrix.mvp.row[2], vertex.attrib[0]; \n"
"DP4 vertexClip.w, state.matrix.mvp.row[3], vertex.attrib[0]; \n"
"ADD vertexClip.y, vertexClip.x, vertexClip.y; \n"
"MOV result.position, vertexClip; \n"
"MOV result.color, vertex.attrib[3]; \n"
"MOV result.texcoord[0], vertex.attrib[8]; \n"
"END \n"
};
static char g_testFragmentProgram01_GA [] =
{
"!!ARBfp1.0 \n"
"TEMP color; \n"
"TEX color, fragment.texcoord[0], texture[0], 2D;"
//"MUL color, fragment.texcoord[0].y, 2.0; \n"
//"ADD color, 1.0, -color; \n"
//"ABS color, color; \n"
//"ADD result.color, 1.0, -color; \n"
//"MOV result.color.a, 1.0; \n"
"MOV result.color, color; \n"
"END \n"
};
void GLMTester::Test3( void )
{
/**************************
XXXXXXXXXXXXXXXXXXXXXX stale test code until we revise the program interface
GLMContext *ctx = m_params.m_ctx;
ctx->MakeCurrent();
StdSetup(); // default test case drawing setup
// make vertex&pixel shader
CGLMProgram *vprog = ctx->NewProgram( kGLMVertexProgram, g_testVertexProgram01_GA );
ctx->BindProgramToCtx( kGLMVertexProgram, vprog );
CGLMProgram *fprog = ctx->NewProgram( kGLMFragmentProgram, g_testFragmentProgram01_GA );
ctx->BindProgramToCtx( kGLMFragmentProgram, fprog );
// draw stuff (loop...)
for( int i=0; i<m_params.m_frameCount; i++)
{
// ramping shades of blue...
GLfloat clear_color[4] = { 0.50f, 0.05f, ((float)(i%100)) / 100.0, 1.0f };
glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);
CheckGLError("test3 clear color");
glClear(GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT+GL_STENCIL_BUFFER_BIT);
CheckGLError("test3 clearing");
// try out debug text
for( int j=0; j<16; j++)
{
char text[256];
sprintf(text, "This here is running through a trivial vertex shader");
float theta = ( (i*0.10f) + (j * 6.28f) ) / 16.0f;
float posx = cos(theta) * 0.5;
float posy = sin(theta) * 0.5;
float charwidth = 6.0 * (2.0 / 800.0);
float charheight = 11.0 * (2.0 / 640.0);
ctx->DrawDebugText( posx, posy, 0.0f, charwidth, charheight, text );
}
glFinish();
CheckGLError("test3 finish");
Present( 3333 );
}
StdCleanup();
*****************************/
}
#if GLMDEBUG
void GLMTriggerDebuggerBreak()
{
// we call an obscure GL function which we know has been breakpointed in the OGLP function list
// What the fuck is that?
// static signed short nada[] = { -1,-1,-1,-1 };
// gGL->glColor4sv( nada );
}
#endif