//========= Copyright 1996-2008, Valve LLC, All rights reserved. ============ // // Purpose: Main class for the game engine -- win32 implementation // // $NoKeywords: $ //============================================================================= #include "stdafx.h" #include #include #include #include #include #include "gameenginesdl.h" #include "steam/isteamdualsense.h" CGameEngineGL *g_engine; // dxabstract will use this.. it is set by the engine constructor IGameEngine *CreateGameEngineSDL( ) { static CGameEngineGL* s_pGameEngine = NULL; if (!s_pGameEngine) { s_pGameEngine = new CGameEngineGL( ); } return s_pGameEngine; } void OutputDebugString( const char *pchMsg ) { fprintf( stderr, "%s", pchMsg ); } struct Packet_t { uint32 unSize; void *pData; }; class CVoiceContext { public: CVoiceContext() { alGenBuffers( ARRAYSIZE(m_buffers), m_buffers ); alGenSources( 1, &m_nSource ); alSourcei( m_nSource, AL_LOOPING, AL_FALSE ); for (int i = 0; i < ARRAYSIZE(m_buffers); i++ ) alSourcei( m_nSource, AL_BUFFER, m_buffers[i] ); m_nNextFreeBuffer = 0; } virtual ~CVoiceContext() { // } ALuint m_buffers[4]; ALuint m_nSource; size_t m_nNextFreeBuffer; std::queue m_pending; }; /* Quick utility function for texture creation */ static int power_of_two(int input) { int value = 1; while (value < input) { value <<= 1; } return value; } //----------------------------------------------------------------------------- // Purpose: Constructor for game engine instance //----------------------------------------------------------------------------- CGameEngineGL::CGameEngineGL( ) { g_engine = this; m_bEngineReadyForUse = false; m_bShuttingDown = false; m_window = NULL; m_context = NULL; m_nWindowWidth = 0; m_nWindowHeight = 0; m_ulPreviousGameTickCount = 0; m_ulGameTickCount = 0; m_unVoiceChannelCount = 0; m_hTextureWhite = 0; m_nNextFontHandle = 1; m_nNextTextureHandle = 1; m_hLastTexture = 0; m_rgflPointsData = new GLfloat[ 3*POINT_BUFFER_TOTAL_SIZE ]; m_rgflPointsColorData = new GLubyte[ 4*POINT_BUFFER_TOTAL_SIZE ]; m_dwPointsToFlush = 0; m_rgflLinesData = new GLfloat[ 6*LINE_BUFFER_TOTAL_SIZE ]; m_rgflLinesColorData = new GLubyte[ 8*LINE_BUFFER_TOTAL_SIZE ]; m_dwLinesToFlush = 0; m_rgflQuadsData = new GLfloat [ 12*QUAD_BUFFER_TOTAL_SIZE ]; m_rgflQuadsColorData = new GLubyte[ 16*QUAD_BUFFER_TOTAL_SIZE ]; m_rgflQuadsTextureData = new GLfloat[ 8*QUAD_BUFFER_TOTAL_SIZE ]; m_dwQuadsToFlush = 0; // clear the action handles for ( int i = 0; i ::const_iterator i; for (i = m_MapGameFonts.begin(); i != m_MapGameFonts.end(); ++i) { TTF_CloseFont( i->second ); } m_MapGameFonts.clear(); TTF_Quit(); m_MapStrings.clear(); m_MapTextures.clear(); m_dwLinesToFlush = 0; m_dwPointsToFlush = 0; m_dwQuadsToFlush = 0; } //----------------------------------------------------------------------------- // Purpose: Initialize voice/audio interfaces //----------------------------------------------------------------------------- bool CGameEngineGL::BInitializeAudio() { m_palDevice = alcOpenDevice(NULL); if ( m_palDevice ) { m_palContext = alcCreateContext( m_palDevice, NULL ); alcMakeContextCurrent( m_palContext ); return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Initialize the GL rendering interfaces and default state //----------------------------------------------------------------------------- #define D3DADAPTER_DEFAULT 0 bool CGameEngineGL::BInitializeGraphics() { int nWindowWidth, nWindowHeight; nWindowWidth = m_nWindowWidth = 1024; nWindowHeight = m_nWindowHeight = 768; SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 ); #if defined(USE_SDL2) int windowX = SDL_WINDOWPOS_CENTERED; int windowY = SDL_WINDOWPOS_CENTERED; m_window = SDL_CreateWindow( "SteamworksExample", windowX, windowY, nWindowWidth, nWindowHeight, SDL_WINDOW_OPENGL ); #else m_window = SDL_CreateWindow( "SteamworksExample", nWindowWidth, nWindowHeight, SDL_WINDOW_OPENGL ); #endif if ( !m_window ) { OutputDebugString( "Couldn't create SDL window: " ); OutputDebugString( SDL_GetError() ); OutputDebugString( "\n" ); return false; } m_context = SDL_GL_CreateContext( m_window ); if ( !m_context ) { OutputDebugString( "Couldn't create OpenGL context: " ); OutputDebugString( SDL_GetError() ); OutputDebugString( "\n" ); return false; } GLenum err = glewInit(); if( err != GLEW_OK ) { fprintf(stderr, "glewInit failed with %s\n", glewGetErrorString( err ) ); return false; } SDL_GL_SetSwapInterval( 1 ); // Clear any errors glGetError(); glClearDepth( 1.0f ); glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glDisable( GL_CULL_FACE ); glDisable( GL_ALPHA_TEST ); glDisable( GL_STENCIL_TEST ); glDisable( GL_SCISSOR_TEST ); glDisable( GL_LIGHTING ); glDisable( GL_DEPTH_TEST ); glDisable( GL_FOG ); glDepthMask( GL_FALSE ); // We always need these two glEnableClientState( GL_COLOR_ARRAY ); glEnableClientState( GL_VERTEX_ARRAY ); // This we'll enable as needed glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef( 0, 0, 0 ); glMatrixMode( GL_TEXTURE ); glLoadIdentity(); glTranslatef( 0, 0, 0 ); glDepthRange( 0.0f, 1.0f ); AdjustViewport(); return true; } void CGameEngineGL::AdjustViewport() { SDL_GetWindowSize( m_window, &m_nWindowWidth, &m_nWindowHeight ); glBindFramebuffer(GL_FRAMEBUFFER, 0 ); // Perspective glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho( 0, m_nWindowWidth, m_nWindowHeight, 0, -1.0f, 1.0f ); glTranslatef( 0, 0, 0 ); // View port has changed as well glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef( 0, 0, 0 ); glViewport( 0, 0, m_nWindowWidth, m_nWindowHeight ); glScissor( 0, 0, m_nWindowWidth, m_nWindowHeight ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); glFlush(); } //----------------------------------------------------------------------------- // Purpose: Updates current tick count for the game engine //----------------------------------------------------------------------------- void CGameEngineGL::UpdateGameTickCount() { m_ulPreviousGameTickCount = m_ulGameTickCount; #if defined(USE_SDL2) m_ulGameTickCount = SDL_GetTicks64(); #else m_ulGameTickCount = SDL_GetTicks(); #endif } //----------------------------------------------------------------------------- // Purpose: Tell the game engine to sleep for a bit if needed to limit frame rate. You must keep // calling this repeatedly until it returns false. If it returns true it's slept a little, but more // time may be needed. //----------------------------------------------------------------------------- bool CGameEngineGL::BSleepForFrameRateLimit( uint32 ulMaxFrameRate ) { // Frame rate limiting float flDesiredFrameMilliseconds = 1000.0f/ulMaxFrameRate; #if defined(USE_SDL2) uint64 ulGameTickCount = SDL_GetTicks64(); #else uint64 ulGameTickCount = SDL_GetTicks(); #endif float flMillisecondsElapsed = (float)(ulGameTickCount - m_ulGameTickCount); if ( flMillisecondsElapsed < flDesiredFrameMilliseconds ) { // If enough time is left sleep, otherwise just keep spinning so we don't go over the limit... if ( flDesiredFrameMilliseconds - flMillisecondsElapsed > 3.0f ) { usleep( 5000 ); } else { // Just return right away so we busy loop, don't want to sleep too long and go over } return true; } else { return false; } } //----------------------------------------------------------------------------- // Purpose: Set the background color to clear to //----------------------------------------------------------------------------- void CGameEngineGL::SetBackgroundColor( short a, short r, short g, short b ) { glClearColor( (float)r/255.0f, (float)g/255.0f, (float)b/255.0f, (float)a/255.0f ); } //----------------------------------------------------------------------------- // Purpose: Start a new frame //----------------------------------------------------------------------------- bool CGameEngineGL::StartFrame() { AdjustViewport(); // Pump system callbacks MessagePump(); // Poll Steam Input devices PollSteamInput(); // We may now be shutting down, check and don't start a frame then if ( BShuttingDown() ) return false; #if 0 // for debug static unsigned char counter; counter++; glClearColor( (float)counter/255.0f, (float)counter/255.0f, (float)counter/255.0f, (float)1.0f ); #endif // Clear the screen for the new frame glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); return true; } //----------------------------------------------------------------------------- // Purpose: End the current frame //----------------------------------------------------------------------------- void CGameEngineGL::EndFrame() { if ( BShuttingDown() ) return; // Flush point buffer BFlushPointBuffer(); // Flush line buffer BFlushLineBuffer(); // Flush quad buffer BFlushQuadBuffer(); // Swap buffers now that everything is flushed SDL_GL_SwapWindow( m_window ); RunAudio(); } //----------------------------------------------------------------------------- // Purpose: Draw a line, the engine internally manages a vertex buffer for batching these //----------------------------------------------------------------------------- bool CGameEngineGL::BDrawLine( float xPos0, float yPos0, DWORD dwColor0, float xPos1, float yPos1, DWORD dwColor1 ) { if ( m_bShuttingDown ) return false; // Check if we are out of room and need to flush the buffer if ( m_dwLinesToFlush == LINE_BUFFER_TOTAL_SIZE ) { BFlushLineBuffer(); } DWORD dwOffset = m_dwLinesToFlush*6; m_rgflLinesData[dwOffset] = xPos0; m_rgflLinesData[dwOffset+1] = yPos0; m_rgflLinesData[dwOffset+2] = 1.0; m_rgflLinesData[dwOffset+3] = xPos1; m_rgflLinesData[dwOffset+4] = yPos1; m_rgflLinesData[dwOffset+5] = 1.0; dwOffset = m_dwLinesToFlush*8; m_rgflLinesColorData[dwOffset] = COLOR_RED( dwColor0 ); m_rgflLinesColorData[dwOffset+1] = COLOR_GREEN( dwColor0 ); m_rgflLinesColorData[dwOffset+2] = COLOR_BLUE( dwColor0 ); m_rgflLinesColorData[dwOffset+3] = COLOR_ALPHA( dwColor0 ); m_rgflLinesColorData[dwOffset+4] = COLOR_RED( dwColor1 ); m_rgflLinesColorData[dwOffset+5] = COLOR_GREEN( dwColor1 ); m_rgflLinesColorData[dwOffset+6] = COLOR_BLUE( dwColor1 ); m_rgflLinesColorData[dwOffset+7] = COLOR_ALPHA( dwColor1 ); ++m_dwLinesToFlush; return true; } //----------------------------------------------------------------------------- // Purpose: Flush batched lines to the screen //----------------------------------------------------------------------------- bool CGameEngineGL::BFlushLineBuffer() { if ( !m_rgflLinesColorData || !m_rgflLinesData || m_bShuttingDown ) return false; if ( m_dwLinesToFlush ) { glColorPointer( 4, GL_UNSIGNED_BYTE, 0, m_rgflLinesColorData ); glVertexPointer( 3, GL_FLOAT, 0, m_rgflLinesData ); glDrawArrays( GL_LINES, 0, m_dwLinesToFlush*2 ); m_dwLinesToFlush = 0; } return true; } //----------------------------------------------------------------------------- // Purpose: Draw a point, the engine internally manages a vertex buffer for batching these //----------------------------------------------------------------------------- bool CGameEngineGL::BDrawPoint( float xPos, float yPos, DWORD dwColor ) { if ( m_bShuttingDown ) return false; // Check if we are out of room and need to flush the buffer if ( m_dwPointsToFlush == POINT_BUFFER_TOTAL_SIZE ) { BFlushPointBuffer(); } DWORD dwOffset = m_dwPointsToFlush*3; m_rgflPointsData[dwOffset] = xPos; m_rgflPointsData[dwOffset+1] = yPos; m_rgflPointsData[dwOffset+2] = 1.0; dwOffset = m_dwPointsToFlush*4; m_rgflPointsColorData[dwOffset] = COLOR_RED( dwColor ); m_rgflPointsColorData[dwOffset+1] = COLOR_GREEN( dwColor ); m_rgflPointsColorData[dwOffset+2] = COLOR_BLUE( dwColor ); m_rgflPointsColorData[dwOffset+3] = COLOR_ALPHA( dwColor ); ++m_dwPointsToFlush; return true; } //----------------------------------------------------------------------------- // Purpose: Flush batched points to the screen //----------------------------------------------------------------------------- bool CGameEngineGL::BFlushPointBuffer() { if ( !m_rgflPointsColorData || !m_rgflPointsData || m_bShuttingDown ) return false; if ( m_dwPointsToFlush ) { glColorPointer( 4, GL_UNSIGNED_BYTE, 0, m_rgflPointsColorData ); glVertexPointer( 3, GL_FLOAT, 0, m_rgflPointsData ); glDrawArrays( GL_POINTS, 0, m_dwPointsToFlush ); m_dwPointsToFlush = 0; } return true; } //----------------------------------------------------------------------------- // Purpose: Draw a filled quad //----------------------------------------------------------------------------- bool CGameEngineGL::BDrawFilledRect( float xPos0, float yPos0, float xPos1, float yPos1, DWORD dwColor ) { if ( !m_hTextureWhite ) { byte *pRGBAData = new byte[ 1 * 1 * 4 ]; memset( pRGBAData, 255, 1*1*4 ); m_hTextureWhite = HCreateTexture( pRGBAData, 1, 1 ); delete[] pRGBAData; } return BDrawTexturedRect( xPos0, yPos0, xPos1, yPos1, 0.0f, 0.0f, 1.0f, 1.0f, dwColor, m_hTextureWhite ); } //----------------------------------------------------------------------------- // Purpose: Draw a textured rect //----------------------------------------------------------------------------- bool CGameEngineGL::BDrawTexturedRect( float xPos0, float yPos0, float xPos1, float yPos1, float u0, float v0, float u1, float v1, DWORD dwColor, HGAMETEXTURE hTexture ) { if ( m_bShuttingDown ) return false; // Find the texture std::map::iterator iter; iter = m_MapTextures.find( hTexture ); if ( iter == m_MapTextures.end() ) { OutputDebugString( "BDrawTexturedQuad called with invalid hTexture value\n" ); return false; } // Check if we are out of room and need to flush the buffer, or if our texture is changing // then we also need to flush the buffer. if ( m_dwQuadsToFlush == QUAD_BUFFER_TOTAL_SIZE || m_hLastTexture != hTexture ) { BFlushQuadBuffer(); } // Bind the new texture glBindTexture( GL_TEXTURE_2D, iter->second.m_uTextureID ); DWORD dwOffset = m_dwQuadsToFlush*12; m_rgflQuadsData[dwOffset] = xPos0; m_rgflQuadsData[dwOffset+1] = yPos0; m_rgflQuadsData[dwOffset+2] = 1.0; m_rgflQuadsData[dwOffset+3] = xPos1; m_rgflQuadsData[dwOffset+4] = yPos0; m_rgflQuadsData[dwOffset+5] = 1.0; m_rgflQuadsData[dwOffset+6] = xPos1; m_rgflQuadsData[dwOffset+7] = yPos1; m_rgflQuadsData[dwOffset+8] = 1.0; m_rgflQuadsData[dwOffset+9] = xPos0; m_rgflQuadsData[dwOffset+10] = yPos1; m_rgflQuadsData[dwOffset+11] = 1.0; dwOffset = m_dwQuadsToFlush*16; m_rgflQuadsColorData[dwOffset] = COLOR_RED( dwColor ); m_rgflQuadsColorData[dwOffset+1] = COLOR_GREEN( dwColor ); m_rgflQuadsColorData[dwOffset+2] = COLOR_BLUE( dwColor ); m_rgflQuadsColorData[dwOffset+3] = COLOR_ALPHA( dwColor ); m_rgflQuadsColorData[dwOffset+4] = COLOR_RED( dwColor ); m_rgflQuadsColorData[dwOffset+5] = COLOR_GREEN( dwColor ); m_rgflQuadsColorData[dwOffset+6] = COLOR_BLUE( dwColor ); m_rgflQuadsColorData[dwOffset+7] = COLOR_ALPHA( dwColor ); m_rgflQuadsColorData[dwOffset+8] = COLOR_RED( dwColor ); m_rgflQuadsColorData[dwOffset+9] = COLOR_GREEN( dwColor ); m_rgflQuadsColorData[dwOffset+10] = COLOR_BLUE( dwColor ); m_rgflQuadsColorData[dwOffset+11] = COLOR_ALPHA( dwColor ); m_rgflQuadsColorData[dwOffset+12] = COLOR_RED( dwColor ); m_rgflQuadsColorData[dwOffset+13] = COLOR_GREEN( dwColor ); m_rgflQuadsColorData[dwOffset+14] = COLOR_BLUE( dwColor ); m_rgflQuadsColorData[dwOffset+15] = COLOR_ALPHA( dwColor ); dwOffset = m_dwQuadsToFlush*8; m_rgflQuadsTextureData[dwOffset] = u0; m_rgflQuadsTextureData[dwOffset+1] = v0; m_rgflQuadsTextureData[dwOffset+2] = u1; m_rgflQuadsTextureData[dwOffset+3] = v0; m_rgflQuadsTextureData[dwOffset+4] = u1; m_rgflQuadsTextureData[dwOffset+5] = v1; m_rgflQuadsTextureData[dwOffset+6] = u0; m_rgflQuadsTextureData[dwOffset+7] = v1; ++m_dwQuadsToFlush; return true; } //----------------------------------------------------------------------------- // Purpose: Draw a textured quad //----------------------------------------------------------------------------- bool CGameEngineGL::BDrawTexturedQuad( float xPos0, float yPos0, float xPos1, float yPos1, float xPos2, float yPos2, float xPos3, float yPos3, float u0, float v0, float u1, float v1, DWORD dwColor, HGAMETEXTURE hTexture ) { if ( m_bShuttingDown ) return false; // Find the texture std::map::iterator iter; iter = m_MapTextures.find( hTexture ); if ( iter == m_MapTextures.end() ) { OutputDebugString( "BDrawTexturedQuad called with invalid hTexture value\n" ); return false; } // Check if we are out of room and need to flush the buffer, or if our texture is changing // then we also need to flush the buffer. if ( m_dwQuadsToFlush == QUAD_BUFFER_TOTAL_SIZE || m_hLastTexture != hTexture ) { BFlushQuadBuffer(); } // Bind the new texture glBindTexture( GL_TEXTURE_2D, iter->second.m_uTextureID ); DWORD dwOffset = m_dwQuadsToFlush*12; m_rgflQuadsData[dwOffset] = xPos0; m_rgflQuadsData[dwOffset+1] = yPos0; m_rgflQuadsData[dwOffset+2] = 1.0; m_rgflQuadsData[dwOffset+3] = xPos1; m_rgflQuadsData[dwOffset+4] = yPos1; m_rgflQuadsData[dwOffset+5] = 1.0; m_rgflQuadsData[dwOffset+6] = xPos2; m_rgflQuadsData[dwOffset+7] = yPos2; m_rgflQuadsData[dwOffset+8] = 1.0; m_rgflQuadsData[dwOffset+9] = xPos3; m_rgflQuadsData[dwOffset+10] = yPos3; m_rgflQuadsData[dwOffset+11] = 1.0; dwOffset = m_dwQuadsToFlush*16; m_rgflQuadsColorData[dwOffset] = COLOR_RED( dwColor ); m_rgflQuadsColorData[dwOffset+1] = COLOR_GREEN( dwColor ); m_rgflQuadsColorData[dwOffset+2] = COLOR_BLUE( dwColor ); m_rgflQuadsColorData[dwOffset+3] = COLOR_ALPHA( dwColor ); m_rgflQuadsColorData[dwOffset+4] = COLOR_RED( dwColor ); m_rgflQuadsColorData[dwOffset+5] = COLOR_GREEN( dwColor ); m_rgflQuadsColorData[dwOffset+6] = COLOR_BLUE( dwColor ); m_rgflQuadsColorData[dwOffset+7] = COLOR_ALPHA( dwColor ); m_rgflQuadsColorData[dwOffset+8] = COLOR_RED( dwColor ); m_rgflQuadsColorData[dwOffset+9] = COLOR_GREEN( dwColor ); m_rgflQuadsColorData[dwOffset+10] = COLOR_BLUE( dwColor ); m_rgflQuadsColorData[dwOffset+11] = COLOR_ALPHA( dwColor ); m_rgflQuadsColorData[dwOffset+12] = COLOR_RED( dwColor ); m_rgflQuadsColorData[dwOffset+13] = COLOR_GREEN( dwColor ); m_rgflQuadsColorData[dwOffset+14] = COLOR_BLUE( dwColor ); m_rgflQuadsColorData[dwOffset+15] = COLOR_ALPHA( dwColor ); dwOffset = m_dwQuadsToFlush*8; m_rgflQuadsTextureData[dwOffset] = u0; m_rgflQuadsTextureData[dwOffset+1] = v0; m_rgflQuadsTextureData[dwOffset+2] = u1; m_rgflQuadsTextureData[dwOffset+3] = v0; m_rgflQuadsTextureData[dwOffset+4] = u1; m_rgflQuadsTextureData[dwOffset+5] = v1; m_rgflQuadsTextureData[dwOffset+6] = u0; m_rgflQuadsTextureData[dwOffset+7] = v1; ++m_dwQuadsToFlush; return true; } //----------------------------------------------------------------------------- // Purpose: Flush buffered quads //----------------------------------------------------------------------------- bool CGameEngineGL::BFlushQuadBuffer() { if ( !m_rgflPointsColorData || !m_rgflPointsData || m_bShuttingDown ) return false; if ( m_dwQuadsToFlush ) { glEnable( GL_TEXTURE_2D ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glColorPointer( 4, GL_UNSIGNED_BYTE, 0, m_rgflQuadsColorData ); glVertexPointer( 3, GL_FLOAT, 0, m_rgflQuadsData ); glTexCoordPointer( 2, GL_FLOAT, 0, m_rgflQuadsTextureData ); glDrawArrays( GL_QUADS, 0, m_dwQuadsToFlush*4 ); glDisable( GL_TEXTURE_2D ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); m_dwQuadsToFlush = 0; } return true; } //----------------------------------------------------------------------------- // Purpose: Creates a new texture //----------------------------------------------------------------------------- HGAMETEXTURE CGameEngineGL::HCreateTexture( byte *pRGBAData, uint32 uWidth, uint32 uHeight, ETEXTUREFORMAT eTextureFormat ) { if ( m_bShuttingDown ) return 0; TextureData_t TexData; TexData.m_uWidth = uWidth; TexData.m_uHeight = uHeight; TexData.m_uTextureID = 0; glEnable( GL_TEXTURE_2D ); glGenTextures( 1, &TexData.m_uTextureID ); glBindTexture( GL_TEXTURE_2D, TexData.m_uTextureID ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0 ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0 ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); // build our texture mipmaps glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, uWidth, uHeight, 0, eTextureFormat == eTextureFormat_RGBA ? GL_RGBA : GL_BGRA, GL_UNSIGNED_BYTE, (void *)pRGBAData ); glDisable( GL_TEXTURE_2D ); int nHandle = m_nNextTextureHandle; ++m_nNextTextureHandle; m_MapTextures[nHandle] = TexData; return nHandle; } //----------------------------------------------------------------------------- // Purpose: update an exiting textue //----------------------------------------------------------------------------- bool CGameEngineGL::UpdateTexture( HGAMETEXTURE texture, byte *pRGBAData, uint32 uWidth, uint32 uHeight, ETEXTUREFORMAT eTextureFormat ) { if ( m_bShuttingDown ) return false; std::map::iterator iter; iter = m_MapTextures.find( texture ); if ( iter == m_MapTextures.end() ) { OutputDebugString( "BDrawTexturedQuad called with invalid hTexture value\n" ); return false; } glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, iter->second.m_uTextureID ); // build our texture mipmaps glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, uWidth, uHeight, 0, eTextureFormat == eTextureFormat_RGBA ? GL_RGBA : GL_BGRA, GL_UNSIGNED_BYTE, (void *)pRGBAData ); glDisable( GL_TEXTURE_2D ); return true; } //----------------------------------------------------------------------------- // Purpose: Creates a new font //----------------------------------------------------------------------------- HGAMEFONT CGameEngineGL::HCreateFont( int nHeight, int nFontWeight, bool bItalic, const char * pchFont ) { // For this sample we include a single font pchFont = "DejaVuSans.ttf"; TTF_Font *font = TTF_OpenFont( pchFont, nHeight ); if ( !font ) { OutputDebugString( "Couldn't create font: " ); OutputDebugString( pchFont ); OutputDebugString( "\n" ); return 0; } HGAMEFONT hFont = m_nNextFontHandle; ++m_nNextFontHandle; int nStyle = TTF_STYLE_NORMAL; if ( nFontWeight & FW_BOLD ) { nStyle |= TTF_STYLE_BOLD; } if ( bItalic ) { nStyle |= TTF_STYLE_ITALIC; } TTF_SetFontStyle( font, nStyle ); m_MapGameFonts[ hFont ] = font; return hFont; } //----------------------------------------------------------------------------- // Purpose: Draws text to the screen inside the given rectangular region, using the given font //----------------------------------------------------------------------------- bool CGameEngineGL::BDrawString( HGAMEFONT hFont, RECT rect, DWORD dwColor, DWORD dwFormat, const char *pchText ) { if ( !hFont ) { OutputDebugString( "Someone is calling BDrawString with a null font handle\n" ); return false; } if ( !pchText || !*pchText ) { return true; } // Very simple cache of complete strings as whole textures. // There are much better ways of doing efficient text rendering. // If nothing else we should expire the strings not being used. HGAMETEXTURE hTexture; char szFontPrefix[32]; sprintf( szFontPrefix, "%d:", hFont ); std::map< std::string, HGAMETEXTURE >::iterator iter; iter = m_MapStrings.find( std::string(szFontPrefix) + std::string(pchText) ); if ( iter == m_MapStrings.end() ) { static SDL_Color white = { 0xff, 0xff, 0xff, 0xff }; // Calculate the text block size SDL_Surface *surface = TTF_RenderUTF8_Blended( m_MapGameFonts[ hFont ], pchText, white ); if ( !surface ) { OutputDebugString( "Out of memory\n" ); return false; } uint32 uWidth = power_of_two( surface->w ); uint32 uHeight = power_of_two( surface->h ); byte *pRGBAData = (byte *)malloc( uWidth*uHeight*4 ); if ( !pRGBAData ) { OutputDebugString( "Out of memory\n" ); return false; } memset( pRGBAData, 0, uWidth*uHeight*4 ); byte *src = (byte*)surface->pixels; byte *dst = pRGBAData; memset(dst, 0xff, uWidth*4); for ( uint32 row = 0; row < surface->h; ++row ) { memcpy( dst, src, surface->w * 4 ); src += surface->pitch; dst += uWidth * 4; } hTexture = HCreateTexture( pRGBAData, uWidth, uHeight ); free( pRGBAData ); // Record the actual text width and height m_MapTextures[ hTexture ].m_uWidth = surface->w; m_MapTextures[ hTexture ].m_uHeight = surface->h; #if defined(USE_SDL2) SDL_FreeSurface( surface ); #else SDL_DestroySurface( surface ); #endif m_MapStrings[ std::string(szFontPrefix) + std::string(pchText) ] = hTexture; } else { hTexture = iter->second; } int nWidth = m_MapTextures[ hTexture ].m_uWidth; int nHeight = m_MapTextures[ hTexture ].m_uHeight; float u = (float)nWidth / power_of_two(nWidth); float v = (float)nHeight / power_of_two(nHeight); // Get text position int nLeft = rect.left, nTop = rect.top; if ( dwFormat & TEXTPOS_TOP ) { nTop = rect.top; } else if ( dwFormat & TEXTPOS_VCENTER ) { nTop = rect.top + ((rect.bottom - rect.top) - nHeight) / 2; } else if ( dwFormat & TEXTPOS_BOTTOM ) { nTop = rect.bottom - nHeight; } if ( dwFormat & TEXTPOS_LEFT ) { nLeft = rect.left; } else if ( dwFormat & TEXTPOS_CENTER ) { nLeft = rect.left + ((rect.right - rect.left) - nWidth) / 2; } else if ( dwFormat & TEXTPOS_RIGHT ) { nLeft = rect.right - nWidth; } //dprintf(2, "Drawing text '%s' at %d,%d %dx%d {%ld,%ld %ld,%ld}\n", pchText, nLeft, nTop, nWidth, nHeight, rect.left, rect.top, rect.right, rect.bottom); return BDrawTexturedRect( nLeft, nTop, nLeft + nWidth, nTop + nHeight, 0.0f, 0.0f, u, v, dwColor, hTexture ); } void CGameEngineGL::UpdateKey( uint32_t vkKey, int nDown ) { if ( nDown ) m_SetKeysDown.insert( vkKey ); else m_SetKeysDown.erase( vkKey ); } //----------------------------------------------------------------------------- // Purpose: Message pump for OS messages //----------------------------------------------------------------------------- void CGameEngineGL::MessagePump() { #if defined(USE_SDL2) #define SDL_EVENT_KEY_DOWN SDL_KEYDOWN #define SDL_EVENT_KEY_UP SDL_KEYUP #define SDL_EVENT_QUIT SDL_QUIT #endif SDL_Event event; do { if ( SDL_PollEvent(&event) <= 0 ) break; if ( event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP ) { DWORD dwVK = 0; switch (event.key.keysym.sym) { case SDLK_BACKSPACE: dwVK = VK_BACK; break; case SDLK_TAB: dwVK = VK_TAB; break; case SDLK_RETURN: dwVK = VK_RETURN; break; case SDLK_LSHIFT: dwVK = VK_SHIFT; break; case SDLK_RSHIFT: dwVK = VK_SHIFT; break; case SDLK_LCTRL: dwVK = VK_CONTROL; break; case SDLK_RCTRL: dwVK = VK_CONTROL; break; case SDLK_ESCAPE: dwVK = VK_ESCAPE; break; case SDLK_SPACE: dwVK = VK_SPACE; break; case SDLK_LEFT: dwVK = VK_LEFT; break; case SDLK_UP: dwVK = VK_UP; break; case SDLK_RIGHT: dwVK = VK_RIGHT; break; case SDLK_DOWN: dwVK = VK_DOWN; break; case SDLK_SELECT: dwVK = VK_SELECT; break; case SDLK_F5: dwVK = VK_F5; break; default: { if ( event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z ) { dwVK = 'A' + event.key.keysym.sym - SDLK_a; } } break; } if (dwVK) { if ( event.type == SDL_EVENT_KEY_DOWN ) { m_SetKeysDown.insert( dwVK ); } else { m_SetKeysDown.erase( dwVK ); } } } else if ( event.type == SDL_EVENT_QUIT ) { CreateGameEngineSDL( )->Shutdown(); } } while ( !BShuttingDown() ); } //----------------------------------------------------------------------------- // Purpose: Find out if a key is currently down //----------------------------------------------------------------------------- bool CGameEngineGL::BIsKeyDown( DWORD dwVK ) { std::set::iterator iter; iter = m_SetKeysDown.find( dwVK ); if ( iter != m_SetKeysDown.end() ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: Get a down key value //----------------------------------------------------------------------------- bool CGameEngineGL::BGetFirstKeyDown( DWORD *pdwVK ) { std::set::iterator iter; iter = m_SetKeysDown.begin(); if ( iter != m_SetKeysDown.end() ) { *pdwVK = *iter; m_SetKeysDown.erase( iter ); return true; } else { return false; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- HGAMEVOICECHANNEL CGameEngineGL::HCreateVoiceChannel() { m_unVoiceChannelCount++; CVoiceContext* pVoiceContext = new CVoiceContext; m_MapVoiceChannel[m_unVoiceChannelCount] = pVoiceContext; return m_unVoiceChannelCount; } void CGameEngineGL::RunAudio() { std::map::iterator iter; for( iter = m_MapVoiceChannel.begin(); iter!=m_MapVoiceChannel.end(); ++iter) { CVoiceContext* pVoice = iter->second; const int nBufferCount = ARRAYSIZE( pVoice->m_buffers ); ALint nQueued, nProcessed; alGetSourcei( pVoice->m_nSource, AL_BUFFERS_QUEUED, &nQueued ); alGetSourcei( pVoice->m_nSource, AL_BUFFERS_PROCESSED, &nProcessed ); if ( ( nQueued == nBufferCount ) && ( nProcessed == 0 ) ) { // No room at the inn continue; } ALuint nBufferID; for ( int i = 0; i < nProcessed; i++ ) alSourceUnqueueBuffers( pVoice->m_nSource, 1, &nBufferID ); int nMaxToQueue = nBufferCount - nQueued + nProcessed; bool bQueued = false; while ( nMaxToQueue && !pVoice->m_pending.empty() ) { Packet_t &packet = pVoice->m_pending.front(); nBufferID = pVoice->m_buffers[ pVoice->m_nNextFreeBuffer ]; alBufferData( nBufferID, AL_FORMAT_MONO16, packet.pData, packet.unSize, VOICE_OUTPUT_SAMPLE_RATE_IDEAL ); pVoice->m_nNextFreeBuffer = (pVoice->m_nNextFreeBuffer + 1 ) % nBufferCount; alSourceQueueBuffers( pVoice->m_nSource, 1, &nBufferID); nMaxToQueue--; free( packet.pData ); pVoice->m_pending.pop(); bQueued = true; } if ( bQueued && ( (nQueued-nProcessed) == 0 ) ) { alSourcePlay( pVoice->m_nSource ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGameEngineGL::DestroyVoiceChannel( HGAMEVOICECHANNEL hChannel ) { std::map::iterator iter; iter = m_MapVoiceChannel.find( hChannel ); if ( iter != m_MapVoiceChannel.end() ) { CVoiceContext* pVoiceContext = iter->second; // free outstanding voice packets while( !pVoiceContext->m_pending.empty() ) { free( pVoiceContext->m_pending.front().pData ); pVoiceContext->m_pending.pop(); } delete pVoiceContext; m_MapVoiceChannel.erase( iter ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CGameEngineGL::AddVoiceData( HGAMEVOICECHANNEL hChannel, const uint8 *pVoiceData, uint32 uLength ) { std::map::iterator iter; iter = m_MapVoiceChannel.find( hChannel ); if ( iter == m_MapVoiceChannel.end() ) return false; // channel not found CVoiceContext* pVoiceContext = iter->second; Packet_t packet; packet.pData = malloc ( uLength ); memcpy( packet.pData, pVoiceData, uLength ); packet.unSize = uLength; pVoiceContext->m_pending.push( packet ); return true; } //----------------------------------------------------------------------------- // Purpose: Return true if there is an active Steam Controller //----------------------------------------------------------------------------- bool CGameEngineGL::BIsSteamInputDeviceActive( ) { if ( m_ActiveControllerHandle ) { return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Initialize the steam controller actions //----------------------------------------------------------------------------- void CGameEngineGL::InitSteamInput( ) { // Digital game actions m_ControllerDigitalActionHandles[eControllerDigitalAction_TurnLeft] =SteamInput()->GetDigitalActionHandle( "turn_left" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_TurnRight] =SteamInput()->GetDigitalActionHandle( "turn_right" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_ForwardThrust] =SteamInput()->GetDigitalActionHandle( "forward_thrust" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_ReverseThrust] =SteamInput()->GetDigitalActionHandle( "backward_thrust" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_FireLasers] =SteamInput()->GetDigitalActionHandle( "fire_lasers" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_PauseMenu] =SteamInput()->GetDigitalActionHandle( "pause_menu" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_MenuUp] =SteamInput()->GetDigitalActionHandle( "menu_up" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_MenuDown] =SteamInput()->GetDigitalActionHandle( "menu_down" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_MenuLeft] =SteamInput()->GetDigitalActionHandle( "menu_left" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_MenuRight] =SteamInput()->GetDigitalActionHandle( "menu_right" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_MenuSelect] =SteamInput()->GetDigitalActionHandle( "menu_select" ); m_ControllerDigitalActionHandles[eControllerDigitalAction_MenuCancel] =SteamInput()->GetDigitalActionHandle( "menu_cancel" ); // Analog game actions m_ControllerAnalogActionHandles[eControllerAnalogAction_AnalogControls] =SteamInput()->GetAnalogActionHandle( "analog_controls" ); // Action set handles m_ControllerActionSetHandles[eControllerActionSet_ShipControls] =SteamInput()->GetActionSetHandle( "ship_controls" ); m_ControllerActionSetHandles[eControllerActionSet_MenuControls] =SteamInput()->GetActionSetHandle( "menu_controls" ); } //----------------------------------------------------------------------------- // Purpose: Find an active Steam controller //----------------------------------------------------------------------------- void CGameEngineGL::FindActiveSteamInputDevice( ) { // Use the first available steam controller for all interaction. We can call this each frame to handle // a controller disconnecting and a different one reconnecting. Handles are guaranteed to be unique for // a given controller, even across power cycles. // See how many Steam Controllers are active. ControllerHandle_t pHandles[STEAM_CONTROLLER_MAX_COUNT]; int nNumActive = SteamInput()->GetConnectedControllers( pHandles ); // If there's an active controller, and if we're not already using it, select the first one. if ( nNumActive && (m_ActiveControllerHandle != pHandles[0]) ) { m_ActiveControllerHandle = pHandles[0]; } } //-------------------------------------------------------------------------------------------------------------- // Purpose: For a given in-game action in a given action set, return a human-reaadable string to use as a prompt. //-------------------------------------------------------------------------------------------------------------- const char *CGameEngineGL::GetTextStringForControllerOriginDigital( ECONTROLLERACTIONSET dwActionSet, ECONTROLLERDIGITALACTION dwDigitalAction ) { EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; int nNumOrigins =SteamInput()->GetDigitalActionOrigins( m_ActiveControllerHandle, m_ControllerActionSetHandles[dwActionSet], m_ControllerDigitalActionHandles[dwDigitalAction], origins ); if ( nNumOrigins ) { // We should handle the case where this action is bound to multiple buttons, but // here we just grab the first. return SteamInput()->GetStringForActionOrigin( origins[0] ); } return SteamInput()->GetStringForActionOrigin( k_EInputActionOrigin_None ); // Return "None" } //-------------------------------------------------------------------------------------------------------------- // Purpose: For a given in-game action in a given action set, return a human-reaadable string to use as a prompt. //-------------------------------------------------------------------------------------------------------------- const char *CGameEngineGL::GetTextStringForControllerOriginAnalog( ECONTROLLERACTIONSET dwActionSet, ECONTROLLERANALOGACTION dwDigitalAction ) { EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; int nNumOrigins =SteamInput()->GetAnalogActionOrigins( m_ActiveControllerHandle, m_ControllerActionSetHandles[dwActionSet], m_ControllerDigitalActionHandles[dwDigitalAction], origins ); if ( nNumOrigins ) { // We should handle the case where this action is bound to multiple buttons, but // here we just grab the first. return SteamInput()->GetStringForActionOrigin( origins[0] ); } return SteamInput()->GetStringForActionOrigin( k_EInputActionOrigin_None ); // Return "None" } //----------------------------------------------------------------------------- // Purpose: Called each frame //----------------------------------------------------------------------------- void CGameEngineGL::PollSteamInput( ) { // There's a bug where the action handles aren't non-zero until a config is done loading. Soon config // information will be available immediately. Until then try to init as long as the handles are invalid. if ( m_ControllerDigitalActionHandles[eControllerDigitalAction_TurnLeft] == 0 ) { InitSteamInput( ); return; } // Each frame check our active controller handle FindActiveSteamInputDevice( ); } //----------------------------------------------------------------------------- // Purpose: Set the LED color on the controller, if supported by controller //----------------------------------------------------------------------------- void CGameEngineGL::SetControllerColor( uint8 nColorR, uint8 nColorG, uint8 nColorB, unsigned int nFlags ) { SteamInput()->SetLEDColor( m_ActiveControllerHandle, nColorR, nColorG, nColorB, nFlags ); } //----------------------------------------------------------------------------- // Purpose: Set the trigger effect on DualSense controllers //----------------------------------------------------------------------------- void CGameEngineGL::SetTriggerEffect( bool bEnabled ) { ScePadTriggerEffectParam param; memset( ¶m, 0, sizeof( param ) ); param.triggerMask = SCE_PAD_TRIGGER_EFFECT_TRIGGER_MASK_R2; // Clear any existing effect param.command[ SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_R2 ].mode = SCE_PAD_TRIGGER_EFFECT_MODE_OFF; SteamInput()->SetDualSenseTriggerEffect( m_ActiveControllerHandle, ¶m ); if ( bEnabled ) { param.command[ SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_R2 ].mode = SCE_PAD_TRIGGER_EFFECT_MODE_VIBRATION; param.command[ SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_R2 ].commandData.vibrationParam.position = 5; param.command[ SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_R2 ].commandData.vibrationParam.amplitude = 5; param.command[ SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_R2 ].commandData.vibrationParam.frequency = 8; SteamInput()->SetDualSenseTriggerEffect( m_ActiveControllerHandle, ¶m ); } } //----------------------------------------------------------------------------- // Purpose: Trigger vibration on the controller, if supported by controller //----------------------------------------------------------------------------- void CGameEngineGL::TriggerControllerVibration( unsigned short nLeftSpeed, unsigned short nRightSpeed ) { SteamInput()->TriggerVibration( m_ActiveControllerHandle, nLeftSpeed, nRightSpeed ); } //----------------------------------------------------------------------------- // Purpose: Trigger haptics on the controller, if supported by controller //----------------------------------------------------------------------------- void CGameEngineGL::TriggerControllerHaptics( ESteamControllerPad ePad, unsigned short usOnMicroSec, unsigned short usOffMicroSec, unsigned short usRepeat ) { SteamInput()->Legacy_TriggerRepeatedHapticPulse( m_ActiveControllerHandle, ePad, usOnMicroSec, usOffMicroSec, usRepeat, 0 ); } //----------------------------------------------------------------------------- // Purpose: Find out if a controller event is currently active //----------------------------------------------------------------------------- bool CGameEngineGL::BIsControllerActionActive( ECONTROLLERDIGITALACTION dwAction ) { ControllerDigitalActionData_t digitalData =SteamInput()->GetDigitalActionData( m_ActiveControllerHandle, m_ControllerDigitalActionHandles[dwAction] ); // Actions are only 'active' when they're assigned to a control in an action set, and that action set is active. if ( digitalData.bActive ) return digitalData.bState; return false; } //--------------------------------------------------------------------------------------------------------------------------------------------------- // Purpose: Get the current x,y state of the analog action. Examples of an analog action are a virtual joystick on the trackpad or the real joystick. //--------------------------------------------------------------------------------------------------------------------------------------------------- void CGameEngineGL::GetControllerAnalogAction( ECONTROLLERANALOGACTION dwAction, float *x, float *y ) { ControllerAnalogActionData_t analogData =SteamInput()->GetAnalogActionData( m_ActiveControllerHandle, m_ControllerAnalogActionHandles[dwAction] ); // Actions are only 'active' when they're assigned to a control in an action set, and that action set is active. if ( analogData.bActive ) { *x = analogData.x; *y = analogData.y; } else { *x = 0.0f; *y = 0.0f; } } //----------------------------------------------------------------------------------------------------------------------------------------------------- // Purpose: Put the controller into a specific action set. Action sets are collections of game-context actions ie "walking", "flying" or "menu" //----------------------------------------------------------------------------------------------------------------------------------------------------- void CGameEngineGL::SetSteamControllerActionSet( ECONTROLLERACTIONSET dwActionSet ) { if ( m_ActiveControllerHandle == 0 ) return; // This call is low-overhead and can be called repeatedly from game code that is active in a specific mode. SteamController( )->ActivateActionSet( m_ActiveControllerHandle, m_ControllerActionSetHandles[dwActionSet] ); } //----------------------------------------------------------------------------------------------------------------------------------------------------- // Purpose: Put the controller into a specific action set layer. Action sets layers apply modifications to an existing action set. //----------------------------------------------------------------------------------------------------------------------------------------------------- void CGameEngineGL::ActivateSteamControllerActionSetLayer( ECONTROLLERACTIONSET dwActionSetLayer ) { if ( m_ActiveControllerHandle == 0 ) return; // This call is low-overhead and can be called repeatedly from game code that is active in a specific mode. SteamInput()->ActivateActionSetLayer( m_ActiveControllerHandle, m_ControllerActionSetHandles[ dwActionSetLayer ] ); } //----------------------------------------------------------------------------------------------------------------------------------------------------- // Purpose: Deactivate an existing action set layer //----------------------------------------------------------------------------------------------------------------------------------------------------- void CGameEngineGL::DeactivateSteamControllerActionSetLayer( ECONTROLLERACTIONSET dwActionSetLayer ) { if ( m_ActiveControllerHandle == 0 ) return; // This call is low-overhead and can be called repeatedly from game code that is active in a specific mode. SteamInput()->DeactivateActionSetLayer( m_ActiveControllerHandle, m_ControllerActionSetHandles[ dwActionSetLayer ] ); } //----------------------------------------------------------------------------------------------------------------------------------------------------- // Purpose: Determine whether an action set layer is currently active //----------------------------------------------------------------------------------------------------------------------------------------------------- bool CGameEngineGL::BIsActionSetLayerActive( ECONTROLLERACTIONSET dwActionSetLayer ) { if ( m_ActiveControllerHandle == 0 ) return false; ControllerActionSetHandle_t pActionSetLayerHandles[ 32 ]; int nActiveLayerCount = SteamInput()->GetActiveActionSetLayers( m_ActiveControllerHandle, pActionSetLayerHandles ); for ( int i = 0; i < nActiveLayerCount; i++ ) { if ( pActionSetLayerHandles[ i ] == m_ControllerActionSetHandles[ dwActionSetLayer ] ) return true; } return false; }