//============ Copyright (c) Valve Corporation, All rights reserved. ============ // // glmgr.cpp // //=============================================================================== #include "togl/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 #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" ); ConVar gl_swap_limit( "gl_swap_limit", "1", FCVAR_RELEASE ); //=============================================================================== // 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 char g_nullFragmentProgramText [] = { "!!ARBfp1.0 \n" "PARAM black = { 0.0, 0.0, 0.0, 1.0 }; \n" // opaque black "MOV result.color, black; \n" "END \n\n\n" "//GLSLfp\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[] = { "//GLSLvp \n" "#version 120 \n" "varying vec4 otex; \n" "void main() \n" "{ \n" "vec4 pos = ftransform(); // 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[] = { "//GLSLfp \n" "#version 120 \n" "varying 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 = texture2D( sampler15, otex.xy ); \n" "gl_FragColor = r0; //discard; \n" "} \n" }; char g_preload3DTexFragmentProgramText[] = { "//GLSLfp \n" "#version 120 \n" "varying vec4 otex; \n" "//SAMPLERMASK-8000 // may not be needed \n" "//HIGHWATER-30 // may not be needed \n" " \n" "uniform vec4 pc[31]; \n" "uniform sampler3D sampler15; \n" " \n" "void main() \n" "{ \n" "vec4 r0; \n" "r0 = texture3D( sampler15, otex.xyz ); \n" "gl_FragColor = r0; //discard; \n" "} \n" }; char g_preloadCubeTexFragmentProgramText[] = { "//GLSLfp \n" "#version 120 \n" "varying vec4 otex; \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 = textureCube( 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, ... ) { va_list args; va_start( args, pMsg ); char buf[1024]; V_vsnprintf( buf, sizeof( buf ), pMsg, args ); va_end( args ); Plat_DebugString( buf ); } void CheckGLFBOStatusTMP( const char *comment ) { return; char errbuf[1024]; if (!comment) { comment = ""; } GLenum errorcode2 = 0; GLenum errorcode3 = 0; const char *decodedStr2 = ""; const char *decodedStr3 = ""; // dig up the more detailed FBO status errorcode2 = gGL->glCheckFramebufferStatusEXT( GL_READ_FRAMEBUFFER_EXT ); decodedStr2 = GLMDecode( eGL_ERROR, errorcode2 ); errorcode3 = gGL->glCheckFramebufferStatusEXT( GL_DRAW_FRAMEBUFFER_EXT ); decodedStr3 = GLMDecode( eGL_ERROR, errorcode3 ); sprintf( errbuf, "\n%s - GL FBO Status %08x/%08x = '%s / %s'\n", comment, errorcode2, errorcode3, decodedStr2, decodedStr3 ); printf("%s", errbuf ); } void CheckGLErrorTMP( const char *comment ) { return; char errbuf[1024]; //borrowed from GLMCheckError.. slightly different if (!comment) { comment = ""; } GLenum errorcode = (GLenum)gGL->glGetError(); GLenum errorcode2 = 0; GLenum errorcode3 = 0; if ( errorcode != GL_NO_ERROR ) { const char *decodedStr = GLMDecode( eGL_ERROR, errorcode ); const char *decodedStr2 = ""; const char *decodedStr3 = ""; if ( errorcode == GL_INVALID_FRAMEBUFFER_OPERATION_EXT ) { // dig up the more detailed FBO status errorcode2 = gGL->glCheckFramebufferStatusEXT( GL_READ_FRAMEBUFFER_EXT ); decodedStr2 = GLMDecode( eGL_ERROR, errorcode2 ); errorcode3 = gGL->glCheckFramebufferStatusEXT( GL_DRAW_FRAMEBUFFER_EXT ); decodedStr3 = GLMDecode( eGL_ERROR, errorcode3 ); sprintf( errbuf, "\n%s - GL Error %08x/%08x/%08x = '%s / %s / %s'\n", comment, errorcode, errorcode2, errorcode3, decodedStr, decodedStr2, decodedStr3 ); } else { sprintf( errbuf, "\n%s - GL Error %08x = '%s'\n", comment, errorcode, decodedStr ); } printf("%s", errbuf ); // DebuggerBreak(); } } //=============================================================================== // 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 ); } #ifdef OSX inline PseudoNSGLContextPtr GetNSGLContextForWindow( void* windowref ) { return g_pLauncherMgr->GetGLContextForWindow( windowref ); } #endif 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 [ %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; nm_nCurOwnerThreadId = ThreadGetCurrentId(); if ( !MakeContextCurrent( context->m_ctx ) ) { // give up GLMStop(); } // Why is there an Assert( 0 ) here? Seems like it's for once we're in game, but we should // hit this on startup so wat? Assert( 0 ); #elif defined( OSX ) context->m_nCurOwnerThreadId = ThreadGetCurrentId(); CGLError cgl_err; cgl_err = CGLSetCurrentContext( context->m_ctx ); if (cgl_err) { // give up GLMStop(); } #endif } GLMContext *GLMgr::GetCurrentContext( void ) { // !!! FIXME: why isn't this abstracted in appframework? #if defined( USE_SDL ) PseudoGLContextPtr context = GetMainContext(); return (GLMContext*) context; #elif defined( OSX ) CGLContextObj ctx = CGLGetCurrentContext(); // Docs say this is always a pointer-sized parameter, even though the API takes an int* intp glm_context_link = 0; CGLGetParameter( ctx, kCGLCPClientStorage, (int*) &glm_context_link ); if ( glm_context_link ) { return (GLMContext*) glm_context_link; } else { return NULL; } #else Assert( 0 ); return NULL; #endif } // #define CHECK_THREAD_USAGE 1 //=============================================================================== // GLMContext public methods void GLMContext::MakeCurrent( bool bRenderThread ) { TM_ZONE( TELEMETRY_LEVEL0, 0, "GLMContext::MakeCurrent" ); Assert( m_nCurOwnerThreadId == 0 || m_nCurOwnerThreadId == ThreadGetCurrentId() ); // !!! FIXME: why isn't this abstracted in appframework? // GLM_FUNC; #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 #elif defined( OSX ) m_nCurOwnerThreadId = ThreadGetCurrentId(); CGLSetCurrentContext( m_ctx ); #else Assert( 0 ); #endif } void GLMContext::ReleaseCurrent( bool bRenderThread ) { TM_ZONE( 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 #elif defined( OSX ) m_nCurOwnerThreadId = 0; CGLSetCurrentContext( NULL ); #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_EXT ); BindFBOToCtx( m_boundDrawFBO, GL_DRAW_FRAMEBUFFER_EXT ); // Current VB/IB/pinned memory buffers gGL->glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_nBoundGLBuffer[ kGLMIndexBuffer] ); gGL->glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nBoundGLBuffer[ kGLMVertexBuffer] ); #ifndef OSX if ( gGL->m_bHave_GL_AMD_pinned_memory ) { gGL->glBindBufferARB( GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, m_PinnedMemoryBuffers[m_nCurPinnedMemoryBuffer].GetHandle() ); } #endif } 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 %p", 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_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_DEPTH_STENCIL_ATTACHMENT_EXT }; void glScrubFBO ( GLenum target ) { gGL->glFramebufferRenderbufferEXT ( target, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, 0); gGL->glFramebufferRenderbufferEXT ( target, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0); gGL->glFramebufferRenderbufferEXT ( target, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0); gGL->glFramebufferTexture2DEXT ( target, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0 ); gGL->glFramebufferTexture2DEXT ( target, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0 ); gGL->glFramebufferTexture2DEXT ( target, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0 ); } void glAttachRBOtoFBO ( GLenum target, eBlitFormatClass formatClass, uint rboName ) { switch( formatClass ) { case eColor: gGL->glFramebufferRenderbufferEXT ( target, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, rboName); break; case eDepth: gGL->glFramebufferRenderbufferEXT ( target, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, rboName); break; case eDepthStencil: gGL->glFramebufferRenderbufferEXT ( target, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, rboName); gGL->glFramebufferRenderbufferEXT ( target, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, rboName); break; } } void glAttachTex2DtoFBO ( GLenum target, eBlitFormatClass formatClass, uint texName, uint texMip ) { switch( formatClass ) { case eColor: gGL->glFramebufferTexture2DEXT ( target, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texName, texMip ); break; case eDepth: gGL->glFramebufferTexture2DEXT ( target, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texName, texMip ); break; case eDepthStencil: gGL->glFramebufferTexture2DEXT ( target, GL_DEPTH_STENCIL_ATTACHMENT_EXT, 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_EXT: // only support depth only blits for now formatClass = eDepth; blitMask = GL_DEPTH_BUFFER_BIT; //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); bool doPushPop = (srcGamma != dstGamma) && gl_radar7954721_workaround_mixed.GetInt() && m_caps.m_nv; // workaround for cross gamma blit problems on NV // ^^ need to re-check this on some post-10.6.3 build on NV to see if it was fixed if (doPushPop) { gGL->glPushAttrib( 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_EXT ); glScrubFBO ( GL_READ_FRAMEBUFFER_EXT ); glAttachRBOtoFBO ( GL_READ_FRAMEBUFFER_EXT, formatClass, srcTex->m_rboName ); // bind scratch FBO1 to write, scrub it, attach scratch tex BindFBOToCtx ( m_scratchFBO[1], GL_DRAW_FRAMEBUFFER_EXT ); glScrubFBO ( GL_DRAW_FRAMEBUFFER_EXT ); glAttachTex2DtoFBO ( GL_DRAW_FRAMEBUFFER_EXT, formatClass, srcTex->m_texName, 0 ); // set read and draw buffers appropriately gGL->glReadBuffer ( glAttachFromClass[formatClass] ); gGL->glDrawBuffer ( glAttachFromClass[formatClass] ); // blit#1 - to resolve to scratch // implicitly means no scaling, thus will be done with NEAREST sampling GLenum resolveFilter = GL_NEAREST; gGL->glBlitFramebufferEXT( 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_EXT ); // zap FBO0 BindFBOToCtx ( m_scratchFBO[1], GL_READ_FRAMEBUFFER_EXT ); srcTex->ForceRBONonDirty(); } else { #if 1 if ( ( blitMask == GL_DEPTH_BUFFER_BIT ) && ( srcTex->m_pBlitSrcFBO != NULL ) && ( dstTex->m_pBlitDstFBO != NULL ) ) { // ensure fbo completeness for both src and dst buffers // on OSX need to call glReadBuffer and glDrawBuffer with GL_NONE for both in order to satify fb completeness (don't need this on Linux) // correct bindings applied below BindFBOToCtx( srcTex->m_pBlitSrcFBO, GL_DRAW_FRAMEBUFFER_EXT ); gGL->glDrawBuffer( GL_NONE ); BindFBOToCtx( dstTex->m_pBlitDstFBO, GL_READ_FRAMEBUFFER_EXT ); gGL->glReadBuffer( GL_NONE ); } if (srcTex->m_pBlitSrcFBO == NULL) { srcTex->m_pBlitSrcFBO = NewFBO(); BindFBOToCtx( srcTex->m_pBlitSrcFBO, GL_READ_FRAMEBUFFER_EXT ); if (blitResolves) { glAttachRBOtoFBO( GL_READ_FRAMEBUFFER_EXT, formatClass, srcTex->m_rboName ); } else { glAttachTex2DtoFBO( GL_READ_FRAMEBUFFER_EXT, formatClass, srcTex->m_texName, srcMip ); } } else { BindFBOToCtx ( srcTex->m_pBlitSrcFBO, GL_READ_FRAMEBUFFER_EXT ); } #else // arrange source surface on FBO1 for blit directly to dest (which could be FBO0 or BACK) BindFBOToCtx( m_scratchFBO[1], GL_READ_FRAMEBUFFER_EXT ); glScrubFBO( GL_READ_FRAMEBUFFER_EXT ); GLMCheckError(); if (blitResolves) { glAttachRBOtoFBO( GL_READ_FRAMEBUFFER_EXT, formatClass, srcTex->m_rboName ); } else { glAttachTex2DtoFBO( GL_READ_FRAMEBUFFER_EXT, formatClass, srcTex->m_texName, srcMip ); } #endif if ( blitMask != GL_DEPTH_BUFFER_BIT ) { gGL->glReadBuffer( glAttachFromClass[formatClass] ); } else { gGL->glReadBuffer( GL_NONE ); } } //----------------------------------------------------------------- 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_EXT ); gGL->glDrawBuffer ( GL_BACK ); 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_EXT ); if (dstTex->m_rboName) { glAttachRBOtoFBO( GL_DRAW_FRAMEBUFFER_EXT, formatClass, dstTex->m_rboName ); } else { glAttachTex2DtoFBO( GL_DRAW_FRAMEBUFFER_EXT, formatClass, dstTex->m_texName, dstMip ); } } else { BindFBOToCtx( dstTex->m_pBlitDstFBO, GL_DRAW_FRAMEBUFFER_EXT ); if ( blitMask == GL_DEPTH_BUFFER_BIT ) { gGL->glDrawBuffer( GL_NONE ); } } #else BindFBOToCtx( m_scratchFBO[0], GL_DRAW_FRAMEBUFFER_EXT ); GLMCheckError(); glScrubFBO( GL_DRAW_FRAMEBUFFER_EXT ); if (dstTex->m_rboName) { glAttachRBOtoFBO( GL_DRAW_FRAMEBUFFER_EXT, formatClass, dstTex->m_rboName ); } else { glAttachTex2DtoFBO( GL_DRAW_FRAMEBUFFER_EXT, 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->glBlitFramebufferEXT( 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->glBlitFramebufferEXT( 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_EXT ); BindFBOToCtx ( NULL, GL_READ_FRAMEBUFFER_EXT ); if (!blitToBack) { // glScrubFBO ( GL_DRAW_FRAMEBUFFER_EXT ); BindFBOToCtx ( NULL, GL_DRAW_FRAMEBUFFER_EXT ); } //----------------------------------------------------------------- restore GLM's drawing FBO // restore GLM drawing FBO BindFBOToCtx( m_drawingFBO, GL_FRAMEBUFFER_EXT ); if (doPushPop) { gGL->glPopAttrib( ); } //----------------------------------------------------------------- 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 )); } int pushed = 0; uint pushmask = gl_radar7954721_workaround_maskval.GetInt(); //GL_COLOR_BUFFER_BIT //| GL_CURRENT_BIT //| GL_ENABLE_BIT //| GL_FOG_BIT //| GL_PIXEL_MODE_BIT //| GL_SCISSOR_BIT //| GL_STENCIL_BUFFER_BIT //| GL_TEXTURE_BIT //GL_VIEWPORT_BIT //; if (gl_radar7954721_workaround_all.GetInt()!=0) { gGL->glPushAttrib( pushmask ); pushed++; } else { bool srcGamma = (srcTex->m_layout->m_key.m_texFlags & kGLMTexSRGB) != 0; bool dstGamma = (dstTex->m_layout->m_key.m_texFlags & kGLMTexSRGB) != 0; if (srcGamma != dstGamma) { if (gl_radar7954721_workaround_mixed.GetInt()) { gGL->glPushAttrib( pushmask ); pushed++; } } } 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_EXT; blitMask = GL_COLOR_BUFFER_BIT; break; case GL_DEPTH_COMPONENT: attachIndex = kAttDepth; attachIndexGL = GL_DEPTH_ATTACHMENT_EXT; blitMask = GL_DEPTH_BUFFER_BIT; break; case GL_DEPTH_STENCIL_EXT: attachIndex = kAttDepthStencil; attachIndexGL = GL_DEPTH_STENCIL_ATTACHMENT_EXT; 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_EXT ); 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_EXT ); gGL->glReadBuffer( attachIndexGL ); // set the write fb and buffer, and attach write tex BindFBOToCtx( m_blitDrawFBO, GL_DRAW_FRAMEBUFFER_EXT ); attparams.m_tex = dstTex; attparams.m_face = dstFace; attparams.m_mip = dstMip; attparams.m_zslice = 0; m_blitDrawFBO->TexAttach( &attparams, attachIndex, GL_DRAW_FRAMEBUFFER_EXT ); gGL->glDrawBuffer( attachIndexGL ); // do the blit gGL->glBlitFramebufferEXT( 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_EXT ); m_blitDrawFBO->TexDetach( attachIndex, GL_DRAW_FRAMEBUFFER_EXT ); // 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_EXT ); // 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_EXT; break; default: Assert(!"Can't blit that format"); break; } BindFBOToCtx( m_blitDrawFBO, GL_DRAW_FRAMEBUFFER_EXT ); 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_EXT ); gGL->glDrawBuffer( 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 float topv = 1.0; float botv = 0.0; gGL->glBegin(GL_QUADS); gGL->glTexCoord2f ( 0.0, botv ); gGL->glVertex3f ( -1.0, -1.0, 0.0 ); gGL->glTexCoord2f ( 1.0, botv ); gGL->glVertex3f ( 1.0, -1.0, 0.0 ); gGL->glTexCoord2f ( 1.0, topv ); gGL->glVertex3f ( 1.0, 1.0, 0.0 ); gGL->glTexCoord2f ( 0.0, topv ); gGL->glVertex3f ( -1.0, 1.0, 0.0 ); gGL->glEnd(); 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_EXT ); // put the original FB back in place (both read and draw) BindFBOToCtx( m_drawingFBO, GL_FRAMEBUFFER_EXT ); } while(pushed) { gGL->glPopAttrib(); pushed--; } 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_EXT; blitMask = GL_COLOR_BUFFER_BIT; break; // case GL_DEPTH_COMPONENT: // attachIndex = kAttDepth; // attachIndexGL = GL_DEPTH_ATTACHMENT_EXT; // blitMask = GL_DEPTH_BUFFER_BIT; // break; case GL_DEPTH_STENCIL_EXT: attachIndex = kAttDepthStencil; attachIndexGL = GL_DEPTH_STENCIL_ATTACHMENT_EXT; 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_EXT ); // 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_EXT) { // you have to attach it both places... // http://www.opengl.org/wiki/GL_EXT_framebuffer_object // bind the RBO to the GL_RENDERBUFFER_EXT target - is this extraneous ? //glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, tex->m_rboName ); // attach the GL_RENDERBUFFER_EXT target to the depth and stencil attach points gGL->glFramebufferRenderbufferEXT( GL_READ_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, tex->m_rboName); gGL->glFramebufferRenderbufferEXT( GL_READ_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, tex->m_rboName); // no need to leave the RBO hanging on //glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, 0 ); } else { //glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, tex->m_rboName ); gGL->glFramebufferRenderbufferEXT( GL_READ_FRAMEBUFFER_EXT, attachIndexGL, GL_RENDERBUFFER_EXT, tex->m_rboName); //glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, 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_EXT ); // regular path - attaching a texture2d if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT_EXT) { gGL->glFramebufferTexture2DEXT( GL_DRAW_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, tex->m_texName, 0 ); gGL->glFramebufferTexture2DEXT( GL_DRAW_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, tex->m_texName, 0 ); } else { gGL->glFramebufferTexture2DEXT( GL_DRAW_FRAMEBUFFER_EXT, attachIndexGL, GL_TEXTURE_2D, tex->m_texName, 0 ); } gGL->glDrawBuffer( attachIndexGL ); //----------------------------------------------------------------------------------- // blit gGL->glBlitFramebufferEXT( 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_EXT, 0 ); if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT_EXT) { // detach the GL_RENDERBUFFER_EXT target from the depth and stencil attach points gGL->glFramebufferRenderbufferEXT( GL_READ_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0); gGL->glFramebufferRenderbufferEXT( GL_READ_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0); } else { gGL->glFramebufferRenderbufferEXT( GL_READ_FRAMEBUFFER_EXT, attachIndexGL, GL_RENDERBUFFER_EXT, 0); } //----------------------------------------------------------------------------------- // unset the write fb and buffer, detach write tex if (attachIndexGL==GL_DEPTH_STENCIL_ATTACHMENT_EXT) { gGL->glFramebufferTexture2DEXT( GL_DRAW_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0 ); gGL->glFramebufferTexture2DEXT( GL_DRAW_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0 ); } else { gGL->glFramebufferTexture2DEXT( GL_DRAW_FRAMEBUFFER_EXT, 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_EXT ); // 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 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, 3, 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_EXT ); m_boundReadFBO = NULL; } if (m_boundDrawFBO == fbo ) { BindFBOToCtx( NULL, GL_DRAW_FRAMEBUFFER_EXT ); 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 = { 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 = { 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 } #ifdef _OSX void GLMContext::UpdateSwapchainVariables( bool bForce ) { static ConVarRef r_frameratesmoothing( "r_frameratesmoothing" ); const bool cbUpdateFramerateSmoothing = bForce || r_frameratesmoothing.GetBool() != m_bFramerateSmoothing; const bool cbUpdateSwapLimit = bForce || gl_swap_limit.GetBool() != m_bSwapLimit; // Bail if no work to do. if ( !cbUpdateFramerateSmoothing && !cbUpdateSwapLimit ) return; #ifdef USE_SDL CGLContextObj context = GetCGLContextFromNSGL( m_ctx ); #else auto context = m_ctx; #endif if ( cbUpdateFramerateSmoothing ) { 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; } // Enable if we can and we haven't been told not to. bool mtgl_on = m_displayParams.m_mtgl && !r_frameratesmoothing.GetBool(); if ( CommandLine()->FindParm( "-glmenablemtgl" ) ) { mtgl_on = true; } if ( CommandLine()->FindParm( "-glmdisablemtgl" ) ) { mtgl_on = false; } CGLError result = ( CGLError ) 0; bool ready = false; if ( new_mtgl ) { // afterburner CGLContextEnable kCGLCPGCDMPEngine = ( ( CGLContextEnable ) 1314 ); result = mtgl_on ? CGLEnable( context, kCGLCPGCDMPEngine ) : CGLDisable( context, kCGLCPGCDMPEngine ); if ( !result ) { ready = true; // succeeded - no need to try non-MTGL printf( "\nMTGL2 %s.\n", mtgl_on ? "enabled" : "disabled" ); } else { printf( "\nMTGL2 *not* enabled, falling back.\n" ); } } if ( !ready ) { // try old MTGL result = mtgl_on ? CGLEnable( context, kCGLCEMPEngine ) : CGLDisable( context, kCGLCEMPEngine ); if ( !result ) { printf( "\nMTGL %s.\n", mtgl_on ? "enabled" : "disabled" ); ready = true; } } // Make sure we can detect edges on this properly. m_bFramerateSmoothing = r_frameratesmoothing.GetBool(); } if ( cbUpdateSwapLimit ) { if ( gl_swap_limit.GetBool() ) { // Limit the number of frames in flight to 1. CGLEnable( context, kCGLCESwapLimit ); printf( "Swap Limit Enabled\n" ); } else { CGLDisable( context, kCGLCESwapLimit ); printf( "Swap Limit Disabled\n" ); } // Make sure we can detect edges on this properly. m_bSwapLimit = gl_swap_limit.GetBool(); } } #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 { #endif if (!m_oneCtxEnable) // if using dual contexts, this flush is needed { gGL->glFlush(); } #if GLMDEBUG 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(); #ifdef HAVE_GL_ARB_SYNC if ( gGL->m_bHave_GL_AMD_pinned_memory ) { m_PinnedMemoryBuffers[m_nCurPinnedMemoryBuffer].InsertFence(); m_nCurPinnedMemoryBuffer = ( m_nCurPinnedMemoryBuffer + 1 ) % cNumPinnedMemoryBuffers; m_PinnedMemoryBuffers[m_nCurPinnedMemoryBuffer].BlockUntilNotBusy(); gGL->glBindBufferARB( GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, m_PinnedMemoryBuffers[m_nCurPinnedMemoryBuffer].GetHandle() ); } if ( gGL->m_bHave_GL_ARB_buffer_storage ) { for (uint lpType = 0; lpType < kGLMNumBufferTypes; ++lpType) { m_persistentBuffer[m_nCurPersistentBuffer][lpType].InsertFence(); } m_nCurPersistentBuffer = ( m_nCurPersistentBuffer + 1 ) % cNumPersistentBuffers; for (uint lpType = 0; lpType < kGLMNumBufferTypes; ++lpType) { m_persistentBuffer[m_nCurPersistentBuffer][lpType].BlockUntilNotBusy(); } } #endif 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 ( m_oneCtxEnable && (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(dstWidth)) || (showparams.m_height != static_cast(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; if (m_oneCtxEnable) // if using single context, we need to blast some state so GLM will recover after the FBO fiddlin' { BindFBOToCtx( NULL, GL_FRAMEBUFFER_EXT ); } } else { ResolveTex( tex, true ); // dxabstract used to do this unconditionally.we still do if new refresh mode doesn't engage. if (m_oneCtxEnable) // if using single context, we need to blast some state so GLM will recover after the FBO fiddlin' { BindFBOToCtx( NULL, GL_FRAMEBUFFER_EXT ); } else { gGL->glFlush(); // this call is needed for dual context mode to get pixels flushed } // showparams.m_noBlit is left set to 0. CocoaMgr does the blit. } ShowPixels(&showparams); } if (m_oneCtxEnable) { // 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_EXT ); // 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 #ifdef _OSX // Give ourselves a chance to update the swapchain parameters on OSX. UpdateSwapchainVariables( false ); #else GLMGPUTimestampManagerTick(); #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; } // this decides whether the engine will try to use fast context mode on 10.6.3 or later. // fast context mode means that a single GL context is used both for the window and the engine, saving on sync and flushes. // it's only a suggestion; if the OS is below 10.6.2 it will be ignored. ConVar gl_singlecontext ( "gl_singlecontext", "1" ); 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; } char buf[256]; V_snprintf( buf, sizeof( buf ), "GL sampler object usage: %s\n", m_bUseSamplerObjects ? "ENABLED" : "DISABLED" ); Plat_DebugString( buf ); m_nCurOwnerThreadId = ThreadGetCurrentId(); m_nThreadOwnershipReleaseCounter = 0; m_pDevice = pDevice; m_nCurFrame = 0; m_nBatchCounter = 0; ClearCurAttribs(); #ifndef OSX m_nCurPinnedMemoryBuffer = 0; if ( gGL->m_bHave_GL_AMD_pinned_memory ) { for ( uint t = 0; t < cNumPinnedMemoryBuffers; t++ ) { m_PinnedMemoryBuffers[t].Init( GLMGR_PINNED_MEMORY_BUFFER_SIZE ); } gGL->glBindBufferARB( GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, m_PinnedMemoryBuffers[m_nCurPinnedMemoryBuffer].GetHandle() ); } #endif // OSX m_nCurPersistentBuffer = 0; if ( gGL->m_bHave_GL_ARB_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; #ifdef OSX // call Cocoa manager, ask for the attrib list (also naming the specific renderer ID) and use that to make our context CGLPixelFormatAttribute *selAttribs = NULL; #elif defined(LINUX) int *selAttribs = NULL; #elif defined(_WIN32 ) int *selAttribs = NULL; #else #error #endif 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 ); m_oneCtxEnable = true; #elif defined( OSX ) // call Cocoa manager, ask it about the window we're targeting, get the NSGLContext back, share against that PseudoNSGLContextPtr shareNsCtx = GetNSGLContextForWindow( (void*)params->m_focusWindow ); // decide if we're going to try single context mode. m_oneCtxEnable = (m_caps.m_osComboVersion >= 0x000A0603) && (gl_singlecontext.GetInt() ); bool success = false; //NewNSGLContext( (unsigned long*)selAttribs, shareNsCtx, &m_nsctx, &m_ctx ); if(m_oneCtxEnable) { // just steal the window's context m_nsctx = shareNsCtx; m_ctx = GetCGLContextFromNSGL( shareNsCtx ); success = (m_nsctx != NULL) && (m_ctx != NULL); } else { success = NewNSGLContext( (unsigned long*)selAttribs, shareNsCtx, &m_nsctx, &m_ctx ); } if (success) { //write a cookie into the CGL context leading back to the GLM context object intp glm_context_link = (intp) this; CGLSetParameter( m_ctx, kCGLCPClientStorage, (int*) &glm_context_link ); // save off the pixel format attributes we used memcpy(m_pixelFormatAttribs, selAttribs, selBytes ); } else { DebuggerBreak(); //FIXME #PMB# bad news, maybe exit to shell if this happens } #else #error #endif IncrementWindowRefCount(); // If we're using GL_ARB_debug_output, go ahead and setup the callback here. if ( gGL->m_bHave_GL_ARB_debug_output && 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 #endif // This should be there if we get in here--make sure. Assert(gGL->glDebugMessageControlARB); gGL->glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, (const GLuint *)NULL, GL_TRUE); // Gonna filter these out, they're "chatty". gGL->glDebugMessageControlARB(GL_DEBUG_SOURCE_API_ARB, GL_DEBUG_TYPE_OTHER_ARB, GL_DEBUG_SEVERITY_LOW_ARB, 0, (const GLuint *)NULL, GL_FALSE); gGL->glDebugMessageCallbackARB(GL_Debug_Output_Callback, (void*)NULL); Plat_DebugString( "GLMContext::GLMContext: Debug output (gl_arb_debug_output) enabled!\n" ); } 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 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; iEnable() ) { 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 } void GLMContext::Reset() { } GLMContext::~GLMContext () { #ifndef OSX GLMGPUTimestampManagerDeinit(); for ( uint t = 0; t < cNumPinnedMemoryBuffers; t++ ) { m_PinnedMemoryBuffers[t].Deinit(); } if (gGL->m_bHave_GL_ARB_buffer_storage) { for (uint lpType = 0; lpType < kGLMNumBufferTypes; ++lpType) { for (uint lpNum = 0; lpNum < cNumPersistentBuffers; ++lpNum) { m_persistentBuffer[lpNum][lpType].Deinit(); } } } if ( m_bUseSamplerObjects ) { for( int i=0; i< GLM_SAMPLER_COUNT; i++) { gGL->glBindSampler( i, 0 ); } for( int i=0; i< cSamplerObjectHashSize; i++) { gGL->glDeleteSamplers( 1, &m_samplerObjectHash[i].m_samplerObject ); m_samplerObjectHash[i].m_samplerObject = 0; } } #endif 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 #ifdef OSX if (m_nsctx && (!m_oneCtxEnable) ) { DelNSGLContext( m_nsctx ); m_nsctx = NULL; m_ctx = NULL; } #endif 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_1D, 0 ); 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_1D ) gGL->glBindTexture( GL_TEXTURE_1D, 0 ); 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_EXT ) { gGL->glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, fbo ? fbo->m_name : 0 ); m_boundReadFBO = fbo; m_boundDrawFBO = fbo; return; } bool targetRead = (bindPoint==GL_READ_FRAMEBUFFER_EXT); bool targetDraw = (bindPoint==GL_DRAW_FRAMEBUFFER_EXT); if (targetRead) { if (fbo) // you can pass NULL to go back to no-FBO { gGL->glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, fbo->m_name ); m_boundReadFBO = fbo; //dontcare fbo->m_bound = true; } else { gGL->glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, 0 ); m_boundReadFBO = NULL; } } if (targetDraw) { if (fbo) // you can pass NULL to go back to no-FBO { gGL->glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, fbo->m_name ); m_boundDrawFBO = fbo; //dontcare fbo->m_bound = true; } else { gGL->glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, 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_ARB; break; case kGLMIndexBuffer: target = GL_ELEMENT_ARRAY_BUFFER_ARB; break; case kGLMUniformBuffer: target = GL_UNIFORM_BUFFER_EXT; break; case kGLMPixelBuffer: target = GL_PIXEL_UNPACK_BUFFER_ARB; break; default: Assert(!"Unknown buffer type" ); } Assert( !pBuff || ( pBuff->m_buffGLTarget == target ) ); m_nBoundGLBuffer[type] = nGLName; gGL->glBindBufferARB( target, nGLName ); } #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 ( (im_caller==eDrawElements); const char *batchtype = is_draw ? "draw" : "clear"; if (options & (1<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<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<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<m_string, eDebugDump)); } */ } else { GLMPRINTF(("-D- FP (none)" )); } } if ( (options & (1<m_attrMask & (1<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<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<m_attrMask; for( int index=0; index < kGLMVertexAttributeIndexMax; index++ ) { uint mask = 1<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<m_drawStart; int end = info->m_drawEnd; int endLimit = start + (1<=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<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<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<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<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<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<= 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<=0) && (flavorSelect 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: short fakecolor[4] = { 0, 0, 0, 0 }; gGL->glColor4sv( fakecolor ); // break to OGLP break; } // re-flush all GLM states so you can fiddle with them in the debugger. then run the batch again and spin.. ForceFlushStates(); } } void GLMContext::DebugPresent( void ) { CGLMTex *drawBufferTex = m_drawingFBO->m_attach[kAttColor0].m_tex; gGL->glFinish(); Present( drawBufferTex ); } void GLMContext::DebugClear( void ) { // get old clear color GLClearColor_t clearcol_orig; m_ClearColor.Read( &clearcol_orig,0 ); // new clear color GLClearColor_t clearcol; clearcol.r = m_autoClearColorValues[0]; clearcol.g = m_autoClearColorValues[1]; clearcol.b = m_autoClearColorValues[2]; clearcol.a = m_autoClearColorValues[3]; m_ClearColor.Write( &clearcol ); // don't check, don't defer uint mask = 0; if (m_autoClearColor) mask |= GL_COLOR_BUFFER_BIT; if (m_autoClearDepth) mask |= GL_DEPTH_BUFFER_BIT; if (m_autoClearStencil) mask |= GL_STENCIL_BUFFER_BIT; gGL->glClear( mask ); gGL->glFinish(); // put old color back m_ClearColor.Write( &clearcol_orig ); // don't check, don't defer } #endif void GLMContext::CheckNative( void ) { // note that this is available in release. We don't use GLMPRINTF for that reason. // note we do not get called unless either slow-batch asserting or logging is enabled. #ifdef OSX bool gpuProcessing; GLint fragmentGPUProcessing, vertexGPUProcessing; CGLGetParameter (CGLGetCurrentContext(), kCGLCPGPUFragmentProcessing, &fragmentGPUProcessing); CGLGetParameter(CGLGetCurrentContext(), kCGLCPGPUVertexProcessing, &vertexGPUProcessing); // spews then asserts. // that way you can enable both, get log output on a pair if it's slow, and then the debugger will pop. if(m_slowSpewEnable) { if ( !vertexGPUProcessing ) { m_drawingProgram[ kGLMVertexProgram ]->LogSlow( m_drawingLang ); } if ( !fragmentGPUProcessing ) { m_drawingProgram[ kGLMFragmentProgram ]->LogSlow( m_drawingLang ); } } if(m_slowAssertEnable) { if ( !vertexGPUProcessing || !fragmentGPUProcessing) { Assert( !"slow batch" ); } } #else //Assert( !"impl GLMContext::CheckNative()" ); if (m_checkglErrorsAfterEveryBatch) { // This is slow, and somewhat redundant (-gldebugoutput uses the GL_ARB_debug_output extension, which can be at least asynchronous), but having a straightforward backup can be useful. // This is useful for callstack purposes - GL_ARB_debug_output may break in a different thread that the thread triggering the GL error. //gGL->glFlush(); GLenum errorcode = (GLenum)gGL->glGetError(); if ( errorcode != GL_NO_ERROR ) { const char *decodedStr = GLMDecode( eGL_ERROR, errorcode ); char buf[512]; V_snprintf( buf, sizeof( buf), "\nGL ERROR! %08x = '%s'\n", errorcode, decodedStr ); // Make sure the dev sees something, because these errors can happen early enough that DevMsg() does nothing. #ifdef WIN32 OutputDebugStringA( buf ); #else printf( "%s", buf ); #endif } } #endif } // debug font void GLMContext::GenDebugFontTex( void ) { if(!m_debugFontTex) { // make a 128x128 RGBA texture GLMTexLayoutKey key; memset( &key, 0, sizeof(key) ); key.m_texGLTarget = GL_TEXTURE_2D; key.m_xSize = 128; key.m_ySize = 128; key.m_zSize = 1; key.m_texFormat = D3DFMT_A8R8G8B8; key.m_texFlags = 0; m_debugFontTex = NewTex( &key, 1, "GLM debug font" ); //----------------------------------------------------- GLMTexLockParams lockreq; lockreq.m_tex = m_debugFontTex; lockreq.m_face = 0; lockreq.m_mip = 0; GLMTexLayoutSlice *slice = &m_debugFontTex->m_layout->m_slices[ lockreq.m_tex->CalcSliceIndex( lockreq.m_face, lockreq.m_mip ) ]; lockreq.m_region.xmin = lockreq.m_region.ymin = lockreq.m_region.zmin = 0; lockreq.m_region.xmax = slice->m_xSize; lockreq.m_region.ymax = slice->m_ySize; lockreq.m_region.zmax = slice->m_zSize; lockreq.m_readback = false; char *lockAddress; int yStride; int zStride; m_debugFontTex->Lock( &lockreq, &lockAddress, &yStride, &zStride ); //----------------------------------------------------- // fetch elements of font data and make texels... we're doing the whole slab so we don't really need the stride info uint32 *destTexelPtr = (uint32 *)lockAddress; for( int index = 0; index < 16384; index++ ) { if (g_glmDebugFontMap[index] == ' ') { // clear *destTexelPtr = 0x00000000; } else { // opaque white (drawing code can modulate if desired) *destTexelPtr = 0xFFFFFFFF; } destTexelPtr++; } //----------------------------------------------------- GLMTexLockParams unlockreq; unlockreq.m_tex = m_debugFontTex; unlockreq.m_face = 0; unlockreq.m_mip = 0; // region need not matter for unlocks unlockreq.m_region.xmin = unlockreq.m_region.ymin = unlockreq.m_region.zmin = 0; unlockreq.m_region.xmax = unlockreq.m_region.ymax = unlockreq.m_region.zmax = 0; unlockreq.m_readback = false; m_debugFontTex->Unlock( &unlockreq ); //----------------------------------------------------- // change up the tex sampling on this texture to be "nearest" not linear //----------------------------------------------------- // don't leave texture bound on the TMU BindTexToTMU( NULL, 0 ); // also make the index and vertex buffers for use - up to 1K indices and 1K verts uint indexBufferSize = 1024*2; m_debugFontIndices = NewBuffer(kGLMIndexBuffer, indexBufferSize, 0); // two byte indices // we go ahead and lock it now, and fill it with indices 0-1023. char *indices = NULL; GLMBuffLockParams idxLock; idxLock.m_nOffset = 0; idxLock.m_nSize = indexBufferSize; idxLock.m_bNoOverwrite = false; idxLock.m_bDiscard = true; m_debugFontIndices->Lock( &idxLock, &indices ); for( int i=0; i<1024; i++) { unsigned short *idxPtr = &((unsigned short*)indices)[i]; *idxPtr = i; } m_debugFontIndices->Unlock(); m_debugFontVertices = NewBuffer(kGLMVertexBuffer, 1024 * 128, 0); // up to 128 bytes per vert } } #define MAX_DEBUG_CHARS 256 struct GLMDebugTextVertex { float x,y,z; float u,v; char rgba[4]; }; void GLMContext::DrawDebugText( float x, float y, float z, float drawCharWidth, float drawCharHeight, char *string ) { if (!m_debugFontTex) { GenDebugFontTex(); } // setup needed to draw text // we're assuming that +x goes left to right on screen, no billboarding math in here // and that +y goes bottom up // caller knows projection / rectangle so it gets to decide vertex spacing // debug font must be bound to TMU 0 // texturing enabled // alpha blending enabled // generate a quad per character // characters are 6px wide by 11 px high. // upper left character in tex is 0x20 // y axis will need to be flipped for display // for any character in 0x20 - 0x7F - here are the needed UV's // leftU = ((character % 16) * 6.0f / 128.0f) // rightU = lowU + (6.0 / 128.0); // topV = ((character - 0x20) * 11.0f / 128.0f) // bottomV = lowV + (11.0f / 128.0f) int stringlen = strlen( string ); if (stringlen > MAX_DEBUG_CHARS) { stringlen = MAX_DEBUG_CHARS; } // lock char *vertices = NULL; GLMBuffLockParams vtxLock; vtxLock.m_nOffset = 0; vtxLock.m_nSize = 1024 * stringlen; vtxLock.m_bNoOverwrite = false; vtxLock.m_bDiscard = false; m_debugFontVertices->Lock( &vtxLock, &vertices ); GLMDebugTextVertex *vtx = (GLMDebugTextVertex*)vertices; GLMDebugTextVertex *vtxOutPtr = vtx; for( int charindex = 0; charindex < stringlen; charindex++ ) { float leftU,rightU,topV,bottomV; int character = (int)string[charindex]; character -= 0x20; if ( (character<0) || (character > 0x7F) ) { character = '*' - 0x20; } leftU = ((character & 0x0F) * 6.0f ) / 128.0f; rightU = leftU + (6.0f / 128.0f); topV = ((character >> 4) * 11.0f ) / 128.0f; bottomV = topV + (11.0f / 128.0f); float posx,posy,posz; posx = x + (drawCharWidth * (float)charindex); posy = y; posz = z; // generate four verts // first vert will be upper left of displayed quad (low X, high Y) then we go clockwise for( int quadvert = 0; quadvert < 4; quadvert++ ) { bool isTop = (quadvert <2); // verts 0 and 1 bool isLeft = (quadvert & 1) == (quadvert >> 1); // verts 0 and 3 vtxOutPtr->x = posx + (isLeft ? 0.0f : drawCharWidth); vtxOutPtr->y = posy + (isTop ? drawCharHeight : 0.0f); vtxOutPtr->z = posz; vtxOutPtr->u = isLeft ? leftU : rightU; vtxOutPtr->v = isTop ? topV : bottomV; vtxOutPtr++; } } // verts are done. // unlock... m_debugFontVertices->Unlock(); // make a vertex setup GLMVertexSetup vertSetup; // position, color, tc = 0, 3, 8 vertSetup.m_attrMask = (1<glDisable( GL_DEPTH_TEST ); gGL->glEnable(GL_TEXTURE_2D); if (0) { gGL->glEnableClientState(GL_VERTEX_ARRAY); gGL->glEnableClientState(GL_TEXTURE_COORD_ARRAY); gGL->glVertexPointer( 3, GL_FLOAT, sizeof( vtx[0] ), &vtx[0].x ); gGL->glClientActiveTexture(GL_TEXTURE0); gGL->glTexCoordPointer( 2, GL_FLOAT, sizeof( vtx[0] ), &vtx[0].u ); } else { SetVertexAttributes( &vertSetup ); } gGL->glDrawArrays( GL_QUADS, 0, stringlen * 4 ); // disable all the input streams if (0) { gGL->glDisableClientState(GL_VERTEX_ARRAY); gGL->glDisableClientState(GL_TEXTURE_COORD_ARRAY); } else { 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*)( (intp)indicesActual + (intp)pIndexBuf->m_pPseudoBuf ); } if (pIndexBuf->m_bUsingPersistentBuffer) { indicesActual = (void*)( (intp)indicesActual + (intp)pIndexBuf->m_nPersistentBufferStartOffset ); } #if GL_ENABLE_INDEX_VERIFICATION // Obviously only for debugging. if ( !pIndexBuf->IsSpanValid( (uint)indices, count * GetDataTypeSizeInBytes( type ) ) ) { // The consumption range crosses more than one lock span, or the lock is trying to consume a bad IB range. DXABSTRACT_BREAK_ON_ERROR(); } if ( ( type == GL_UNSIGNED_SHORT ) && ( pIndexBuf->m_bPseudo ) ) { Assert( start <= end ); for ( int i = 0; i < count; i++) { uint n = ((const uint16*)indicesActual)[i]; if ( ( n < start ) || ( n > end ) ) { DXABSTRACT_BREAK_ON_ERROR(); } } unsigned char *pVertexShaderAttribMap = m_pDevice->m_vertexShader->m_vtxAttribMap; const int nMaxVertexAttributesToCheck = m_drawingProgram[ kGLMVertexProgram ]->m_maxVertexAttrs; IDirect3DVertexDeclaration9 *pVertDecl = m_pDevice->m_pVertDecl; const uint8 *pVertexAttribDescToStreamIndex = pVertDecl->m_VertexAttribDescToStreamIndex; // FIXME: Having to duplicate all this flush logic is terrible here for( int nMask = 1, nIndex = 0; nIndex < nMaxVertexAttributesToCheck; ++nIndex, nMask <<= 1 ) { uint8 vertexShaderAttrib = pVertexShaderAttribMap[ nIndex ]; uint nDeclIndex = pVertexAttribDescToStreamIndex[vertexShaderAttrib]; if ( nDeclIndex == 0xFF ) continue; D3DVERTEXELEMENT9_GL *pDeclElem = &pVertDecl->m_elements[nDeclIndex]; Assert( ( ( vertexShaderAttrib >> 4 ) == pDeclElem->m_dxdecl.Usage ) && ( ( vertexShaderAttrib & 0x0F ) == pDeclElem->m_dxdecl.UsageIndex) ); const uint nStreamIndex = pDeclElem->m_dxdecl.Stream; const D3DStreamDesc *pStream = &m_pDevice->m_streams[ nStreamIndex ]; CGLMBuffer *pBuf = m_pDevice->m_vtx_buffers[ nStreamIndex ]; if ( pBuf == m_pDevice->m_pDummy_vtx_buffer ) continue; Assert( pStream->m_vtxBuffer->m_vtxBuffer == pBuf ); int nBufOffset = pDeclElem->m_gldecl.m_offset + pStream->m_offset; Assert( nBufOffset >= 0 ); Assert( nBufOffset < (int)pBuf->m_nSize ); uint nBufSize = pStream->m_vtxBuffer->m_vtxBuffer->m_nSize; uint nDataTypeSize = GetDataTypeSizeInBytes( pDeclElem->m_gldecl.m_datatype ); uint nActualStride = pStream->m_stride ? pStream->m_stride : nDataTypeSize; uint nStart = nBufOffset + ( start + baseVertex ) * nActualStride; uint nEnd = nBufOffset + ( end + baseVertex ) * nActualStride + nDataTypeSize; if ( nEnd > nBufSize ) { DXABSTRACT_BREAK_ON_ERROR(); } if ( !pStream->m_vtxBuffer->m_vtxBuffer->IsSpanValid( nStart, nEnd - nStart ) ) { // The draw is trying to consume a range of the bound VB that hasn't been set to valid data! DXABSTRACT_BREAK_ON_ERROR(); } } } #endif Assert( m_drawingLang == kGLMGLSL ); if ( m_pBoundPair ) { gGL->glDrawRangeElementsBaseVertex( mode, start, end, count, type, indicesActual, baseVertex ); #if GLMDEBUG if ( m_slowCheckEnable ) { CheckNative(); } #endif } } #else // support for OSX 10.6 (no support for glDrawRangeElementsBaseVertex) // legacy path that we're re-enabling for all OSX users as a result of perf regressions 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*)( (intp)indicesActual + (intp)pIndexBuf->m_pPseudoBuf ); } if (pIndexBuf->m_bUsingPersistentBuffer) { indicesActual = (void*)( (intp)indicesActual + (intp)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 // #ifndef 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_EXT ); 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_EXT ); // 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_EXT ) { // dig up the more detailed FBO status errorcode2 = gGL->glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ); 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 m_layout->m_faceCount; face++) { for( int mip=0; mip 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 m_layout->m_faceCount; face++) { for( int mip=0; mip 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_EXT ); // 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; iglClearColor(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; iDrawDebugText( 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 static signed short nada[] = { -1,-1,-1,-1 }; gGL->glColor4sv( nada ); } #endif