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

3k views Asked by At

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)?

2

There are 2 answers

4
Martin Beckett On BEST ANSWER

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

}
0
SirAndy On

I'm a bit late to this, but I just got the stereoscopic 3D to work using nothing but a GTX 580 and OpenGL. No need for a quadro card or DirectX.

I have the nVidia 3D Vision driver and IR emitter and simply set the emitter to "Always on" in the nVidia control panel.

In my game engine, I switched to a full screen mode with 120Hz and render the scene twice with a slight frustum offset (as per nVidia's own documentation PDF on the manual implementation "2010_GTC2010.pdf").

No quad buffers or any other tricks needed, it works great. Plus, I am in control of all the settings, like convergence etc.