diff --git a/stepmania/src/RageMovieTexture.cpp b/stepmania/src/RageMovieTexture.cpp index 88d5aa1177..6de6601304 100644 --- a/stepmania/src/RageMovieTexture.cpp +++ b/stepmania/src/RageMovieTexture.cpp @@ -7,345 +7,300 @@ Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford - Adapted from Nehe tutorial 35 + Glenn Maynard ----------------------------------------------------------------------------- */ + +#pragma comment(lib, "winmm.lib") + +// Link with the DirectShow base class libraries +#if defined(DEBUG) | defined(_DEBUG) + #pragma comment(lib, "baseclasses/debug/strmbasd.lib") +#else + #pragma comment(lib, "baseclasses/release/strmbase.lib") +#endif + +#include "RageMovieTextureHelper.h" + #include "RageMovieTexture.h" #include "RageUtil.h" #include "RageLog.h" #include "RageException.h" -#include "RageDisplay.h" -#include "RageTimer.h" -#include "sdl_opengl.h" -#include - -#pragma comment( lib, "vfw32.lib" ) - - -static int power_of_two(int input) -{ - int value = 1; - - while ( value < input ) value <<= 1; - - return value; -} - - -struct AviRenderer -{ -private: - AVISTREAMINFO psi; // Pointer To A Structure Containing Stream Info - PAVISTREAM pavi; // Handle To An Open Stream - PGETFRAME pgf; // Pointer To A GetFrame Object - BITMAPINFOHEADER bmih; // Header Information For DrawDibDraw Decoding - int videoWidth; - int videoHeight; - int frameWidth; - int frameHeight; - char *pdata; // Pointer To Texture Data - int numFrames; - float secsIntoPlay; // Will Hold seconds since beginning of this loop - float streamSecs; // Will Hold seconds since beginning of this loop - HDRAWDIB hdd; // Handle For Our Dib - HBITMAP hBitmap; // Handle To A Device Dependant Bitmap - HDC hdc; // Creates A Compatible Device Context - unsigned char* data; // Pointer To Our Resized Image - int last_frame; // true if frame hasn't let been requested - bool frame_is_new; // true if frame hasn't let been requested - -public: - int GetVideoWidth() { return videoWidth; } - int GetVideoHeight() { return videoHeight; } - int GetFrameWidth() { return frameWidth; } - int GetFrameHeight() { return frameHeight; } - void Play() {} - void Pause() {} - void Stop() {} - void SetPosition( float fSeconds ) {} - bool IsPlaying() { return true; } - - AviRenderer( CString sFile ) - { - hdc = CreateCompatibleDC(0); // Creates A Compatible Device Context - hdd = DrawDibOpen(); // Grab A Device Context For Our Dib - - AVIFileInit(); // Opens The AVIFile Library - - // Opens The AVI Stream - if (AVIStreamOpenFromFile(&pavi, sFile, streamtypeVIDEO, 0, OF_READ, NULL) !=0 ) - throw RageException( "Failed To Open The AVI Stream" ); - - AVIStreamInfo(pavi, &psi, sizeof(psi)); // Reads Information About The Stream Into psi - videoWidth=psi.rcFrame.right-psi.rcFrame.left; // Width Is Right Side Of Frame Minus Left - videoHeight=psi.rcFrame.bottom-psi.rcFrame.top; // Height Is Bottom Of Frame Minus Top - frameWidth = power_of_two( videoWidth ); - frameHeight = power_of_two( videoHeight ); - - last_frame = -1; - - numFrames = AVIStreamLength(pavi); // The Last Frame Of The Stream - streamSecs = AVIStreamSampleToTime(pavi,numFrames)/1000.f; // Calculate stream length - secsIntoPlay = 0; - - bmih.biSize = sizeof (BITMAPINFOHEADER); // Size Of The BitmapInfoHeader - bmih.biPlanes = 1; // Bitplanes - bmih.biBitCount = 24; // Bits Format We Want (24 Bit, 3 Bytes) - bmih.biWidth = frameWidth; // Width We Want (256 Pixels) - bmih.biHeight = frameHeight; // Height We Want (256 Pixels) - bmih.biCompression = BI_RGB; // Requested Mode = RGB - - hBitmap = CreateDIBSection (hdc, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&data), NULL, NULL); - SelectObject (hdc, hBitmap); // Select hBitmap Into Our Device Context (hdc) - - pgf=AVIStreamGetFrameOpen(pavi, NULL); // Create The PGETFRAME Using Our Request Mode - if (pgf==NULL) - throw RageException( "Failed To Open The AVI Frame" ); - } - - ~AviRenderer(void) // Properly Closes The Avi File - { - DeleteObject(hBitmap); // Delete The Device Dependant Bitmap Object - DrawDibClose(hdd); // Closes The DrawDib Device Context - AVIStreamGetFrameClose(pgf); // Deallocates The GetFrame Resources - AVIStreamRelease(pavi); // Release The Stream - AVIFileExit(); // Release The File - } - - void Update( float fDeltaTime ) - { - secsIntoPlay += fDeltaTime; - while( secsIntoPlay > streamSecs ) - { - secsIntoPlay -= streamSecs; - } - - int this_frame = secsIntoPlay/streamSecs*numFrames; - - if( last_frame != this_frame ) - { - frame_is_new = true; - GetFrame( this_frame ); - last_frame = this_frame; - } - } - - void flipIt(void* buffer) // Flips The Red And Blue Bytes (256x256) - { - void* b = buffer; // Pointer To The Buffer - unsigned int numFips = frameWidth*frameHeight; - __asm // Assembler Code To Follow - { - mov ecx, numFips // Set Up A Counter (Dimensions Of Memory Block) - mov ebx, b // Points ebx To Our Data (b) - label: // Label Used For Looping - mov al,[ebx+0] // Loads Value At ebx Into al - mov ah,[ebx+2] // Loads Value At ebx+2 Into ah - mov [ebx+2],al // Stores Value In al At ebx+2 - mov [ebx+0],ah // Stores Value In ah At ebx - - add ebx,3 // Moves Through The Data By 3 Bytes - dec ecx // Decreases Our Loop Counter - jnz label // If Not Zero Jump Back To Label - } - } - - bool IsNewFrameReady() // returns true only once when a new frame has been taken from the stream - { - bool bNew = frame_is_new; - frame_is_new = false; - return bNew; - } - - unsigned char* GetData() // Grabs current frame from the stream - { - Update( 1/60.f ); - return data; - } - - void GetFrame(int frame) // Grabs A Frame From The Stream - { - LPBITMAPINFOHEADER lpbi; // Holds The Bitmap Header Information - lpbi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, frame); // Grab Data From The AVI Stream - pdata=(char *)lpbi+lpbi->biSize+lpbi->biClrUsed * sizeof(RGBQUAD); // Pointer To Data Returned By AVIStreamGetFrame - - SelectObject (hdc, hBitmap); // Select hBitmap Into Our Device Context (hdc) - - // (Skip The Header Info To Get To The Data) - // 1:1 copy into the new surface - DrawDibDraw (hdd, hdc, 0, 0, videoWidth, videoHeight, lpbi, pdata, 0, 0, videoWidth, videoHeight, 0); - - flipIt(data); // Swap The Red And Blue Bytes (GL Compatability) - } -}; - - - - - // Copy the bits - // OPTIMIZATION OPPORTUNITY: Use a video and texture - // format that allows a simpler copy than this one. -// switch( m_TextureFormat ) -// { -// case D3DFMT_A8R8G8B8: -// { -// for(int y = 0; y < m_pTexture->GetImageHeight(); y++ ) { -// BYTE *pBmpBufferOld = pBmpBuffer; -// BYTE *pTxtBufferOld = pTxtBuffer; -// for (int x = 0; x < m_pTexture->GetImageWidth(); x++) { -// pTxtBuffer[0] = pBmpBuffer[0]; -// pTxtBuffer[1] = pBmpBuffer[1]; -// pTxtBuffer[2] = pBmpBuffer[2]; -// pTxtBuffer[3] = 0xff; -// pTxtBuffer += 4; -// pBmpBuffer += 3; -// } -// pBmpBuffer = pBmpBufferOld + m_lVidPitch; -// pTxtBuffer = pTxtBufferOld + lTxtPitch; -// } -// } -// break; -// case D3DFMT_A1R5G5B5: -// { -// for(int y = 0; y < m_pTexture->GetImageHeight(); y++ ) { -// BYTE *pBmpBufferOld = pBmpBuffer; -// BYTE *pTxtBufferOld = pTxtBuffer; -// for (int x = 0; x < m_pTexture->GetImageWidth(); x++) { -// *(WORD *)pTxtBuffer = -// 0x8000 + -// ((pBmpBuffer[2] & 0xF8) << 7) + -// ((pBmpBuffer[1] & 0xF8) << 2) + -// (pBmpBuffer[0] >> 3); -// pTxtBuffer += 2; -// pBmpBuffer += 3; -// } -// pBmpBuffer = pBmpBufferOld + m_lVidPitch; -// pTxtBuffer = pTxtBufferOld + lTxtPitch; -// } -// } -// break; - +#include +#define NO_SDL_GLEXT +#define __glext_h_ /* try harder to stop glext.h from being forced on us by someone else */ +#include "SDL_opengl.h" +#include "glext.h" //----------------------------------------------------------------------------- // RageMovieTexture constructor //----------------------------------------------------------------------------- -RageMovieTexture::RageMovieTexture( CString sFilePath, RageTexturePrefs prefs ) : +RageMovieTexture::RageMovieTexture( const CString &sFilePath, RageTexturePrefs prefs ) : RageTexture( sFilePath, prefs ) { LOG->Trace( "RageBitmapTexture::RageBitmapTexture()" ); - m_uGLTextureID = 0; - pAviRenderer = new AviRenderer( m_sFilePath ); - pAviRenderer->Play(); + m_FilePath = sFilePath; + buffer_changed = false; + m_uGLTextureID = 0; + buffer = NULL; + Create(); + + CreateFrameRects(); + // flip all frame rects because movies are upside down + for( unsigned i=0; iTrace("RageMovieTexture::~RageMovieTexture"); - delete pAviRenderer; + // Shut down the graph + if (m_pGB) { + Stop(); + m_pGB.Release (); + } + + delete [] buffer; + + if(m_uGLTextureID) + glDeleteTextures(1, &m_uGLTextureID); } void RageMovieTexture::Reload( RageTexturePrefs prefs ) { - DISPLAY->SetTexture(0); + // do nothing +} - if(m_uGLTextureID) - { - glDeleteTextures(1, &m_uGLTextureID); - m_uGLTextureID = 0; - } - Create(); +unsigned int RageMovieTexture::GetGLTextureID() +{ + CheckMovieStatus(); // restart the movie if we reach the end + return m_uGLTextureID; +} + +void RageMovieTexture::Update(float fDeltaTime) +{ + LockMutex L(buffer_mutex); + + if(!buffer_changed) + return; + + buffer_changed = false; + + DISPLAY->SetTexture(this); + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, m_iSourceWidth); + + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, 0, + min(m_iSourceWidth, m_iTextureWidth), + min(m_iSourceHeight, m_iTextureHeight), + GL_BGR, GL_UNSIGNED_BYTE, buffer); + + glFlush(); +} + +void HandleDivXError() +{ + /* Actually, we might need XviD; we might want to look + * at the file and try to figure out if it's something + * common: DIV3, DIV4, DIV5, XVID, or maybe even MPEG2. */ + throw RageException( + "Could not locate the DivX video codec.\n" + "DivX is required to movie textures and must\n" + "be installed before running the application.\n\n" + "Please visit http://www.divx.com to download the latest version." + ); } void RageMovieTexture::Create() { - if(!m_uGLTextureID) - glGenTextures(1, &m_uGLTextureID); + HRESULT hr; + + if( FAILED( hr=CoInitialize(NULL) ) ) + throw RageException( hr_ssprintf(hr, "Could not CoInitialize") ); - /* Cap the max texture size to the hardware max. */ - m_prefs.iMaxSize = min( m_prefs.iMaxSize, DISPLAY->GetMaxTextureSize() ); + // Create the filter graph + if( FAILED( hr=m_pGB.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC) ) ) + throw RageException( hr_ssprintf(hr, "Could not create CLSID_FilterGraph!") ); + + // Create the Texture Renderer object + CTextureRenderer *pCTR = new CTextureRenderer; + + /* Get a pointer to the IBaseFilter on the TextureRenderer, and add it to the + * graph. When m_pGB is released, it will free pFTR. */ + CComPtr pFTR = pCTR; + if( FAILED( hr = m_pGB->AddFilter(pFTR, L"TEXTURERENDERER" ) ) ) + throw RageException( hr_ssprintf(hr, "Could not add renderer filter to graph!") ); - /* Save information about the source. */ - m_iSourceWidth = pAviRenderer->GetVideoWidth(); - m_iSourceHeight = pAviRenderer->GetVideoHeight(); + // Add the source filter + CComPtr pFSrc; // Source Filter + WCHAR wFileName[MAX_PATH]; + MultiByteToWideChar(CP_ACP, 0, m_FilePath.GetString(), -1, wFileName, MAX_PATH); + // if this fails, it's probably because the user doesn't have DivX installed + if( FAILED( hr = m_pGB->AddSourceFilter( wFileName, L"SOURCE", &pFSrc ) ) ) + { + HandleDivXError(); + throw RageException( hr_ssprintf(hr, "Could not create source filter to graph!") ); + } + + // Find the source's output and the renderer's input + CComPtr pFTRPinIn; // Texture Renderer Input Pin + if( FAILED( hr = pFTR->FindPin( L"In", &pFTRPinIn ) ) ) + throw RageException( hr_ssprintf(hr, "Could not find input pin!") ); + + CComPtr pFSrcPinOut; // Source Filter Output Pin + if( FAILED( hr = pFSrc->FindPin( L"Output", &pFSrcPinOut ) ) ) + throw RageException( hr_ssprintf(hr, "Could not find output pin!") ); + + // Connect these two filters + if( FAILED( hr = m_pGB->Connect( pFSrcPinOut, pFTRPinIn ) ) ) + { + HandleDivXError(); + throw RageException( hr_ssprintf(hr, "Could not connect pins!") ); + } + + // Pass us to our TextureRenderer. + pCTR->SetRenderTarget(this); + + // The graph is built, now get the set the output video width and height. + // The source and image width will always be the same since we can't scale the video + m_iSourceWidth = pCTR->GetVidWidth(); + m_iSourceHeight = pCTR->GetVidHeight(); + + /* We've set up the movie, so we know the dimensions we need. Set + * up the texture. */ + CreateTexture(); + + // Start the graph running + if( !PlayMovie() ) + throw RageException( "Could not run the DirectShow graph." ); +} + +void RageMovieTexture::NewData(char *data) +{ + LockMutex L(buffer_mutex); + buffer_changed = true; + memcpy(buffer, data, m_iSourceWidth * m_iSourceHeight * 3); +} + +bool RageMovieTexture::CreateTexture() +{ /* image size cannot exceed max size */ m_iImageWidth = min( m_iSourceWidth, m_prefs.iMaxSize ); m_iImageHeight = min( m_iSourceHeight, m_prefs.iMaxSize ); /* Texture dimensions need to be a power of two; jump to the next. */ - m_iTextureWidth = power_of_two( m_iSourceWidth ); - m_iTextureHeight = power_of_two( m_iSourceHeight ); + m_iTextureWidth = power_of_two(m_iImageWidth); + m_iTextureHeight = power_of_two(m_iImageHeight); + buffer = new char[m_iSourceWidth * m_iSourceHeight * 3]; + if(!m_uGLTextureID) + glGenTextures(1, &m_uGLTextureID); - glBindTexture( GL_TEXTURE_2D, m_uGLTextureID ); + DISPLAY->SetTexture(this); - // Create The Texture - unsigned char* data = pAviRenderer->GetData(); - glPixelStorei(GL_UNPACK_ROW_LENGTH, m_iTextureWidth); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, m_iTextureWidth, m_iTextureHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, data); - - - CreateFrameRects(); - - // invert text coor rect - m_TextureCoordRects[0].top = 1 - m_TextureCoordRects[0].top; - m_TextureCoordRects[0].bottom = 1 - m_TextureCoordRects[0].bottom; + /* Initialize the texture and set it to black. */ + string buf(m_iTextureWidth*m_iTextureHeight*3, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, + m_iTextureWidth, m_iTextureHeight, 0, GL_BGR, GL_UNSIGNED_BYTE, buf.data()); + return true; } -unsigned int RageMovieTexture::GetGLTextureID() +bool RageMovieTexture::PlayMovie() { - if( pAviRenderer->IsNewFrameReady() ) - { - unsigned char* data = pAviRenderer->GetData(); + LOG->Trace("RageMovieTexture::PlayMovie()"); + CComPtr pMC; + m_pGB.QueryInterface(&pMC); - // Update The Texture - glBindTexture( GL_TEXTURE_2D, m_uGLTextureID ); - glPixelStorei(GL_UNPACK_ROW_LENGTH, m_iTextureWidth); - glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, m_iTextureWidth, m_iTextureHeight, GL_RGB, GL_UNSIGNED_BYTE, data); - } + // Start the graph running; + HRESULT hr; + if( FAILED( hr = pMC->Run() ) ) + throw RageException( hr_ssprintf(hr, "Could not run the DirectShow graph.") ); - return m_uGLTextureID; + m_bPlaying = true; + + return true; } -void RageMovieTexture::Update( float fDeltaTime ) + +//----------------------------------------------------------------------------- +// CheckMovieStatus: If the movie has ended, rewind to beginning +//----------------------------------------------------------------------------- +void RageMovieTexture::CheckMovieStatus() { - pAviRenderer->Update( fDeltaTime ); + long lEventCode; + long lParam1; + long lParam2; + + // Check for completion events + CComPtr pME; + m_pGB.QueryInterface(&pME); + pME->GetEvent( &lEventCode, &lParam1, &lParam2, 0 ); + if( EC_COMPLETE == lEventCode && m_bLoop ) + SetPosition(0); } void RageMovieTexture::Play() { - pAviRenderer->Play(); + PlayMovie(); } void RageMovieTexture::Pause() { - pAviRenderer->Pause(); + LOG->Trace("RageMovieTexture::Pause()"); + CComPtr pMC; + m_pGB.QueryInterface(&pMC); + + HRESULT hr; + if( FAILED( hr = pMC->Pause() ) ) + throw RageException( hr_ssprintf(hr, "Could not pause the DirectShow graph.") ); + } void RageMovieTexture::Stop() { - pAviRenderer->Stop(); + LOG->Trace("RageMovieTexture::Stop()"); + CComPtr pMC; + m_pGB.QueryInterface(&pMC); + + HRESULT hr; + if( FAILED( hr = pMC->Stop() ) ) + throw RageException( hr_ssprintf(hr, "Could not stop the DirectShow graph.") ); + + m_bPlaying = false; } void RageMovieTexture::SetPosition( float fSeconds ) { - pAviRenderer->SetPosition( fSeconds ); + LOG->Trace("RageMovieTexture::Stop()"); + CComPtr pMP; + m_pGB.QueryInterface(&pMP); + pMP->put_CurrentPosition(0); } bool RageMovieTexture::IsPlaying() const { - return pAviRenderer->IsPlaying(); + return m_bPlaying; } diff --git a/stepmania/src/RageMovieTexture.h b/stepmania/src/RageMovieTexture.h index 663a00e0a7..4edafea535 100644 --- a/stepmania/src/RageMovieTexture.h +++ b/stepmania/src/RageMovieTexture.h @@ -1,18 +1,41 @@ -#pragma once +#ifndef RAGEMOVIETEXTURE_H +#define RAGEMOVIETEXTURE_H /* ----------------------------------------------------------------------------- Class: RageMovieTexture - Desc: Based on the NeHe tutorial 35 + Desc: Based on the DShowTextures example in the DX8 SDK. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford ----------------------------------------------------------------------------- */ -#include "RageTexture.h" +/* Don't know why we need this for the headers ... */ +typedef char TCHAR, *PTCHAR; + +/* Prevent these from using Dbg stuff, which we don't link in. */ +#ifdef DEBUG +#undef DEBUG +#undef _DEBUG +#define GIVE_BACK_DEBUG +#endif + +#include + +#ifdef GIVE_BACK_DEBUG +#undef GIVE_BACK_DEBUG +#define _DEBUG +#define DEBUG +#endif + +#include "baseclasses/streams.h" + +#include "RageDisplay.h" +#include "RageTexture.h" +#include "RageThreads.h" + -struct AviRenderer; // defined in RageMovieTexture.cpp //----------------------------------------------------------------------------- // RageMovieTexture Class Declarations @@ -20,24 +43,50 @@ struct AviRenderer; // defined in RageMovieTexture.cpp class RageMovieTexture : public RageTexture { public: - RageMovieTexture( CString sFilePath, RageTexturePrefs prefs ); + RageMovieTexture( const CString &sFilePath, RageTexturePrefs prefs ); virtual ~RageMovieTexture(); - virtual void Update( float fDeltaTime ); - virtual void Invalidate() { m_uGLTextureID = 0; } + void Update(float fDeltaTime); + virtual void Reload( RageTexturePrefs prefs ); virtual void Play(); virtual void Pause(); virtual void Stop(); virtual void SetPosition( float fSeconds ); + virtual bool IsAMovie() const { return true; }; virtual bool IsPlaying() const; - void SetLooping(bool looping); + void SetLooping(bool looping=true) { m_bLoop = looping; } + +// LPDIRECT3DTEXTURE8 GetBackBuffer() { return m_pd3dTexture[!m_iIndexFrontBuffer]; } +// void Flip() { m_iIndexFrontBuffer = !m_iIndexFrontBuffer; } + + void NewData(char *buffer); protected: - void Create(); // called by constructor and Reload +// LPDIRECT3DTEXTURE8 m_pd3dTexture[2]; // double buffered + int m_iIndexFrontBuffer; // index of the buffer that should be rendered from - either 0 or 1 + + char *buffer; + bool buffer_changed; + RageMutex buffer_mutex; + + void Create(); + + bool CreateTexture(); + bool PlayMovie(); + void CheckMovieStatus(); + + CString m_FilePath; virtual unsigned int GetGLTextureID(); + unsigned int m_uGLTextureID; - unsigned int m_uGLTextureID; - struct AviRenderer* pAviRenderer; + //----------------------------------------------------------------------------- + // DirectShow pointers + //----------------------------------------------------------------------------- + CComPtr m_pGB; // GraphBuilder + bool m_bLoop; + bool m_bPlaying; }; + +#endif