You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5927 lines
162 KiB
5927 lines
162 KiB
//========= 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 ) |
|
{ |
|
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 %08x", (uint)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 int 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_INT, 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_bUseSamplerObjects = true; |
|
// |
|
// // On most AMD drivers (like the current latest, 12.10 Windows), the PCF depth comparison mode doesn't work on sampler objects, so just punt them. |
|
// if ( gGL->m_nDriverProvider == cGLDriverProviderAMD ) |
|
// { |
|
// m_bUseSamplerObjects = false; |
|
// } |
|
|
|
// if ( CommandLine()->CheckParm( "-gl_disablesamplerobjects" ) ) |
|
// { |
|
// Disable sampler object usage for now since ScaleForm isn't aware of them |
|
// and doesn't know how to push/pop their binding state. It seems we don't |
|
// really use them in this codebase anyhow, except to preload textures. |
|
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 ¶ms = 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 |
|
unsigned long *destTexelPtr = (unsigned long *)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( ¶ms, 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( ¶ms ); |
|
|
|
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; |
|
} |
|
|
|
#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*)( (int)indicesActual + (int)pIndexBuf->m_pPseudoBuf ); |
|
} |
|
if (pIndexBuf->m_bUsingPersistentBuffer) |
|
{ |
|
indicesActual = (void*)( (int)indicesActual + (int)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( >p ); |
|
|
|
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
|
|
|