|
|
//====== 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
|