//====== Copyright 1996-2006, Valve Corporation, All rights reserved. ======= // // Purpose: // //============================================================================= #if defined( BINK_VIDEO ) #if !defined( _GAMECONSOLE ) || defined( BINK_ENABLED_FOR_CONSOLE ) #include "avi/ibik.h" #if defined( _X360 ) # include "bink_x360/bink.h" #elif defined( _PS3 ) # include "bink_ps3/bink.h" #else # include "bink/bink.h" #endif #include "filesystem.h" #include "tier1/strtools.h" #include "tier1/utllinkedlist.h" #include "tier1/keyvalues.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/MaterialSystemUtil.h" #include "materialsystem/itexture.h" #include "callqueue.h" #include "vtf/vtf.h" #include "pixelwriter.h" #include "tier3/tier3.h" #if defined( _X360 ) #include "snd_dev_xaudio.h" #endif // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" #pragma warning( disable : 4201 ) #define WIN32_LEAN_AND_MEAN #pragma warning( default : 4201 ) //#define BINK_TRACK_71_ENABLED // Current movies are in 5.1 #if defined( PLATFORM_X360 ) // On 360, we specifically want a linear format so that we aren't swizzling on the CPU every frame. #define BINK_SHADER_IMAGE_FORMAT IMAGE_FORMAT_LINEAR_I8 #define BINK_NUMBER_OF_CHANNELS 6 // Up to 5.1 #elif defined( PLATFORM_PS3 ) #define BINK_SHADER_IMAGE_FORMAT IMAGE_FORMAT_I8 #define BINK_NUMBER_OF_CHANNELS 8 // Up to 7.1 #else #define BINK_SHADER_IMAGE_FORMAT IMAGE_FORMAT_I8 #define BINK_NUMBER_OF_CHANNELS 6 // Up to 5.1 #endif ConVar bink_mat_queue_mode( "bink_mat_queue_mode", "1", 0, "Update bink on mat queue thread if mat_queue_mode is on (if turned off, always update bink on main thread; may cause stalls!)" ); ConVar bink_try_load_vmt( "bink_try_load_vmt", "0", 0, "Try and load a VMT with the same name as the BIK file to override settings" ); ConVar bink_use_preallocated_scratch_texture( "bink_use_preallocated_scratch_texture", "1", 0, "Use a pre-allocated VTF instead of creating a new one and deleting it for every texture update. Gameconsole only." ); // don't set volume when using the wave out device. This will cause changes to the global volume // mixer and we can't tell the difference between waveOut setting those and a user doing that. // Also bink will set our volume to 25% instead of 100% in that case. static bool g_bDisableVolumeChanges = false; // We don't support the alpha channel in bink files due to dx8. Can make it work if necessary. //#define SUPPORT_BINK_ALPHA class CBIKMaterial; struct PrecachedMovie_t { CUtlString m_BaseName; CUtlBuffer m_MemoryBuffer; }; PathTypeFilter_t GetMoviePathFilter() { static ConVarRef force_audio_english( "force_audio_english" ); #if defined( _GAMECONSOLE ) if ( XBX_IsAudioLocalized() && force_audio_english.GetBool() ) { // skip the localized search paths and fall through return FILTER_CULLLOCALIZED_ANY; } #else if ( force_audio_english.GetBool() ) { // skip the localized search paths and fall through return FILTER_CULLLOCALIZED_ANY; } #endif // No movies exists inside of zips, all the movies are external return FILTER_CULLPACK; } class CBIKMaterialYTextureRegenerator : public ITextureRegenerator { public: void SetParentMaterial( CBIKMaterial *pBIKMaterial, int nWidth, int nHeight ) { m_pBIKMaterial = pBIKMaterial; m_nSourceWidth = nWidth; m_nSourceHeight = nHeight; } // Inherited from ITextureRegenerator virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ); virtual void Release(); virtual bool HasPreallocatedScratchTexture() const { return IsGameConsole() ? bink_use_preallocated_scratch_texture.GetBool() : false; } virtual IVTFTexture *GetPreallocatedScratchTexture(); private: CBIKMaterial *m_pBIKMaterial; int m_nSourceWidth; int m_nSourceHeight; }; #ifdef SUPPORT_BINK_ALPHA class CBIKMaterialATextureRegenerator : public ITextureRegenerator { public: void SetParentMaterial( CBIKMaterial *pBIKMaterial, int nWidth, int nHeight ) { m_pBIKMaterial = pBIKMaterial; m_nSourceWidth = nWidth; m_nSourceHeight = nHeight; } // Inherited from ITextureRegenerator virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ); virtual void Release(); virtual bool HasPreallocatedScratchTexture() const { return IsGameConsole() ? bink_use_preallocated_scratch_texture.GetBool() : false; } virtual IVTFTexture *GetPreallocatedScratchTexture(); private: CBIKMaterial *m_pBIKMaterial; int m_nSourceWidth; int m_nSourceHeight; }; #endif class CBIKMaterialCrTextureRegenerator : public ITextureRegenerator { public: void SetParentMaterial( CBIKMaterial *pBIKMaterial, int nWidth, int nHeight ) { m_pBIKMaterial = pBIKMaterial; m_nSourceWidth = nWidth; m_nSourceHeight = nHeight; } // Inherited from ITextureRegenerator virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ); virtual void Release(); virtual bool HasPreallocatedScratchTexture() const { return IsGameConsole() ? bink_use_preallocated_scratch_texture.GetBool() : false; } virtual IVTFTexture *GetPreallocatedScratchTexture(); private: CBIKMaterial *m_pBIKMaterial; int m_nSourceWidth; int m_nSourceHeight; }; class CBIKMaterialCbTextureRegenerator : public ITextureRegenerator { public: void SetParentMaterial( CBIKMaterial *pBIKMaterial, int nWidth, int nHeight ) { m_pBIKMaterial = pBIKMaterial; m_nSourceWidth = nWidth; m_nSourceHeight = nHeight; } // Inherited from ITextureRegenerator virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ); virtual void Release(); virtual bool HasPreallocatedScratchTexture() const { return IsGameConsole() ? bink_use_preallocated_scratch_texture.GetBool() : false; } virtual IVTFTexture *GetPreallocatedScratchTexture(); private: CBIKMaterial *m_pBIKMaterial; int m_nSourceWidth; int m_nSourceHeight; }; //----------------------------------------------------------------------------- // // Class used to associated BIK files with IMaterials // //----------------------------------------------------------------------------- class CBIKMaterial { public: CBIKMaterial(); // Initializes, shuts down the material bool Init( const char *pMaterialName, const char *pFileName, const char *pPathID, int flags ); bool Shutdown(); // Keeps the frames updated // Work is actually done by UpdateInternal, either on the main thread or on the mat queue thread bool Update( void ); // Call this in a loop that does nothing or something minor right before calling SwapBuffers. bool ReadyForSwap(); // Returns the material IMaterial *GetMaterial(); // Returns the texcoord range void GetTexCoordRange( float *pMaxU, float *pMaxV ); // Returns the frame size of the BIK (stored in a subrect of the material itself) void GetFrameSize( int *pWidth, int *pHeight ); // Returns the frame rate/count of the BIK int GetFrame( void ); int GetFrameRate( void ); int GetFrameCount( void ); // Sets the frame for an BIK material (use instead of SetTime) void SetFrame( float flFrame ); void SetLooping( bool bLoops = true ) { m_bLoops = bLoops; } void Pause( void ); void Unpause( void ); // Access cached frame information (takes a mutex; safe to call any time, but may be a frame delayed) bool IsVideoFinished(); IVTFTexture * GetScratchVTFTexture() { return m_pScratchTexture; } void UpdateVolume(); bool IsMovieResidentInMemory(); private: friend class CBIKMaterialYTextureRegenerator; #ifdef SUPPORT_BINK_ALPHA friend class CBIKMaterialATextureRegenerator; #endif friend class CBIKMaterialCrTextureRegenerator; friend class CBIKMaterialCbTextureRegenerator; // Initializes, shuts down the procedural texture void CreateProceduralTextures( const char *pTextureName ); void DestroyProceduralTexture( CTextureReference &texture ); void DestroyProceduralTextures(); // Initializes, shuts down the procedural material void CreateProceduralMaterial( const char *pMaterialName ); void DestroyProceduralMaterial(); // Initializes, shuts down the video stream void CreateVideoStream( ); void DestroyVideoStream( ); // Performs the actual bink texture update, either on the mat queue thread or on the main thread void UpdateInternal(); void UpdateCurrentFrameIndex(); // Performs the actual bink frame set operation void SetFrameInternal( int nFrame ); void SetTracks(); CMaterialReference m_Material; CTextureReference m_TextureY; #ifdef SUPPORT_BINK_ALPHA CTextureReference m_TextureA; #endif CTextureReference m_TextureCr; CTextureReference m_TextureCb; HBINK m_pHBINK; BINKFRAMEBUFFERS m_buffers; int m_nBinkFlags; int m_nBIKWidth; int m_nBIKHeight; int m_nFrameRate; int m_nFrameCount; int m_nCurrentFrame; bool m_bLoops; bool m_bShutdown; CBIKMaterialYTextureRegenerator m_YTextureRegenerator; #ifdef SUPPORT_BINK_ALPHA CBIKMaterialATextureRegenerator m_ATextureRegenerator; #endif CBIKMaterialCrTextureRegenerator m_CrTextureRegenerator; CBIKMaterialCbTextureRegenerator m_CbTextureRegenerator; // CTexture::Download() will make a temp copy in a scratch texture every time we update the Bink textures, // so use this instead of re-allocating the scratch texture on every update (4 times per frame per bink movie) IVTFTexture *m_pScratchTexture; // Since bink can be updated on the mat queue thread, we need to protect internal class state CThreadFastMutex m_BinkUpdateMutex; CThreadFastMutex m_BinkFrameCountMutex; // The number of outstanding calls to UpdateInternal (gets incremented on Update() and decremented when UpdateInternal() is finished int m_nQueuedUpdateCount; #if IsPlatformPS3() // Indicates if we have to resume the prefetches, and if yes, when. enum PrefetchResumeMode_t { PRM_NO_RESUME_NECESSARY, // Used if the movie is in memory PRM_RESUME_AT_END_OF_FIRST_FRAME, // Used if the movie is preloaded PRM_RESUME_AT_END_OF_MOVIE // Used if the movie is streamed }; PrefetchResumeMode_t m_ResumeMode; #endif }; //----------------------------------------------------------------------------- // Inherited from ITextureRegenerator //----------------------------------------------------------------------------- void CBIKMaterialYTextureRegenerator::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) { int nWidth = m_nSourceWidth; int nHeight = m_nSourceHeight; unsigned char *pYData = NULL; int nBytes = 0; int y; CPixelWriter pixelWriter; int nBufferPitch = 0; // Error condition if ( (pVTFTexture->FrameCount() > 1) || (pVTFTexture->FaceCount() > 1) || (pVTFTexture->MipCount() > 1) || (pVTFTexture->Depth() > 1) ) { goto BIKMaterialError; } pYData = (unsigned char *)m_pBIKMaterial->m_buffers.Frames[ m_pBIKMaterial->m_buffers.FrameNum ].YPlane.Buffer; nBufferPitch = m_pBIKMaterial->m_buffers.Frames[ m_pBIKMaterial->m_buffers.FrameNum ].YPlane.BufferPitch; Assert( pVTFTexture->Format() == BINK_SHADER_IMAGE_FORMAT ); Assert( pVTFTexture->RowSizeInBytes( 0 ) == pVTFTexture->Width() ); Assert( pVTFTexture->Width() >= m_nSourceWidth ); Assert( pVTFTexture->Height() >= m_nSourceHeight ); // Set up the pixel writer to write into the VTF texture pixelWriter.SetPixelMemory( pVTFTexture->Format(), pVTFTexture->ImageData(), pVTFTexture->RowSizeInBytes( 0 ) ); for ( y = 0; y < nHeight; ++y ) { pixelWriter.Seek( 0, y ); memcpy( pixelWriter.GetCurrentPixel(), pYData, nWidth ); pYData += nBufferPitch; } return; BIKMaterialError: nBytes = pVTFTexture->ComputeTotalSize(); memset( pVTFTexture->ImageData(), 0xFF, nBytes ); return; } IVTFTexture *CBIKMaterialYTextureRegenerator::GetPreallocatedScratchTexture() { return m_pBIKMaterial->GetScratchVTFTexture(); } void CBIKMaterialYTextureRegenerator::Release() { } #ifdef SUPPORT_BINK_ALPHA //----------------------------------------------------------------------------- // Inherited from ITextureRegenerator //----------------------------------------------------------------------------- void CBIKMaterialATextureRegenerator::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) { CPixelWriter pixelWriter; int nBufferPitch = 0; // Error condition if ( (pVTFTexture->FrameCount() > 1) || (pVTFTexture->FaceCount() > 1) || (pVTFTexture->MipCount() > 1) || (pVTFTexture->Depth() > 1) ) { goto BIKMaterialError; } unsigned char *pAData = (unsigned char *)m_pBIKMaterial->m_buffers.Frames[ m_pBIKMaterial->m_buffers.FrameNum ].APlane.Buffer; nBufferPitch = m_pBIKMaterial->m_buffers.Frames[ m_pBIKMaterial->m_buffers.FrameNum ].APlane.BufferPitch; Assert( pVTFTexture->Format() == BINK_SHADER_IMAGE_FORMAT ); Assert( pVTFTexture->RowSizeInBytes( 0 ) == pVTFTexture->Width() ); Assert( pVTFTexture->Width() >= m_nSourceWidth ); Assert( pVTFTexture->Height() >= m_nSourceHeight ); // Set up the pixel writer to write into the VTF texture pixelWriter.SetPixelMemory( pVTFTexture->Format(), pVTFTexture->ImageData(), pVTFTexture->RowSizeInBytes( 0 ) ); int nWidth = m_nSourceWidth; int nHeight = m_nSourceHeight; int y; if( pAData ) { for ( y = 0; y < nHeight; ++y ) { pixelWriter.Seek( 0, y ); memcpy( pixelWriter.GetCurrentPixel(), pAData, nWidth ); pAData += nBufferPitch; } } else { for ( y = 0; y < nHeight; ++y ) { pixelWriter.Seek( 0, y ); memset( pixelWriter.GetCurrentPixel(), 255, nWidth ); } } return; BIKMaterialError: int nBytes = pVTFTexture->ComputeTotalSize(); memset( pVTFTexture->ImageData(), 0xFF, nBytes ); return; } IVTFTexture *CBIKMaterialATextureRegenerator::GetPreallocatedScratchTexture() { return m_pBIKMaterial->GetScratchVTFTexture(); } void CBIKMaterialATextureRegenerator::Release() { } #endif //----------------------------------------------------------------------------- // Inherited from ITextureRegenerator //----------------------------------------------------------------------------- void CBIKMaterialCrTextureRegenerator::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) { int nWidth = m_nSourceWidth; int nHeight = m_nSourceHeight; unsigned char *pCrData = NULL; CPixelWriter pixelWriter; int nBufferPitch = 0; // Error condition if ( (pVTFTexture->FrameCount() > 1) || (pVTFTexture->FaceCount() > 1) || (pVTFTexture->MipCount() > 1) || (pVTFTexture->Depth() > 1) ) { goto BIKMaterialError; } pCrData = (unsigned char *)m_pBIKMaterial->m_buffers.Frames[ m_pBIKMaterial->m_buffers.FrameNum ].cRPlane.Buffer; nBufferPitch = m_pBIKMaterial->m_buffers.Frames[ m_pBIKMaterial->m_buffers.FrameNum ].cRPlane.BufferPitch; Assert( pVTFTexture->Format() == BINK_SHADER_IMAGE_FORMAT ); Assert( pVTFTexture->RowSizeInBytes( 0 ) == pVTFTexture->Width() ); Assert( pVTFTexture->Width() >= m_nSourceWidth ); Assert( pVTFTexture->Height() >= m_nSourceHeight ); // Set up the pixel writer to write into the VTF texture pixelWriter.SetPixelMemory( pVTFTexture->Format(), pVTFTexture->ImageData(), pVTFTexture->RowSizeInBytes( 0 ) ); int y; for ( y = 0; y < nHeight; ++y ) { pixelWriter.Seek( 0, y ); memcpy( pixelWriter.GetCurrentPixel(), pCrData, nWidth ); pCrData += nBufferPitch; } return; BIKMaterialError: int nBytes = pVTFTexture->ComputeTotalSize(); memset( pVTFTexture->ImageData(), 0xFF, nBytes ); return; } IVTFTexture *CBIKMaterialCrTextureRegenerator::GetPreallocatedScratchTexture() { return m_pBIKMaterial->GetScratchVTFTexture(); } void CBIKMaterialCrTextureRegenerator::Release() { } //----------------------------------------------------------------------------- // Inherited from ITextureRegenerator //----------------------------------------------------------------------------- void CBIKMaterialCbTextureRegenerator::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) { int nWidth = m_nSourceWidth; int nHeight = m_nSourceHeight; unsigned char *pCbData = NULL; CPixelWriter pixelWriter; int nBufferPitch = 0; // Error condition if ( (pVTFTexture->FrameCount() > 1) || (pVTFTexture->FaceCount() > 1) || (pVTFTexture->MipCount() > 1) || (pVTFTexture->Depth() > 1) ) { goto BIKMaterialError; } pCbData = (unsigned char *)m_pBIKMaterial->m_buffers.Frames[ m_pBIKMaterial->m_buffers.FrameNum ].cBPlane.Buffer; nBufferPitch = m_pBIKMaterial->m_buffers.Frames[ m_pBIKMaterial->m_buffers.FrameNum ].cBPlane.BufferPitch; Assert( pVTFTexture->Format() == BINK_SHADER_IMAGE_FORMAT ); Assert( pVTFTexture->RowSizeInBytes( 0 ) == pVTFTexture->Width() ); Assert( pVTFTexture->Width() >= m_nSourceWidth ); Assert( pVTFTexture->Height() >= m_nSourceHeight ); // Set up the pixel writer to write into the VTF texture pixelWriter.SetPixelMemory( pVTFTexture->Format(), pVTFTexture->ImageData(), pVTFTexture->RowSizeInBytes( 0 ) ); int y; for ( y = 0; y < nHeight; ++y ) { pixelWriter.Seek( 0, y ); memcpy( pixelWriter.GetCurrentPixel(), pCbData, nWidth ); pCbData += nBufferPitch; } return; BIKMaterialError: int nBytes = pVTFTexture->ComputeTotalSize(); memset( pVTFTexture->ImageData(), 0xFF, nBytes ); return; } IVTFTexture *CBIKMaterialCbTextureRegenerator::GetPreallocatedScratchTexture() { return m_pBIKMaterial->GetScratchVTFTexture(); } void CBIKMaterialCbTextureRegenerator::Release() { } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CBIKMaterial::CBIKMaterial() { m_pHBINK = NULL; Q_memset( &m_buffers, 0, sizeof( m_buffers ) ); m_bLoops = false; m_nQueuedUpdateCount = 0; m_bShutdown = false; #if IsPlatformPS3() m_ResumeMode = PRM_NO_RESUME_NECESSARY; #endif m_pScratchTexture = NULL; m_nBinkFlags = 0; } //----------------------------------------------------------------------------- // Initializes the material //----------------------------------------------------------------------------- bool CBIKMaterial::Init( const char *pMaterialName, const char *pFileName, const char *pPathID, int flags ) { // Determine the full path name of the BIK char pBIKFileName[ 512 ]; char pFullBIKFileName[ 512 ]; Q_snprintf( pBIKFileName, sizeof( pBIKFileName ), "%s", pFileName ); Q_DefaultExtension( pBIKFileName, ".bik", sizeof( pBIKFileName ) ); PathTypeQuery_t pathType; if ( !g_pFullFileSystem->RelativePathToFullPath( pBIKFileName, pPathID, pFullBIKFileName, sizeof( pFullBIKFileName ), GetMoviePathFilter(), &pathType ) ) { // A file by that name was not found Assert( 0 ); return false; } //Msg( "BinkOpen( %s )\n", pFullBIKFileName ); U32 binkFlags = BINKNOFRAMEBUFFERS | BINKSNDTRACK; if ( flags & BIK_PRELOAD ) { binkFlags |= BINKPRELOADALL; } if ( ( flags & BIK_NO_AUDIO ) != 0 ) { // No audio BinkSetSoundTrack( 0, 0 ); } else { #if BINK_NUMBER_OF_CHANNELS == 8 // Settings for 7.1 // Track ID 0 - A stereo track containing the front left and front right channels. // Track ID 1 - A mono track containing the center channel. // Track ID 2 - A mono track containing the sub-woofer channel. // Track ID 3 - A stereo track containing the back left and back right channels. // Track ID 4 - A stereo track containing the side left and side right channels. U32 TrackIDsToPlay[ 5 ] = { 0, 1, 2, 3, 4 }; BinkSetSoundTrack( 5, TrackIDsToPlay ); #elif defined(OSX) U32 TrackIDsToPlay[ 2 ] = { 0, 1 }; BinkSetSoundTrack( 2, TrackIDsToPlay ); #else // Setting 8 channels may not seem to make the X360 implementation of Bink happy. Use 4 tracks for the 6 channels. U32 TrackIDsToPlay[ 4 ] = { 0, 1, 2, 3 }; BinkSetSoundTrack( 4, TrackIDsToPlay ); #endif } // perhaps already in memory void *pBinkInMemory = g_pBIK->GetPrecachedMovie( pFullBIKFileName ); if ( pBinkInMemory ) { binkFlags &= ~BINKPRELOADALL; binkFlags |= BINKFROMMEMORY; } #if IsPlatformPS3() if ( ( binkFlags & BINKFROMMEMORY ) != 0 ) { m_ResumeMode = PRM_NO_RESUME_NECESSARY; // No issue with prefetching in this case } else if ( ( binkFlags & BINKPRELOADALL ) != 0 ) { g_pFullFileSystem->SuspendPrefetches( "Bink movie is going to be preloaded." ); // At the end of the first frame, the movie should be loaded m_ResumeMode = PRM_RESUME_AT_END_OF_FIRST_FRAME; // prefetches can restart after that. So we can continue prefetching during the movie. } else { // This is the streamed mode g_pFullFileSystem->SuspendPrefetches( "Bink movie is going to be streamed."); // As it is streamed, suspend all prefetches until the end m_ResumeMode = PRM_RESUME_AT_END_OF_MOVIE; // to avoid stuttering while playing the movie } #endif m_nBinkFlags = binkFlags; m_pHBINK = BinkOpen( pBinkInMemory ? (const char *)pBinkInMemory : pFullBIKFileName, binkFlags ); if ( !m_pHBINK ) { // The file was unable to be opened Assert( 0 ); m_nBIKWidth = 64; m_nBIKHeight = 64; m_nFrameRate = 1; m_nFrameCount = 1; m_Material.Init( "debug/debugempty", TEXTURE_GROUP_OTHER ); return false; } SetTracks(); // Get BIK size m_nBIKWidth = m_pHBINK->Width; m_nBIKHeight = m_pHBINK->Height; m_nFrameRate = (int)( (float)m_pHBINK->FrameRate / (float)m_pHBINK->FrameRateDiv ); m_nFrameCount = m_pHBINK->Frames; m_nCurrentFrame = 0; CreateVideoStream(); // Now we can properly setup out regenerators m_YTextureRegenerator.SetParentMaterial( this, m_nBIKWidth, m_nBIKHeight ); #ifdef SUPPORT_BINK_ALPHA m_ATextureRegenerator.SetParentMaterial( this, m_nBIKWidth, m_nBIKHeight); #endif // The Cr and Cb display textures are always HALF the res of the Y/A textures. // However, the bink framebuffers which get decompressed have their own alignment requirements that may cause them to // be slightly larger. In such a case, we only use this smaller sub-rect (the rest may be invalid) m_CrTextureRegenerator.SetParentMaterial( this, m_nBIKWidth >> 1, m_nBIKHeight >> 1 ); m_CbTextureRegenerator.SetParentMaterial( this, m_nBIKWidth >> 1, m_nBIKHeight >> 1 ); CreateProceduralTextures( pMaterialName ); CreateProceduralMaterial( pMaterialName ); SetLooping( ( flags & BIK_LOOP ) != 0 ); UpdateVolume(); return true; } #if PLATFORM_X360 // After trying without success to have a the same setup between PC, PS3 and X360, // I ended up creating specific version for each to make Bink work in all cases. // Talking with Bink support, they said that the model to follow should be the X360 model. // I.e. each call is listing the number of channels in the track, instead of listing all channels. // I did not test it though, and it still seems to be a flaw in their API. // The other model (X360 following the PS3 model mixed with X360) does not work. void CBIKMaterial::SetTracks() { U32 bins[ 2 ]; // front LR bins[ 0 ] = 0; // 0 is front left on XAudio bins[ 1 ] = 1; // 1 is front right on XAudio BinkSetMixBins( m_pHBINK, 0, bins, 2 ); // center bins [ 0 ] = 2; // 2 is center on XAudio BinkSetMixBins( m_pHBINK, 1, bins, 1 ); // sub bins [ 0 ] = 3; // 3 is sub on XAudio BinkSetMixBins( m_pHBINK, 2, bins, 1 ); // back LR bins[ 0 ] = 4; // 4 is back left on XAudio bins[ 1 ] = 5; // 5 is back right on XAudio BinkSetMixBins( m_pHBINK, 3, bins, 2 ); } #elif defined(PLATFORM_PS3) void CBIKMaterial::SetTracks() { int nMasterVolume = 32768; S32 nVolumes[ BINK_NUMBER_OF_CHANNELS ]; // Up to 8 tracks for 7.1 // front LR memset( nVolumes, 0, sizeof( nVolumes ) ); nVolumes[ 0 ] = nMasterVolume; nVolumes[ 1 ] = nMasterVolume; BinkSetMixBinVolumes( m_pHBINK, 0, NULL, nVolumes, BINK_NUMBER_OF_CHANNELS ); // center memset( nVolumes, 0, sizeof( nVolumes ) ); nVolumes[ 2 ] = nMasterVolume; BinkSetMixBinVolumes( m_pHBINK, 1, NULL, nVolumes, BINK_NUMBER_OF_CHANNELS ); // sub memset( nVolumes, 0, sizeof( nVolumes ) ); nVolumes[ 3 ] = nMasterVolume; BinkSetMixBinVolumes( m_pHBINK, 2, NULL, nVolumes, BINK_NUMBER_OF_CHANNELS ); #if BINK_TRACK_71_ENABLED // This is not enabled, we only play 5.1 right now (on Portal 2). // back LR memset( nVolumes, 0, sizeof( nVolumes ) ); nVolumes[ 4 ] = nMasterVolume; nVolumes[ 5 ] = nMasterVolume; BinkSetMixBinVolumes( m_pHBINK, 3, NULL, nVolumes, BINK_NUMBER_OF_CHANNELS ); #if ( BINK_NUMBER_OF_CHANNELS == 8 ) // side LR memset( nVolumes, 0, sizeof( nVolumes ) ); nVolumes[ 6 ] = nMasterVolume; nVolumes[ 7 ] = nMasterVolume; BinkSetMixBinVolumes( m_pHBINK, 4, NULL, nVolumes, BINK_NUMBER_OF_CHANNELS ); #endif #else // route rear bink track to both back and side speakers memset( nVolumes, 0, sizeof( nVolumes ) ); nVolumes[ 4 ] = nMasterVolume; nVolumes[ 5 ] = nMasterVolume; // If we are in 7.1, we want to duplicate the side speakers to the speakers. snd_ps3_back_channel_multiplier will be equal to 1.0f. // If we are in 5.1, we may not want to do that to create issues with the downmixer. snd_ps3_back_channel_multiplier will be equal to 0.0f. extern ConVar snd_ps3_back_channel_multiplier; nMasterVolume = ( int )( ( ( float ) nMasterVolume ) * snd_ps3_back_channel_multiplier.GetFloat() ); #if ( BINK_NUMBER_OF_CHANNELS == 8 ) nVolumes[ 6 ] = nMasterVolume; nVolumes[ 7 ] = nMasterVolume; #endif BinkSetMixBinVolumes( m_pHBINK, 3, NULL, nVolumes, BINK_NUMBER_OF_CHANNELS ); #endif } #elif defined(PLATFORM_WINDOWS) void CBIKMaterial::SetTracks() { S32 volumes[ 6 ]; // 6 channels for 5.1 U32 bins[ 6 ]; // turn on the front left and right for the first Bink track memset( volumes, 0, sizeof( volumes ) ); memset( bins, 0, sizeof( bins ) ); volumes[ 0 ] = 32768; volumes[ 1 ] = 32768; bins[ 0 ] = 0; bins[ 1 ] = 1; BinkSetMixBinVolumes( m_pHBINK, 0, bins, volumes, 6 ); // turn on the center for the second Bink track memset( volumes, 0, sizeof( volumes ) ); memset( bins, 0, sizeof( bins ) ); volumes[ 2 ] = 32768; bins[ 2 ] = 2; BinkSetMixBinVolumes( m_pHBINK, 1, bins, volumes, 6 ); // turn on the sub woofer for the third Bink track memset( volumes, 0, sizeof( volumes ) ); memset( bins, 0, sizeof( bins ) ); volumes[ 3 ] = 32768; bins[ 3 ] = 3; BinkSetMixBinVolumes( m_pHBINK, 2, bins, volumes, 6 ); // turn on the back left and right for the final Bink track memset( volumes, 0, sizeof( volumes ) ); memset( bins, 0, sizeof( bins ) ); volumes[ 4 ] = 32768; volumes[ 5 ] = 32768; bins[ 4 ] = 4; bins[ 5 ] = 5; BinkSetMixBinVolumes( m_pHBINK, 3, bins, volumes, 6 ); } #elif defined(OSX) void CBIKMaterial::SetTracks() { S32 volumes[ 3 ]; // 2 channels for stero + mix in the center channel to left and right U32 bins[ 3 ]; // turn on the front left and right for the first Bink track memset( volumes, 0, sizeof( volumes ) ); memset( bins, 0, sizeof( bins ) ); volumes[ 0 ] = 32768; volumes[ 1 ] = 32768; bins[ 0 ] = 0; bins[ 1 ] = 1; BinkSetMixBinVolumes( m_pHBINK, 0, bins, volumes, 3 ); // turn on the center for the second Bink track memset( volumes, 0, sizeof( volumes ) ); memset( bins, 0, sizeof( bins ) ); volumes[ 0 ] = 16535; volumes[ 1 ] = 16535; bins[ 2 ] = 2; BinkSetMixBinVolumes( m_pHBINK, 1, bins, volumes, 3 ); } #else void CBIKMaterial::SetTracks() { Assert( !"Need some code here please" ); // Do nothing... Mac does not use Bink. } #endif void CBIKMaterial::UpdateVolume() { if ( !m_pHBINK || g_bDisableVolumeChanges ) return; if ( !( m_nBinkFlags & BIK_NO_AUDIO ) ) { // set the master volume static ConVarRef volumeConVar( "volume" ); static ConVarRef movieVolumeScaleConVar( "movie_volume_scale" ); float flVolume = volumeConVar.GetFloat() * movieVolumeScaleConVar.GetFloat() * 32768.0f; static ConVarRef snd_surroundSpeakersConVarRef( "snd_surround_speakers" ); switch ( snd_surroundSpeakersConVarRef.GetInt() ) { default: // 5.1 or 7.1 case -1: // Not initialized yet, keep the same value // Keep it the way it is... break; case 2: // The output is in stereo, but the movie is 5.1 // We reduce the volume so when downmixed we have the correct overall volume. #if defined( PLATFORM_PS3 ) // This value is coming from this formula: // 8 (7.1)ch -> 2ch [CELL_AUDIO_OUT_DOWNMIXER_TYPE_A] // L(mix) = 0.707 x L + 0.5 x C + 0.5 x Ls + 0.5 x Le // R(mix) = 0.707 x R + 0.5 x C + 0.5 x Rs + 0.5 x Re // In this case Le and Re are 0.0, so the multiplying factor is 1.707 // Thus we have to multiply it by 0.58585858 to re-normalize the volume. // flVolume *= 0.58585858f; // However after empirical testing on Portal 2, this value sounds better: flVolume *= 0.781144f; #elif defined( PLATFORM_X360 ) // Similar values for X360. // (potentially the downmixing on X360 is adding 0.25 of the center and 0.25 of the back surround) flVolume *= 0.66f; #endif break; } S32 nVolume = (S32)( flVolume ); for ( int i = 0; i != m_pHBINK->NumTracks; ++i ) { BinkSetVolume( m_pHBINK, BinkGetTrackID( m_pHBINK, i ), nVolume ); } } } bool CBIKMaterial::IsMovieResidentInMemory() { if ( !m_pHBINK ) return false; return ( ( m_nBinkFlags & ( BINKPRELOADALL | BINKFROMMEMORY ) ) != 0 ); } static void ShutdownAndDeleteBinkMaterial( CBIKMaterial *pBIK ) { if ( !pBIK->Shutdown() ) { // WILL CRASH HERE DebuggerBreakIfDebugging(); } delete pBIK; } bool CBIKMaterial::Shutdown( void ) { m_bShutdown = true; AUTO_LOCK( m_BinkUpdateMutex ); if ( m_nQueuedUpdateCount != 0 ) { CMatRenderContextPtr pRenderContext( materials ); if ( pRenderContext->GetCallQueue() ) { pRenderContext->GetCallQueue()->QueueCall( &ShutdownAndDeleteBinkMaterial, this ); return false; } } // If this isn't 0, then this zombie object is going to get called by the mat queue thread... badness! Assert( m_nQueuedUpdateCount == 0 ); DestroyVideoStream(); DestroyProceduralMaterial(); DestroyProceduralTextures(); if ( m_pHBINK ) { if ( ENABLE_BIK_PERF_SPEW ) { BINKSUMMARY summary; BinkGetSummary( m_pHBINK, &summary ); Warning( "BINK PERF:\n" ); Warning( "--------------------------\n" ); Warning( "Height of frames: %u\n", summary.Height ); Warning( "total time (ms): %u\n", summary.TotalTime ); Warning( "file frame rate: %f\n", ( float )summary.FileFrameRate / ( float )summary.FileFrameRateDiv ); Warning( "file frame rate: %u\n", summary.FileFrameRate ); Warning( "file frame rate divisor: %u\n", summary.FileFrameRateDiv ); Warning( "frame rate: %f\n", ( float )summary.FrameRate / ( float )summary.FrameRateDiv ); Warning( "frame rate: %u\n", summary.FrameRate ); Warning( "frame rate divisor: %u\n", summary.FrameRateDiv ); Warning( "Time to open and prepare for decompression: %u\n", summary.TotalOpenTime ); Warning( "Total Frames: %u\n", summary.TotalFrames ); Warning( "Total Frames played: %u\n", summary.TotalPlayedFrames ); Warning( "Total number of skipped frames: %u\n", summary.SkippedFrames ); Warning( "Total number of skipped blits: %u\n", summary.SkippedBlits ); Warning( "Total number of sound skips: %u\n", summary.SoundSkips ); Warning( "Total time spent blitting: %u\n", summary.TotalBlitTime ); Warning( "Total time spent reading: %u\n", summary.TotalReadTime ); Warning( "Total time spent decompressing video: %u\n", summary.TotalVideoDecompTime ); Warning( "Total time spent decompressing audio: %u\n", summary.TotalAudioDecompTime ); Warning( "Total time spent reading while idle: %u\n", summary.TotalIdleReadTime ); Warning( "Total time spent reading in background: %u\n", summary.TotalBackReadTime ); Warning( "Total io speed (bytes/second): %u\n", summary.TotalReadSpeed ); Warning( "Slowest single frame time (ms): %u\n", summary.SlowestFrameTime ); Warning( "Second slowest single frame time (ms): %u\n", summary.Slowest2FrameTime ); Warning( "Slowest single frame number: %u\n", summary.SlowestFrameNum ); Warning( "Second slowest single frame number: %u\n", summary.Slowest2FrameNum ); Warning( "Average data rate of the movie: %u\n", summary.AverageDataRate ); Warning( "Average size of the frame: %u\n", summary.AverageFrameSize ); Warning( "Highest amount of memory allocated: %u\n", summary.HighestMemAmount ); Warning( "Total extra memory allocated: %u\n", summary.TotalIOMemory ); Warning( "Highest extra memory actually used: %u\n", summary.HighestIOUsed ); Warning( "Highest 1 second rate: %u\n", summary.Highest1SecRate ); Warning( "Highest 1 second start frame: %u\n", summary.Highest1SecFrame ); Warning( "--------------------------\n" ); } BinkClose( m_pHBINK ); m_pHBINK = NULL; } #if IsPlatformPS3() switch ( m_ResumeMode ) { case PRM_NO_RESUME_NECESSARY: break; case PRM_RESUME_AT_END_OF_FIRST_FRAME: // This should not happen, as hopefully one frame at least occurred, but just in case let's do what is expected Assert( false ); // Pass through... case PRM_RESUME_AT_END_OF_MOVIE: g_pFullFileSystem->ResumePrefetches( "Streaming of Bink movie is finished." ); m_ResumeMode = PRM_NO_RESUME_NECESSARY; // Reset the state just in case. break; } #endif return true; } bool CBIKMaterial::IsVideoFinished() { AUTO_LOCK( m_BinkFrameCountMutex ); return m_bShutdown || ( ( m_nCurrentFrame == m_nFrameCount ) && ( m_bLoops == false ) ); } // Unless you like to Dine with Philosophers, // I don't recommend calling this function unless you already have the m_BinkUpdateMutex void CBIKMaterial::UpdateCurrentFrameIndex() { AUTO_LOCK( m_BinkUpdateMutex ); // If you already have this mutex, this is re-entrant safe and will early-out AUTO_LOCK( m_BinkFrameCountMutex ); m_nCurrentFrame = m_pHBINK->FrameNum; } //----------------------------------------------------------------------------- // Purpose: Updates our scene // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBIKMaterial::Update( void ) { // is the video done? if ( IsVideoFinished() ) return false; // This gets decremented by UpdateInternal(), either right now (if not queueing) or when the work is done (if queueing) ThreadInterlockedIncrement( &m_nQueuedUpdateCount ); CMatRenderContextPtr pRenderContext( materials ); if ( pRenderContext->GetCallQueue() && bink_mat_queue_mode.GetBool() ) { pRenderContext->GetCallQueue()->QueueCall( this, &CBIKMaterial::UpdateInternal ); } else { UpdateInternal(); if ( IsVideoFinished() ) return false; } // When using mat_queue_mode with bink movies, the return value 'false' (which indicates that the movie is over) will be delayed by a frame return true; } void CBIKMaterial::UpdateInternal() { // If you crash in this function when called from the mat queue thread and the member variables appear to be junk, // it is likely that this bink material has been shutdown/deleted already. // That would be bad, because the call queue needs to be flushed before you can safely destroy this object. AUTO_LOCK( m_BinkUpdateMutex ); ThreadInterlockedDecrement( &m_nQueuedUpdateCount ); // If we're waiting, then go away if ( BinkWait( m_pHBINK ) ) return; // Decompress this frame BinkDoFrame( m_pHBINK ); // do we need to skip a frame? while ( BinkShouldSkip( m_pHBINK ) ) { UpdateCurrentFrameIndex(); // is the video done? if ( IsVideoFinished() ) break; BinkNextFrame( m_pHBINK ); BinkDoFrame( m_pHBINK ); } // Regenerate our textures m_TextureY->Download(); #ifdef SUPPORT_BINK_ALPHA m_TextureA->Download(); #endif m_TextureCr->Download(); m_TextureCb->Download(); UpdateCurrentFrameIndex(); // is the video done? if ( IsVideoFinished() ) return; // Move on BinkNextFrame( m_pHBINK ); UpdateCurrentFrameIndex(); #if IsPlatformPS3() if ( m_ResumeMode == PRM_RESUME_AT_END_OF_FIRST_FRAME ) { g_pFullFileSystem->ResumePrefetches( "Bink movie has been preloaded." ); m_ResumeMode = PRM_NO_RESUME_NECESSARY; } #endif } // Call this in a loop that does nothing or something minor right before calling SwapBuffers. bool CBIKMaterial::ReadyForSwap( void ) { AUTO_LOCK( m_BinkUpdateMutex ); return !BinkWait( m_pHBINK ); } //----------------------------------------------------------------------------- // Returns the material //----------------------------------------------------------------------------- IMaterial *CBIKMaterial::GetMaterial() { return m_Material; } //----------------------------------------------------------------------------- // Returns the texcoord range //----------------------------------------------------------------------------- void CBIKMaterial::GetTexCoordRange( float *pMaxU, float *pMaxV ) { AUTO_LOCK( m_BinkUpdateMutex ); // Must have a luminosity channel if ( m_TextureY == NULL ) { *pMaxU = *pMaxV = 1.0f; return; } // YA texture is always larger than the CrCb texture, so always base our size on that int nTextureWidth = m_TextureY->GetActualWidth(); int nTextureHeight = m_TextureY->GetActualHeight(); if ( nTextureWidth ) *pMaxU = (float)m_nBIKWidth / (float)nTextureWidth; else *pMaxU = 0.0f; if ( nTextureHeight ) *pMaxV = (float)m_nBIKHeight / (float)nTextureHeight; else *pMaxV = 0.0f; } //----------------------------------------------------------------------------- // Returns the frame size of the BIK (stored in a subrect of the material itself) //----------------------------------------------------------------------------- void CBIKMaterial::GetFrameSize( int *pWidth, int *pHeight ) { *pWidth = m_nBIKWidth; *pHeight = m_nBIKHeight; } //----------------------------------------------------------------------------- // Initializes, shuts down the procedural texture //----------------------------------------------------------------------------- void CBIKMaterial::CreateProceduralTextures( const char *pTextureName ) { int nWidth, nHeight; char textureName[MAX_PATH]; Q_strncpy( textureName, pTextureName, MAX_PATH-1 ); Q_StripExtension( textureName, textureName, sizeof( textureName ) ); Q_strncat( textureName, "Y", MAX_PATH ); unsigned int nTextureFlags = ( TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_NOLOD ); nWidth = m_nBIKWidth; nHeight = m_nBIKHeight; m_TextureY.InitProceduralTexture( textureName, "bik", nWidth, nHeight, BINK_SHADER_IMAGE_FORMAT, nTextureFlags ); m_TextureY->SetTextureRegenerator( &m_YTextureRegenerator ); #ifdef SUPPORT_BINK_ALPHA Q_strncpy( textureName, pTextureName, MAX_PATH-1 ); Q_StripExtension( textureName, textureName, sizeof( textureName ) ); Q_strncat( textureName, "A", MAX_PATH ); m_TextureA.InitProceduralTexture( textureName, "bik", nWidth, nHeight, BINK_SHADER_IMAGE_FORMAT, nTextureFlags ); m_TextureA->SetTextureRegenerator( &m_ATextureRegenerator ); #endif Q_strncpy( textureName, pTextureName, MAX_PATH-1 ); Q_StripExtension( textureName, textureName, sizeof( textureName ) ); Q_strncat( textureName, "Cr", MAX_PATH ); nWidth = m_nBIKWidth >> 1; nHeight = m_nBIKHeight >> 1; m_TextureCr.InitProceduralTexture( textureName, "bik", nWidth, nHeight, BINK_SHADER_IMAGE_FORMAT, nTextureFlags ); m_TextureCr->SetTextureRegenerator( &m_CrTextureRegenerator ); Q_strncpy( textureName, pTextureName, MAX_PATH-1 ); Q_StripExtension( textureName, textureName, sizeof( textureName ) ); Q_strncat( textureName, "Cb", MAX_PATH ); m_TextureCb.InitProceduralTexture( textureName, "bik", nWidth, nHeight, BINK_SHADER_IMAGE_FORMAT, nTextureFlags ); m_TextureCb->SetTextureRegenerator( &m_CbTextureRegenerator ); // This pulls in way too many lib dependencies on the PC; on consoles it's in engine.dll // It's also just a perf improvement and not as useful on PC. #ifdef _GAMECONSOLE Assert( !m_pScratchTexture ); m_pScratchTexture = CreateVTFTexture(); #endif // _GAMECONSOLE } void CBIKMaterial::DestroyProceduralTexture( CTextureReference &texture ) { if( texture ) { texture->SetTextureRegenerator( NULL ); texture.Shutdown( true ); } } void CBIKMaterial::DestroyProceduralTextures() { #ifdef _GAMECONSOLE DestroyVTFTexture( m_pScratchTexture ); m_pScratchTexture = NULL; #endif // GAMECONSOLE DestroyProceduralTexture( m_TextureY ); #ifdef SUPPORT_BINK_ALPHA DestroyProceduralTexture( m_TextureA ); #endif DestroyProceduralTexture( m_TextureCr ); DestroyProceduralTexture( m_TextureCb ); } //----------------------------------------------------------------------------- // Initializes, shuts down the procedural material //----------------------------------------------------------------------------- void CBIKMaterial::CreateProceduralMaterial( const char *pMaterialName ) { // FIXME: gak, this is backwards. Why doesn't the material just see that it has a funky basetexture? char vmtfilename[ 512 ]; Q_strcpy( vmtfilename, pMaterialName ); Q_SetExtension( vmtfilename, ".vmt", sizeof( vmtfilename ) ); KeyValues *pVMTKeyValues = new KeyValues( "Bik" ); if ( bink_try_load_vmt.GetBool() && pVMTKeyValues->LoadFromFile( g_pFullFileSystem , vmtfilename, "GAME" ) ) { // use VMT settings } else { pVMTKeyValues->SetString( "$ytexture", m_TextureY->GetName() ); #ifdef SUPPORT_BINK_ALPHA pVMTKeyValues->SetString( "$atexture", m_TextureA->GetName() ); #endif pVMTKeyValues->SetString( "$crtexture", m_TextureCr->GetName() ); pVMTKeyValues->SetString( "$cbtexture", m_TextureCb->GetName() ); pVMTKeyValues->SetInt( "$nofog", 1 ); pVMTKeyValues->SetInt( "$spriteorientation", 3 ); pVMTKeyValues->SetInt( "$translucent", 1 ); pVMTKeyValues->SetInt( "$vertexcolor", 1 ); pVMTKeyValues->SetInt( "$vertexalpha", 1 ); pVMTKeyValues->SetInt( "$nolod", 1 ); pVMTKeyValues->SetInt( "$nomip", 1 ); pVMTKeyValues->SetInt( "$nobasetexture", 1 ); } m_Material.Init( pMaterialName, pVMTKeyValues ); m_Material->Refresh(); } void CBIKMaterial::DestroyProceduralMaterial() { m_Material.Shutdown( true ); } //----------------------------------------------------------------------------- // Returns the frame rate of the BIK //----------------------------------------------------------------------------- int CBIKMaterial::GetFrameRate( ) { return m_nFrameRate; } int CBIKMaterial::GetFrameCount( ) { return m_nFrameCount; } //----------------------------------------------------------------------------- // Sets the frame for an BIK material (use instead of SetTime) //----------------------------------------------------------------------------- void CBIKMaterial::SetFrame( float flFrame ) { AUTO_LOCK( m_BinkUpdateMutex ); U32 nFrame = (U32)flFrame + 1; CMatRenderContextPtr pRenderContext( materials ); if ( pRenderContext->GetCallQueue() && bink_mat_queue_mode.GetBool() ) { pRenderContext->GetCallQueue()->QueueCall( this, &CBIKMaterial::SetFrameInternal, nFrame ); } else { SetFrameInternal( nFrame ); } } void CBIKMaterial::SetFrameInternal( int nFrame ) { AUTO_LOCK( m_BinkUpdateMutex ); if ( m_pHBINK->LastFrameNum != nFrame ) { BinkGoto( m_pHBINK, nFrame, 0 ); m_TextureY->Download(); #ifdef SUPPORT_BINK_ALPHA m_TextureA->Download(); #endif m_TextureCr->Download(); m_TextureCb->Download(); } UpdateCurrentFrameIndex(); } //----------------------------------------------------------------------------- // Initializes, shuts down the video stream //----------------------------------------------------------------------------- void CBIKMaterial::CreateVideoStream( ) { // get the frame buffers info BinkGetFrameBuffersInfo( m_pHBINK, &m_buffers ); // fixme: these should point to local buffers that the material system can splat for ( int i = 0 ; i < m_buffers.TotalFrames ; i++ ) { if ( m_buffers.Frames[ i ].YPlane.Allocate ) { // calculate a good pitch m_buffers.Frames[ i ].YPlane.BufferPitch = ( m_buffers.YABufferWidth + 15 ) & ~15; // now allocate the pointer m_buffers.Frames[ i ].YPlane.Buffer = MemAlloc_AllocAligned( m_buffers.Frames[ i ].YPlane.BufferPitch * m_buffers.YABufferHeight, 16 ); } if ( m_buffers.Frames[ i ].cRPlane.Allocate ) { // calculate a good pitch m_buffers.Frames[ i ].cRPlane.BufferPitch = ( m_buffers.cRcBBufferWidth + 15 ) & ~15; // now allocate the pointer m_buffers.Frames[ i ].cRPlane.Buffer = MemAlloc_AllocAligned( m_buffers.Frames[ i ].cRPlane.BufferPitch * m_buffers.cRcBBufferHeight, 16 ); } if ( m_buffers.Frames[ i ].cBPlane.Allocate ) { // calculate a good pitch m_buffers.Frames[ i ].cBPlane.BufferPitch = ( m_buffers.cRcBBufferWidth + 15 ) & ~15; // now allocate the pointer m_buffers.Frames[ i ].cBPlane.Buffer = MemAlloc_AllocAligned( m_buffers.Frames[ i ].cBPlane.BufferPitch * m_buffers.cRcBBufferHeight, 16 ); } #ifdef SUPPORT_BINK_ALPHA if ( m_buffers.Frames[ i ].APlane.Allocate ) { // calculate a good pitch m_buffers.Frames[ i ].APlane.BufferPitch = ( m_buffers.YABufferWidth + 15 ) & ~15; // now allocate the pointer m_buffers.Frames[ i ].APlane.Buffer = MemAlloc_AllocAligned( m_buffers.Frames[ i ].APlane.BufferPitch * m_buffers.YABufferHeight, 16 ); } #endif } // Now tell Bink to use these new planes BinkRegisterFrameBuffers( m_pHBINK, &m_buffers ); } //----------------------------------------------------------------------------- // Purpose: Destroy the stream //----------------------------------------------------------------------------- void CBIKMaterial::DestroyVideoStream( ) { // who free's this? for ( int i = 0 ; i < m_buffers.TotalFrames ; i++ ) { if ( m_buffers.Frames[ i ].YPlane.Allocate && m_buffers.Frames[ i ].YPlane.Buffer ) { // now allocate the pointer MemAlloc_FreeAligned( m_buffers.Frames[ i ].YPlane.Buffer ); m_buffers.Frames[ i ].YPlane.Buffer = NULL; } if ( m_buffers.Frames[ i ].cRPlane.Allocate && m_buffers.Frames[ i ].cRPlane.Buffer ) { // now allocate the pointer MemAlloc_FreeAligned( m_buffers.Frames[ i ].cRPlane.Buffer ); m_buffers.Frames[ i ].cRPlane.Buffer = NULL; } if ( m_buffers.Frames[ i ].cBPlane.Allocate && m_buffers.Frames[ i ].cBPlane.Buffer ) { // now allocate the pointer MemAlloc_FreeAligned( m_buffers.Frames[ i ].cBPlane.Buffer ); m_buffers.Frames[ i ].cBPlane.Buffer = NULL; } #ifdef SUPPORT_BINK_ALPHA if ( m_buffers.Frames[ i ].APlane.Allocate && m_buffers.Frames[ i ].APlane.Buffer ) { // now allocate the pointer MemAlloc_FreeAligned( m_buffers.Frames[ i ].APlane.Buffer ); m_buffers.Frames[ i ].APlane.Buffer = NULL; } #endif } } //----------------------------------------------------------------------------- // Purpose: Returns the current frame number of the video //----------------------------------------------------------------------------- int CBIKMaterial::GetFrame( void ) { AUTO_LOCK( m_BinkFrameCountMutex ); return m_nCurrentFrame; } //----------------------------------------------------------------------------- // Purpose: Pause playback //----------------------------------------------------------------------------- void CBIKMaterial::Pause( void ) { AUTO_LOCK( m_BinkUpdateMutex ); BinkPause( m_pHBINK, 1 ); } //----------------------------------------------------------------------------- // Purpose: Resume playback //----------------------------------------------------------------------------- void CBIKMaterial::Unpause( void ) { AUTO_LOCK( m_BinkUpdateMutex ); BinkPause( m_pHBINK, 0 ); } //----------------------------------------------------------------------------- // // Implementation of IAvi // //----------------------------------------------------------------------------- class CBik : public CBaseAppSystem< IBik > { public: CBik(); // Inherited from IAppSystem virtual bool Connect( CreateInterfaceFn factory ); virtual void Disconnect(); virtual void *QueryInterface( const char *pInterfaceName ); virtual InitReturnVal_t Init(); virtual void Shutdown(); // Inherited from IBik virtual BIKMaterial_t CreateMaterial( const char *pMaterialName, const char *pFileName, const char *pPathID, int flags ); virtual void DestroyMaterial( BIKMaterial_t hMaterial ); virtual bool Update( BIKMaterial_t hMaterial ); virtual bool ReadyForSwap( BIKMaterial_t hMaterial ); virtual IMaterial* GetMaterial( BIKMaterial_t hMaterial ); virtual void GetTexCoordRange( BIKMaterial_t hMaterial, float *pMaxU, float *pMaxV ); virtual void GetFrameSize( BIKMaterial_t hMaterial, int *pWidth, int *pHeight ); virtual int GetFrameRate( BIKMaterial_t hMaterial ); virtual int GetFrame( BIKMaterial_t hMaterial ); virtual void SetFrame( BIKMaterial_t hMaterial, float flFrame ); virtual int GetFrameCount( BIKMaterial_t hMaterial ); #ifdef WIN32 #if !defined( _X360 ) virtual bool SetDirectSoundDevice( void *pDevice ); virtual bool SetMilesSoundDevice( void *pDevice ); #else virtual bool HookXAudio( void ); #endif #endif #if defined( _PS3 ) virtual bool SetPS3SoundDevice( int nChannelCount ); #endif virtual void Pause( BIKMaterial_t hMaterial ); virtual void Unpause( BIKMaterial_t hMaterial ); virtual int GetGlobalMaterialAllocationNumber( void ) { return s_nMaterialAllocation; } virtual bool PrecacheMovie( const char *pFileName, const char *pPathID ); virtual void *GetPrecachedMovie( const char *pFileName ); virtual void EvictPrecachedMovie( const char *pFileName ); virtual void EvictAllPrecachedMovies(); void DumpPrecachedMovieList(); virtual void UpdateVolume( BIKMaterial_t hMaterial ); virtual bool IsMovieResidentInMemory( BIKMaterial_t hMaterial ); private: static void * RADLINK BinkMemAlloc( U32 bytes ) { return malloc( bytes ); }; static void RADLINK BinkMemFree( void PTR4* ptr ) { free( ptr ); }; // NOTE: Have to use pointers here since BIKMaterials inherit from ITextureRegenerator // The realloc screws up the pointers held to ITextureRegenerators in the material system. CUtlLinkedList< CBIKMaterial*, BIKMaterial_t > m_BIKMaterials; static int s_nMaterialAllocation; CUtlVector< PrecachedMovie_t > m_PrecachedMovies; }; //----------------------------------------------------------------------------- // Static variables //----------------------------------------------------------------------------- int CBik::s_nMaterialAllocation = 0; //----------------------------------------------------------------------------- // Singleton //----------------------------------------------------------------------------- static CBik g_BIK; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CBik, IBik, BIK_INTERFACE_VERSION, g_BIK ); //----------------------------------------------------------------------------- // Constructor/destructor //----------------------------------------------------------------------------- CBik::CBik() { } //----------------------------------------------------------------------------- // Connect/disconnect //----------------------------------------------------------------------------- bool CBik::Connect( CreateInterfaceFn factory ) { if ( IsGameConsole() ) { return true; } ConnectTier1Libraries( &factory, 1 ); ConnectTier2Libraries( &factory, 1 ); if ( !( g_pFullFileSystem && materials ) ) { Msg( "Bik failed to connect to a required system\n" ); } return ( g_pFullFileSystem && materials ); } //----------------------------------------------------------------------------- // Connect/disconnect //----------------------------------------------------------------------------- void CBik::Disconnect( void ) { } //----------------------------------------------------------------------------- // Query Interface //----------------------------------------------------------------------------- void *CBik::QueryInterface( const char *pInterfaceName ) { if (!Q_strncmp( pInterfaceName, BIK_INTERFACE_VERSION, Q_strlen(BIK_INTERFACE_VERSION) + 1)) return (IBik*)this; return NULL; } //----------------------------------------------------------------------------- // Init/shutdown //----------------------------------------------------------------------------- InitReturnVal_t CBik::Init() { BinkSetMemory( BinkMemAlloc, BinkMemFree ); return INIT_OK; } void CBik::Shutdown() { #ifdef _PS3 BinkFreeGlobals(); #endif // _PS3 } //----------------------------------------------------------------------------- // Create/destroy an BIK material //----------------------------------------------------------------------------- BIKMaterial_t CBik::CreateMaterial( const char *pMaterialName, const char *pFileName, const char *pPathID, int flags ) { // material names aren't filenames, they are expected to be adherent to forward slashes char fixedMaterialName[MAX_PATH]; V_strncpy( fixedMaterialName, pMaterialName, sizeof( fixedMaterialName ) ); V_FixSlashes( fixedMaterialName, '/' ); BIKMaterial_t h = m_BIKMaterials.AddToTail(); m_BIKMaterials[h] = new CBIKMaterial; if ( m_BIKMaterials[h]->Init( fixedMaterialName, pFileName, pPathID, flags ) == false ) { delete m_BIKMaterials[h]; m_BIKMaterials.Remove( h ); return BIKMATERIAL_INVALID; } s_nMaterialAllocation++; return h; } void CBik::DestroyMaterial( BIKMaterial_t h ) { if ( h != BIKMATERIAL_INVALID ) { if ( m_BIKMaterials[h]->Shutdown() ) { delete m_BIKMaterials[h]; } m_BIKMaterials.Remove( h ); } } //----------------------------------------------------------------------------- // Purpose: // Input : hMaterial - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBik::Update( BIKMaterial_t hMaterial ) { if ( hMaterial == BIKMATERIAL_INVALID ) return false; return m_BIKMaterials[hMaterial]->Update(); } bool CBik::ReadyForSwap( BIKMaterial_t hMaterial ) { Assert( hMaterial != BIKMATERIAL_INVALID ); if ( hMaterial == BIKMATERIAL_INVALID ) { return true; } return m_BIKMaterials[hMaterial]->ReadyForSwap(); } //----------------------------------------------------------------------------- // Gets the IMaterial associated with an BIK material //----------------------------------------------------------------------------- IMaterial* CBik::GetMaterial( BIKMaterial_t h ) { if ( h != BIKMATERIAL_INVALID ) return m_BIKMaterials[h]->GetMaterial(); return NULL; } //----------------------------------------------------------------------------- // Returns the max texture coordinate of the BIK //----------------------------------------------------------------------------- void CBik::GetTexCoordRange( BIKMaterial_t h, float *pMaxU, float *pMaxV ) { if ( h != BIKMATERIAL_INVALID ) { m_BIKMaterials[h]->GetTexCoordRange( pMaxU, pMaxV ); } else { *pMaxU = *pMaxV = 1.0f; } } //----------------------------------------------------------------------------- // Returns the frame size of the BIK (is a subrect of the material itself) //----------------------------------------------------------------------------- void CBik::GetFrameSize( BIKMaterial_t h, int *pWidth, int *pHeight ) { if ( h != BIKMATERIAL_INVALID ) { m_BIKMaterials[h]->GetFrameSize( pWidth, pHeight ); } else { *pWidth = *pHeight = 1; } } //----------------------------------------------------------------------------- // Returns the frame size of the BIK (is a subrect of the material itself) //----------------------------------------------------------------------------- int CBik::GetFrameRate( BIKMaterial_t h ) { if ( h == BIKMATERIAL_INVALID ) return -1; return m_BIKMaterials[h]->GetFrameRate(); } //----------------------------------------------------------------------------- // Returns the frame rate of the BIK //----------------------------------------------------------------------------- int CBik::GetFrameCount( BIKMaterial_t h ) { if ( h == BIKMATERIAL_INVALID ) return -1; return m_BIKMaterials[h]->GetFrameCount(); } //----------------------------------------------------------------------------- // Sets the frame for an BIK material (use instead of SetTime) //----------------------------------------------------------------------------- void CBik::SetFrame( BIKMaterial_t h, float flFrame ) { if ( h != BIKMATERIAL_INVALID ) { m_BIKMaterials[h]->SetFrame( flFrame ); } } #if defined( WIN32 ) //----------------------------------------------------------------------------- // Purpose: // Input : pDevice - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- #if !defined( _X360 ) bool CBik::SetDirectSoundDevice( void *pDevice ) { g_bDisableVolumeChanges = false; return ( BinkSoundUseDirectSound( pDevice ) != 0 ); } bool CBik::SetMilesSoundDevice( void *pDevice ) { if ( !pDevice ) { g_bDisableVolumeChanges = true; return BinkSoundUseWaveOut() != 0; } g_bDisableVolumeChanges = false; return ( BinkSoundUseMiles( pDevice ) != 0 ); } #else bool CBik::HookXAudio( void ) { IXAudio2 *pXAudio2 = Audio_GetXAudio2(); if ( !pXAudio2 ) { // it better be there, it was when th Warning( "Bink playback not supported, init sequence of audio has been regressed." ); return false; } return ( BinkSoundUseXAudio2( pXAudio2 ) != 0 ); } #endif #endif // WIN32 #if defined( _PS3 ) bool CBik::SetPS3SoundDevice( int nChannelCount ) { return BinkSoundUseLibAudio( nChannelCount ); } #endif // _PS3 //----------------------------------------------------------------------------- // Purpose: Gets the current frame from the Bink movie (for playback purposes) //----------------------------------------------------------------------------- int CBik::GetFrame( BIKMaterial_t hMaterial ) { if ( hMaterial != BIKMATERIAL_INVALID ) { return m_BIKMaterials[hMaterial]->GetFrame(); } return -1; } //----------------------------------------------------------------------------- // Purpose: Pause the movie playback //----------------------------------------------------------------------------- void CBik::Pause( BIKMaterial_t hMaterial ) { if ( hMaterial != BIKMATERIAL_INVALID ) { m_BIKMaterials[hMaterial]->Pause(); } } //----------------------------------------------------------------------------- // Purpose: Resume movie playback //----------------------------------------------------------------------------- void CBik::Unpause( BIKMaterial_t hMaterial ) { if ( hMaterial != BIKMATERIAL_INVALID ) { m_BIKMaterials[hMaterial]->Unpause(); } } bool CBik::PrecacheMovie( const char *pFileName, const char *pPathID ) { if ( GetPrecachedMovie( pFileName ) ) { // alread precached return true; } MEM_ALLOC_CREDIT(); // must deal in absolute paths to ensure zip cull (media files are external) char pBIKFileName[ 512 ]; char pFullBIKFileName[ 512 ]; Q_snprintf( pBIKFileName, sizeof( pBIKFileName ), "%s", pFileName ); Q_DefaultExtension( pBIKFileName, ".bik", sizeof( pBIKFileName ) ); PathTypeQuery_t pathType; if ( !g_pFullFileSystem->RelativePathToFullPath( pBIKFileName, pPathID, pFullBIKFileName, sizeof( pFullBIKFileName ), GetMoviePathFilter(), &pathType ) ) { // A file by that name was not found return false; } const char *pBaseName = V_UnqualifiedFileName( pFullBIKFileName ); int iIndex = m_PrecachedMovies.AddToTail(); m_PrecachedMovies[iIndex].m_BaseName = pBaseName; if ( !g_pFullFileSystem->ReadFile( pFullBIKFileName, NULL, m_PrecachedMovies[iIndex].m_MemoryBuffer ) ) { m_PrecachedMovies.Remove( iIndex ); return false; } #if defined( PLAT_BIG_ENDIAN ) // per Bink Docs DWORD *pBase = (DWORD *)m_PrecachedMovies[iIndex].m_MemoryBuffer.Base(); int numDWords = m_PrecachedMovies[iIndex].m_MemoryBuffer.TellPut() / sizeof( DWORD ); for ( int i = 0; i < numDWords; i++ ) { pBase[i] = DWordSwap( pBase[i] ); } #endif return true; } void *CBik::GetPrecachedMovie( const char *pFileName ) { const char *pBaseName = V_UnqualifiedFileName( pFileName ); for ( int i = 0; i < m_PrecachedMovies.Count(); i++ ) { if ( !V_stricmp( m_PrecachedMovies[i].m_BaseName.Get(), pBaseName ) ) { return m_PrecachedMovies[i].m_MemoryBuffer.Base(); } } // not found return NULL; } void CBik::EvictPrecachedMovie( const char *pFileName ) { const char *pBaseName = V_UnqualifiedFileName( pFileName ); for ( int i = 0; i < m_PrecachedMovies.Count(); i++ ) { if ( !V_stricmp( m_PrecachedMovies[i].m_BaseName.Get(), pBaseName ) ) { m_PrecachedMovies.Remove( i ); break; } } } void CBik::EvictAllPrecachedMovies() { m_PrecachedMovies.Purge(); } void CBik::UpdateVolume( BIKMaterial_t hMaterial ) { if ( hMaterial != BIKMATERIAL_INVALID ) { m_BIKMaterials[hMaterial]->UpdateVolume(); } } bool CBik::IsMovieResidentInMemory( BIKMaterial_t hMaterial ) { if ( hMaterial != BIKMATERIAL_INVALID ) { return m_BIKMaterials[hMaterial]->IsMovieResidentInMemory(); } return false; } void CBik::DumpPrecachedMovieList() { Msg( "-- %d precached bink movies -- \n", m_PrecachedMovies.Count() ); for ( int i = 0; i < m_PrecachedMovies.Count(); ++ i ) { Msg( "%d: %s, %d bytes\n", i, m_PrecachedMovies[i].m_BaseName.String(), m_PrecachedMovies[i].m_MemoryBuffer.Size() ); } } CON_COMMAND( bink_dump_precached_movies, "Dumps information about all precached Bink movies" ) { g_BIK.DumpPrecachedMovieList(); } #endif #endif // BINK_VIDEO