Search code examples
stereoscopy

How do I program a stereo-capable graphics card to display stereo images?


I'd like to write my own stereo image viewer, because there are certain features I need which are missing from the one bundled with my NVidia/EVGA GTX 580.

I can't figure out how to program the card to enter "shutterglass" mode where every other frame (at 120 HZ) alternates left and right.

I've looked at the OpenGL, Direct3D, and XNA APIs, as well as information from NVIDIA, and can't figure out how to get started. How do I set separate left and right images, how do I tell the screen to display it, and how to I tell the driver to activate the shutterglass transmitter?

(Another disconcerting thing is that whenever I use the bundled software to view stereo images and video in shutterglass mode, it's in fullscreen, and the screen blinks when entering that mode--even though I run the screen at 120Hz in 2D. Is there a way to have a 3D surface in a window without upsetting the rest of the screen on the NVidia "gamer" cards that are 3D capable (570, 580)?


Solution

  • For the NVidia 3Dvision with the GEForce range you need to write a full screen directX surface twice the width of the display with the left image on the left,right on the right (duh).
    Then you need to write a magic value into the bottom left of the image which the NVision driver picks up and turns on the glasses, you don't need the nvapi.dll

    With the Nvidia pro glasses and a Quadra card you can use the regular OpenGL stereo API.

    ps.I did find some sample code that manages to do this with a normal window.
    Edit - it was a low level USB code talking to the xmitter that I could never get to build, I think it eventually became this http://sourceforge.net/projects/libnvstusb/

    Here is some sample code for full screen with the NVision glasses.
    I'm not a DirectX expert so some of this might be less than optimal.
    My app is also based on Qt, there might be some Qt bits left in the code

    -----------------------------------------------------------------
        // header
        void create3D();
        void set3D();
        IDirect3D9 *_d3d;
        IDirect3DDevice9 *_d3ddev;
        QSize _size; // full screen size 
    
        IDirect3DSurface9 *_imageBuf; //Source stereo image
        IDirect3DSurface9 *_backBuf;    
    
    
    --------------------------------------------------------
       // the code     
    #include <windows.h>
    #include <windowsx.h>
    #include <d3d9.h>
    #include <d3dx9.h>
    #include <strsafe.h>
    
    #pragma comment (lib, "d3d9.lib")
    
    #define NVSTEREO_IMAGE_SIGNATURE 0x4433564e //NV3D
    
    typedef struct _Nv_Stereo_Image_Header
    {
    unsigned int dwSignature;
    unsigned int dwWidth;
    unsigned int dwHeight;
    unsigned int dwBPP;
    unsigned int dwFlags;
    } NVSTEREOIMAGEHEADER, *LPNVSTEREOIMAGEHEADER;
    
    
    // ORedflags in the dwFlagsfielsof the _Nv_Stereo_Image_Headerstructure above
    #define SIH_SWAP_EYES 0x00000001
    #define SIH_SCALE_TO_FIT 0x00000002
    
    // call at start to set things up
    void DisplayWidget::create3D()
    {
            _size = QSize(1680,1050); //resolution of my Samsung 2233z
    
            _d3d = Direct3DCreate9(D3D_SDK_VERSION);    // create the Direct3D interface
    
            D3DPRESENT_PARAMETERS d3dpp;    // create a struct to hold various device information
    
            ZeroMemory(&d3dpp, sizeof(d3dpp));    // clear out the struct for use
            d3dpp.Windowed = FALSE;    // program fullscreen
            d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;    // discard old frames
            d3dpp.hDeviceWindow = winId();    // set the window to be used by Direct3D
            d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;  // set the back buffer format to 32 bit // or D3DFMT_R8G8B8
            d3dpp.BackBufferWidth = _size.width();
            d3dpp.BackBufferHeight = _size.height();
            d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
            d3dpp.BackBufferCount = 1;
    
            // create a device class using this information and information from the d3dpp stuct
            _d3d->CreateDevice(D3DADAPTER_DEFAULT,
                              D3DDEVTYPE_HAL,
                              winId(),
                              D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                              &d3dpp,
                              &_d3ddev);
    
    
        //3D VISION uses a single surface 2x images wide and image high
        // create the surface 
        _d3ddev->CreateOffscreenPlainSurface(_size.width()*2, _size.height(), D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &_imageBuf, NULL);
    
        set3D();
    
    }
    
    // call to put 3d signature in image
    void DisplayWidget::set3D()
    {
    
        // Lock the stereo image
        D3DLOCKED_RECT lock;
        _imageBuf->LockRect(&lock,NULL,0);
    
        // write stereo signature in the last raw of the stereo image
        LPNVSTEREOIMAGEHEADER pSIH = (LPNVSTEREOIMAGEHEADER)(((unsigned char *) lock.pBits) + (lock.Pitch * (_size.height()-1)));
    
        // Update the signature header values
        pSIH->dwSignature = NVSTEREO_IMAGE_SIGNATURE;
        pSIH->dwBPP = 32;
        //pSIH->dwFlags = SIH_SWAP_EYES; // Src image has left on left and right on right, thats why this flag is not needed.
        pSIH->dwFlags = SIH_SCALE_TO_FIT;
        pSIH->dwWidth = _size.width() *2;
        pSIH->dwHeight = _size.height();
    
        // Unlock surface
        _imageBuf->UnlockRect();
    
    }
    
    // call in display loop
    void DisplayWidget::paintEvent()
    {
        // clear the window to a deep blue
        //_d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
    
        _d3ddev->BeginScene();    // begins the 3D scene
    
        // do 3D rendering on the back buffer here
        RECT destRect;
        destRect.left = 0;
        destRect.top = 0;
        destRect.bottom = _size.height();
        destRect.right = _size.width();
    
        // Get the Backbuffer then Stretch the Surface on it.
        _d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &_backBuf);
        _d3ddev->StretchRect(_imageBuf, NULL, _backBuf, &destRect, D3DTEXF_NONE); 
        _backBuf->Release();
    
        _d3ddev->EndScene();    // ends the 3D scene
    
        _d3ddev->Present(NULL, NULL, NULL, NULL);    // displays the created frame
    }
    
    // my images come from a camera
    // _left and _right are QImages but it should be obvious what the functions do  
    void DisplayWidget::getImages()
    {
                    RECT srcRect;
                    srcRect.left = 0;
                    srcRect.top = 0;
                    srcRect.bottom = _size.height();
                    srcRect.right = _size.width();
    
                    RECT destRect;              
                    destRect.top = 0;
                    destRect.bottom = _size.height();
    
                    if ( isOdd() ) {                    
                        destRect.left = _size.width();
                        destRect.right = _size.width()*2;
                        // get camera data for _left here, code not shown               
                        D3DXLoadSurfaceFromMemory(_imageBuf, NULL, &destRect,_right.bits(),D3DFMT_A8R8G8B8,_right.bytesPerLine(),NULL,&srcRect,D3DX_DEFAULT,0);         
                    } else {
                        destRect.left = 0;
                        destRect.right = _size.width();
                        // get camera data for _right here, code not shown          
                        D3DXLoadSurfaceFromMemory(_imageBuf, NULL, &destRect,_left.bits(),D3DFMT_A8R8G8B8,_left.bytesPerLine(),NULL,&srcRect,D3DX_DEFAULT,0);           
                    }
    
    
                    set3D();    // add NVidia signature
    
    }
    
    DisplayWidget::~DisplayWidget()
    {
        _d3ddev->Release();    // close and release the 3D device
        _d3d->Release();    // close and release Direct3D
    
    }