Implement Ogre video texture with VMR9

These two days watch the KlayGE engine, video textures inside. I thought I’d do it in the Ogre.
The core idea is to use VMR9 to render video and update Ogre texture in Ogre frame monitoring. Below give their own code, if necessary, you can also ask me to learn together.
The key code is given below:

DShowVMR9Allocator. H

 1  
 2 
 3 #ifndef _DSHOWVMR9ALLOCATOR_HPP
 4 #define _DSHOWVMR9ALLOCATOR_HPP
 5 
 7   
 8 #ifndef CHECK_HRESULT 
 9 #define CHECK_HRESULT(x) { HRESULT _hr = x; if (static_cast<HRESULT>(_hr) < 0) { std::stringstream ss;ss << __FILE__ << ": " << __LINE__; throw std::runtime_error(ss.str()); } }
10 #endif
11 
12 #include <d3d9.h>
13 #include <strmif.h>
14 #include <vmr9.h>
15 #include <vector>
16 #include"Ogre.h"
17 #include "mutex.hpp"
18  
19 
20 
21 #define USER_ID  0x12345678
22 
23  class VMR9Allocator : public IVMRSurfaceAllocator9, IVMRImagePresenter9
24 { 
25     public:
26         explicit VMR9Allocator(HWND wnd,Ogre::String texname);
27         virtual~VMR9Allocator();
28          
29         virtual HRESULT STDMETHODCALLTYPE InitializeDevice(DWORD_PTR dwUserID,VMR9AllocationInfo *lpAllocInfo,DWORD *lpNumBuffers);
30         virtual HRESULT STDMETHODCALLTYPE TerminateDevice(DWORD_PTR dwID);
31         virtual HRESULT STDMETHODCALLTYPE GetSurface(DWORD_PTR dwUserID,DWORD SurfaceIndex,DWORD SurfaceFlags,IDirect3DSurface9 **lplpSurface);
32         virtual HRESULT STDMETHODCALLTYPE AdviseNotify(IVMRSurfaceAllocatorNotify9 *lpIVMRSurfAllocNotify);
33         virtual HRESULT STDMETHODCALLTYPE StartPresenting(DWORD_PTR dwUserID);
34         virtual HRESULT STDMETHODCALLTYPE StopPresenting(DWORD_PTR dwUserID);
35         virtual HRESULT STDMETHODCALLTYPE PresentImage(DWORD_PTR dwUserID,VMR9PresentationInfo *lpPresInfo);
36         virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void** ppvObject);
37         virtual ULONG STDMETHODCALLTYPE AddRef();
38         virtual ULONG STDMETHODCALLTYPE Release(); 
39     public:
40         void ResetDecive();
41         Ogre::TexturePtr PresentTexture(); 
42         Ogre::TexturePtr GetTexture()const{ return present_tex_;};
43 
44     protected: 
45         void CreateDevice();
46         void DeleteSurfaces();
47     private:  
48         static int d3DDLLRefCount_;
49         static HMODULE mod_d3d9_;
50     private:
51         HWND wnd_;
52         int    ref_count_;
53 
54         IDirect3D9* d3d_;
55         IDirect3DDevice9* d3d_device_;
56 
57         IVMRSurfaceAllocatorNotify9* vmr_surf_alloc_notify_;
58         std::vector<IDirect3DSurface9*>    surfaces_;
59         int cur_surf_index_; 
60         IDirect3DSurface9* cache_surf_; 
61 
62         typedef IDirect3D9* (WINAPI *Direct3DCreate9Func)(UINT SDKVersion);
63         Direct3DCreate9Func DynamicDirect3DCreate9_; 
64         D3DPRESENT_PARAMETERS d3dpp_; 
65    private:
66         Ogre::TexturePtr present_tex_; 
67         Ogre::String     texName;
68 private:
69     VMR::mutex  mMutex;
70     }; 
71 
72 #endif

DShowVMR9Allocator.cpp



View Code

VMR9Allocator::VMR9Allocator(HWND wnd,Ogre::String texname): wnd_(wnd),texName(texname), ref_count_(1),cur_surf_index_(0xFFFFFFFF),cache_surf_(nullptr)
{
    if(d3DDLLRefCount_++==0)
     VMR9Allocator::mod_d3d9_ = ::LoadLibraryW(L"d3d9.dll");
    if (nullptr == mod_d3d9_)
     {
         ::MessageBoxW(nullptr, L"failure to load d3d9.dll", L"Error", MB_OK);
     }
         if (mod_d3d9_ != nullptr)
         {
             DynamicDirect3DCreate9_ = reinterpret_cast<Direct3DCreate9Func>(::GetProcAddress(mod_d3d9_, "Direct3DCreate9"));
         }
         d3d_ =  DynamicDirect3DCreate9_(D3D_SDK_VERSION);
         this->CreateDevice();
}

VMR9Allocator::~VMR9Allocator()
{    
    this->DeleteSurfaces();
    if( --d3DDLLRefCount_==0) FreeLibrary( VMR9Allocator::mod_d3d9_);
}



View Code

HRESULT VMR9Allocator::InitializeDevice(DWORD_PTR dwUserID,VMR9AllocationInfo* lpAllocInfo, DWORD* lpNumBuffers)
{
        if (dwUserID != USER_ID)
        {
            return S_OK;
        }

        if (nullptr == lpNumBuffers)
        {
            return E_POINTER;
        }

        if (!vmr_surf_alloc_notify_)
        {
            return E_FAIL;
        }
         

        HRESULT hr = S_OK;
 
        lpAllocInfo->dwFlags |= VMR9AllocFlag_TextureSurface;
        this->DeleteSurfaces();
        surfaces_.resize(*lpNumBuffers);
        hr = vmr_surf_alloc_notify_->AllocateSurfaceHelper(lpAllocInfo, lpNumBuffers, &surfaces_[0]);
        if (FAILED(hr) && !(lpAllocInfo->dwFlags & VMR9AllocFlag_3DRenderTarget))
        {
            this->DeleteSurfaces();

            lpAllocInfo->dwFlags &= ~VMR9AllocFlag_TextureSurface;
            lpAllocInfo->dwFlags |= VMR9AllocFlag_OffscreenSurface;

            CHECK_HRESULT(vmr_surf_alloc_notify_->AllocateSurfaceHelper(lpAllocInfo, lpNumBuffers, &surfaces_[0]));
        } 
         
        
        //Create Ogre Texture
        if(present_tex_.isNull())
        {
            printf("Create Ogre\n");
            present_tex_=Ogre::TextureManager::getSingleton().createManual(
            texName,
            Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
            Ogre::TEX_TYPE_2D,
            lpAllocInfo->dwWidth,
            lpAllocInfo->dwHeight,
            0,
            Ogre::PF_BYTE_BGRA,
            Ogre::TU_DYNAMIC_WRITE_ONLY
            );
        }
        CHECK_HRESULT(d3d_device_->CreateOffscreenPlainSurface(lpAllocInfo->dwWidth, lpAllocInfo->dwHeight,
            D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &cache_surf_, nullptr)); 

        return S_OK;
}



View Code

Ogre::TexturePtr VMR9Allocator::PresentTexture()
{ 
    if(present_tex_.isNull())
    {
        printf("wenli\n");
        return Ogre::TexturePtr();
    }
    VMR::TryLock _lock(mMutex);
    if(!_lock.IsLock()) return Ogre::TexturePtr();

    if (FAILED(d3d_device_->TestCooperativeLevel()))
    {
            return Ogre::TexturePtr();
    } 

    if (cur_surf_index_ < surfaces_.size())
    {
            CHECK_HRESULT(d3d_device_->GetRenderTargetData(surfaces_[cur_surf_index_], cache_surf_));

            D3DLOCKED_RECT d3dlocked_rc;
            if(cache_surf_==NULL) return Ogre::TexturePtr();
            CHECK_HRESULT(cache_surf_->LockRect(&d3dlocked_rc, nullptr, D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY));

            uint32_t const width = present_tex_->getWidth();
            uint32_t const height = present_tex_->getHeight(); 
            uint8_t const * src = static_cast<uint8_t const *>(d3dlocked_rc.pBits);
            {  
                Ogre::HardwarePixelBufferSharedPtr pixelBuffer = present_tex_->getBuffer();
                pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
                const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
                size_t rowPitch = pixelBox.rowPitch;
                Ogre::uint32* dst = static_cast<Ogre::uint32*>(pixelBox.data);
                {
                    for (uint32_t y = 0; y < height; ++ y)
                    {
                        memcpy(dst, src, width * 4);
                        dst += rowPitch;
                        src += d3dlocked_rc.Pitch;
                    }
                } 
                pixelBuffer->unlock();
            } 
            CHECK_HRESULT(cache_surf_->UnlockRect());
    } 
    return present_tex_;
}

ShowVideoTexture.h



View Code

#pragma once
#include <windows.h>
#include <control.h>
#include <d3d9.h>
#include <strmif.h>
#include <vmr9.h>
#include "Ogre.h"
#include "OgreFrameListener.h"
#include "string" 
using namespace std;


enum ShowState
{
    SS_Unkown,
    SS_Uninit,
    SS_Stopped,
    SS_Paused,
    SS_Playing,
};

//已解决D3D设备丢失 纹理重设

class ShowVideoTexture:public Ogre::FrameListener,public Ogre::RenderSystem::Listener
{
public:
    ShowVideoTexture(HWND hwnd);
    ~ShowVideoTexture();

    bool IsComplete();

    void Load(std::string const & fileName);
    Ogre::TexturePtr PresentTexture();

    ShowState State(long msTimeout = -1);

    bool frameStarted(const Ogre::FrameEvent& evt);
    void eventOccurred(const Ogre::String &  eventName,const Ogre::NameValuePairList *parameters = 0);

private:
    IGraphBuilder*        graph_;
    IBaseFilter*        filter_;
    IMediaControl*        media_control_;
    IMediaEvent*        media_event_;
    IMediaSeeking*      media_seek_;
    IVMRSurfaceAllocator9* vmr_allocator_;
    ShowState    state_;
    HWND HWnd;
    float               mtimeOffset;
    float               currTimeflow;
private:
    void Init();
    void Free();

    void DoPlay();
    void DoStop();
    void DoPause(); 

public:
    bool CanPlay() const;
    bool CanStop() const;
    bool CanPause() const;
    bool IsInitialized() const;

    void Play();
    void Stop();
    void Pause(); 
    LONGLONG GetMediaLength()const;
    LONGLONG GetCurrPosition()const;
    void SetPosition(LONGLONG );

    void SetTexUpdateTimeOffset(float timeos){mtimeOffset = timeos;};

    Ogre::TexturePtr GetTexture();
    string VideofileName;

private:
    void CreateMaterial(Ogre::String matName);
    Ogre::MaterialPtr mMaterial;
    bool ResetTexture;
public:
    Ogre::MaterialPtr GetMaterial()const{return mMaterial;};
};

ShowVideoTexture.cpp



View Code

ShowVideoTexture::ShowVideoTexture(HWND hwnd):HWnd(hwnd),vmr_allocator_(NULL),media_seek_(NULL),ResetTexture(false),
        media_event_(NULL),media_control_(NULL),filter_(NULL),graph_(NULL),mtimeOffset(0.05f),currTimeflow(0)
{
        Ogre::Root::getSingletonPtr()->addFrameListener(this);
        Ogre::Root::getSingletonPtr()->getRenderSystem()->addListener(this);
}

ShowVideoTexture::~ShowVideoTexture()
{
        this->Free();
        Ogre::Root::getSingletonPtr()->removeFrameListener(this);
        Ogre::Root::getSingletonPtr()->getRenderSystem()->removeListener(this);
}

Create Ogre materials



View Code

void ShowVideoTexture::CreateMaterial(Ogre::String matName)
{ 
    mMaterial=Ogre::MaterialManager::getSingleton().create(matName,Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
    //Create texture objects and set parameters
    Ogre::TextureUnitState *tex= mMaterial->getTechnique(0)->getPass(0)->createTextureUnitState();
    tex->setTextureFiltering(Ogre::FO_LINEAR, Ogre::FO_LINEAR, Ogre::FO_NONE);
    //Get the texture space and assign a value to it.
    tex=mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0); 
    tex->setTexture( GetTexture());  
}



View Code

void ShowVideoTexture::Load(std::string const & fileName)
{
         this->Free();
         this->Init(); 
         VideofileName = fileName;
         
        CHECK_HRESULT(::CoCreateInstance(CLSID_FilterGraph, nullptr, CLSCTX_ALL,
            IID_IGraphBuilder, reinterpret_cast<void**>(&graph_))); 

     
        CHECK_HRESULT(::CoCreateInstance(CLSID_VideoMixingRenderer9, nullptr, CLSCTX_INPROC_SERVER,
            IID_IBaseFilter, reinterpret_cast<void**>(&filter_))); 

        IVMRFilterConfig9* filter_config;
        { 
            CHECK_HRESULT(filter_->QueryInterface(IID_IVMRFilterConfig9, reinterpret_cast<void**>(&filter_config))); 
        }

        CHECK_HRESULT(filter_config->SetRenderingMode(VMR9Mode_Renderless));
        CHECK_HRESULT(filter_config->SetNumberOfStreams(1));

        IVMRSurfaceAllocatorNotify9*vmr_surf_alloc_notify; 
            CHECK_HRESULT(filter_->QueryInterface(IID_IVMRSurfaceAllocatorNotify9, reinterpret_cast<void**>(&vmr_surf_alloc_notify))); 


        std::wstring fn;
        ConvertToWstr(fn, fileName);

        // create our surface allocator
        vmr_allocator_ =  new VMR9Allocator(HWnd,fileName);

        // let the allocator and the notify know about each other
        CHECK_HRESULT(vmr_surf_alloc_notify->AdviseSurfaceAllocator(static_cast<DWORD_PTR>(USER_ID),vmr_allocator_));
        CHECK_HRESULT(vmr_allocator_->AdviseNotify(vmr_surf_alloc_notify));
          
        CHECK_HRESULT(graph_->AddFilter(filter_, fn.c_str()));

        { 
            CHECK_HRESULT(graph_->QueryInterface(IID_IMediaControl, reinterpret_cast<void**>(&media_control_))); 
        }
        { 
            CHECK_HRESULT(graph_->QueryInterface(IID_IMediaEvent, reinterpret_cast<void**>(&media_event_))); 
        } 
    
        CHECK_HRESULT(graph_->RenderFile(fn.c_str(), nullptr));  
        { 
            CHECK_HRESULT(graph_->QueryInterface(IID_IMediaSeeking, reinterpret_cast<void**>(&media_seek_))); 
        } 
        state_ = SS_Stopped;

        media_seek_->SetTimeFormat(&TIME_FORMAT_FRAME);  

        CreateMaterial(fileName);
}



View Code

bool ShowVideoTexture::frameStarted(const Ogre::FrameEvent& evt)
{ 
    if(ResetTexture)
    {
        printf("Resetting Texture Materials\n");
        mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTexture( GetTexture());  
        ResetTexture = false;
        return true;
    }

    //mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTexture( GetTexture());  

        currTimeflow+=evt.timeSinceLastFrame;
        if(currTimeflow >mtimeOffset)
        {
            currTimeflow = 0;
            this->PresentTexture();
        }  
//         if(IsComplete()) 
//         {
//             SetPosition(0);
//             Play();
//         }  
        return true;
}



View Code

void ShowVideoTexture::eventOccurred(const Ogre::String &  eventName,const Ogre::NameValuePairList *parameters)
{    
         if (eventName == "DeviceRestored")
        {  
            printf("Resetting Messages\n"); 
            ((VMR9Allocator*)vmr_allocator_)->ResetDecive();
            Play();   
            ResetTexture = true; 
         }
}



View Code

#pragma once

#include "Windows.h"

namespace VMR
{

    class Lock;
    class TryLock;

    class mutex
    {
        protected: 
        mutex(mutex&);
        mutex& operator=(mutex const&);
    public:
        mutex()
        {
           InitializeCriticalSection(&critical_sec);
        }
        ~mutex()
        {
            DeleteCriticalSection(&critical_sec);
        }

        friend Lock;
        friend TryLock;

    private:
        void Lock()
        {
            EnterCriticalSection(&critical_sec);
        }
        void UnLock()
        {
            LeaveCriticalSection(&critical_sec);
        }
        bool TryLock()
        {
            return TryEnterCriticalSection(&critical_sec);
        }

    private:
        CRITICAL_SECTION critical_sec;
    };

    class Lock
    {
    public:
        Lock(mutex&mut):mut_(&mut)
        {
            mut_->Lock();
        }

        ~Lock()
        {
            mut_->UnLock();
        }
    private:
        Lock(Lock&);
        Lock& operator=(Lock const&);
        mutex*mut_;
    }; 



    class TryLock
    {
    public:
        TryLock(mutex&mut):mut_(&mut),islock(false)
        {
            islock = mut_->TryLock();
        }

        ~TryLock()
        {
            if(islock)
            mut_->UnLock();
        }

        bool IsLock()const{return islock;};
    private:
        TryLock(Lock&);
        TryLock& operator=(TryLock const&);
        mutex*mut_;
        bool islock;
    }; 
}

The VMR9Allocator class is used to allocate the VMR9 resources and implement the corresponding interfaces, and is also responsible for the actual Ogre texture update. The ShowVideoTexture class is used to create the Ogre materials and provide the interfaces for external calls. Meanwhile, the ShowVideoTexture class is also responsible for the responses such as Lost of the corresponding D3D Device. Ogre is easy to device lost, and so is THE D3D device of VMR9. Here, I am responsible for the re-initialization of video texture resources after Ogre D3D device lost.
I’m not good at writing, just post the code. Welcome board brick. Here is the effect map:

Read More: