Video 3D de Nvidia usando DirectX11 y SlimDX en C #

Buen día, estoy tratando de mostrar un video estéreo en tiempo real usando nvidia 3DVision y dos cámaras IP. Soy totalmente nuevo en DirectX, pero he intentado resolver algunos tutoriales y otras preguntas en este y otros sitios. Por ahora, estoy mostrando dos mapas de bits estáticos para los ojos izquierdo y derecho. Estos serán reemplazados por mapas de bits de mis cámaras una vez que tenga funcionando esta parte de mi programa. Esta pregunta NV_STEREO_IMAGE_SIGNATURE y DirectX 10/11 (nVidia 3D Vision) me ha ayudado bastante, pero todavía estoy luchando para que mi programa funcione como debería. Lo que encuentro es que mis anteojos con obturador comienzan a funcionar como deberían, pero solo se muestra la imagen del ojo derecho, mientras que el ojo izquierdo permanece en blanco (excepto el cursor del mouse).

Aquí está mi código para generar las imágenes estéreo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.Windows;
using SlimDX.DXGI;

using Device = SlimDX.Direct3D11.Device;            // Make sure we use DX11
using Resource = SlimDX.Direct3D11.Resource;

namespace SlimDxTest2
{
static class Program
{
    private static Device device;               // DirectX11 Device
    private static int Count;                   // Just to make sure things are being updated

    // The NVSTEREO header. 
    static byte[] stereo_data = new byte[] {0x4e, 0x56, 0x33, 0x44,   //NVSTEREO_IMAGE_SIGNATURE         = 0x4433564e; 
    0x00, 0x0F, 0x00, 0x00,                                           //Screen width * 2 = 1920*2 = 3840 = 0x00000F00; 
    0x38, 0x04, 0x00, 0x00,                                           //Screen height = 1080             = 0x00000438; 
    0x20, 0x00, 0x00, 0x00,                                           //dwBPP = 32                       = 0x00000020; 
    0x02, 0x00, 0x00, 0x00};                                          //dwFlags = SIH_SCALE_TO_FIT       = 0x00000002

    [STAThread]
    static void Main()
    {

        Bitmap left_im = new Bitmap("Blue.png");        // Read in Bitmaps
        Bitmap right_im = new Bitmap("Red.png");

        // Device creation 
        var form = new RenderForm("Stereo test") { ClientSize = new Size(1920, 1080) };
        var desc = new SwapChainDescription()
        {
            BufferCount = 1,
            ModeDescription = new ModeDescription(1920, 1080, new Rational(120, 1), Format.R8G8B8A8_UNorm),
            IsWindowed = false, //true,
            OutputHandle = form.Handle,
            SampleDescription = new SampleDescription(1, 0),
            SwapEffect = SwapEffect.Discard,
            Usage = Usage.RenderTargetOutput
        };

        SwapChain swapChain;
        Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.Debug, desc, out device, out swapChain);

        RenderTargetView renderTarget;          // create a view of our render target, which is the backbuffer of the swap chain we just created
        using (var resource = Resource.FromSwapChain<Texture2D>(swapChain, 0))
            renderTarget = new RenderTargetView(device, resource);

        var context = device.ImmediateContext;                  // set up a viewport
        var viewport = new Viewport(0.0f, 0.0f, form.ClientSize.Width, form.ClientSize.Height);
        context.OutputMerger.SetTargets(renderTarget);
        context.Rasterizer.SetViewports(viewport);

        // prevent DXGI handling of alt+enter, which doesn't work properly with Winforms
        using (var factory = swapChain.GetParent<Factory>())
            factory.SetWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAll);

        form.KeyDown += (o, e) =>                   // handle alt+enter ourselves
        {
            if (e.Alt && e.KeyCode == Keys.Enter)
                swapChain.IsFullScreen = !swapChain.IsFullScreen;
        };

        form.KeyDown += (o, e) =>                   // Alt + X -> Exit Program
        {
            if (e.Alt && e.KeyCode == Keys.X)
            {
                form.Close();
            }
        };

        context.ClearRenderTargetView(renderTarget, Color.Green);       // Fill Screen with specified colour

        Texture2DDescription stereoDesc = new Texture2DDescription()
        {
            ArraySize = 1,
            Width = 3840,
            Height = 1081,
            BindFlags = BindFlags.None,
            CpuAccessFlags = CpuAccessFlags.Write,
            Format = SlimDX.DXGI.Format.R8G8B8A8_UNorm,
            OptionFlags = ResourceOptionFlags.None,
            Usage = ResourceUsage.Staging,
            MipLevels = 1,
            SampleDescription = new SampleDescription(1, 0)
        };

        // Main Loop 
        MessagePump.Run(form, () =>
        {
            Texture2D texture_stereo =  Make3D(left_im, right_im);      // Create Texture from two bitmaps in memory
            ResourceRegion stereoSrcBox = new ResourceRegion { Front = 0, Back = 1, Top = 0, Bottom = 1080, Left = 0, Right = 1920 };
            context.CopySubresourceRegion(texture_stereo, 0, stereoSrcBox, renderTarget.Resource, 0, 0, 0, 0);
            texture_stereo.Dispose();

            swapChain.Present(0, PresentFlags.None);
        });

        // Dispose resources 

        swapChain.IsFullScreen = false;     // Required before swapchain dispose
        device.Dispose();
        swapChain.Dispose();
        renderTarget.Dispose();

    }



    static Texture2D Make3D(Bitmap leftBmp, Bitmap rightBmp)
    {
        var context = device.ImmediateContext;
        Bitmap left2 = leftBmp.Clone(new RectangleF(0, 0, leftBmp.Width, leftBmp.Height), PixelFormat.Format32bppArgb);     // Change bmp to 32bit ARGB
        Bitmap right2 = rightBmp.Clone(new RectangleF(0, 0, rightBmp.Width, rightBmp.Height), PixelFormat.Format32bppArgb);

        // Show FrameCount on screen: (To test)
        Graphics left_graph = Graphics.FromImage(left2);
        left_graph.DrawString("Frame: " + Count.ToString(), new System.Drawing.Font("Arial", 16), Brushes.Black, new PointF(100, 100));
        left_graph.Dispose();

        Graphics right_graph = Graphics.FromImage(right2);
        right_graph.DrawString("Frame: " + Count.ToString(), new System.Drawing.Font("Arial", 16), Brushes.Black, new PointF(200, 200));
        right_graph.Dispose();
        Count++;

        Texture2DDescription desc2d = new Texture2DDescription()
        {
            ArraySize = 1,
            Width = 1920,
            Height = 1080,
            BindFlags = BindFlags.None,
            CpuAccessFlags = CpuAccessFlags.Write,
            Format = SlimDX.DXGI.Format.R8G8B8A8_UNorm,
            OptionFlags = ResourceOptionFlags.None,
            Usage = ResourceUsage.Staging,
            MipLevels = 1,
            SampleDescription = new SampleDescription(1, 0)
        };

        Texture2D leftText2 = new Texture2D(device, desc2d);        // Texture2D for each bmp
        Texture2D rightText2 = new Texture2D(device, desc2d);

        Rectangle rect = new Rectangle(0, 0, left2.Width, left2.Height);
        BitmapData leftData = left2.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        IntPtr left_ptr = leftData.Scan0;
        int left_num_bytes = Math.Abs(leftData.Stride) * leftData.Height;
        byte[] left_bytes = new byte[left_num_bytes];
        byte[] left_bytes2 = new byte[left_num_bytes];

        System.Runtime.InteropServices.Marshal.Copy(left_ptr, left_bytes, 0, left_num_bytes);       // Get Byte array from bitmap
        left2.UnlockBits(leftData);
        DataBox box1 = context.MapSubresource(leftText2, 0, MapMode.Write, SlimDX.Direct3D11.MapFlags.None);
        box1.Data.Write(left_bytes, 0, left_bytes.Length);
        context.UnmapSubresource(leftText2, 0);

        BitmapData rightData = right2.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        IntPtr right_ptr = rightData.Scan0;
        int right_num_bytes = Math.Abs(rightData.Stride) * rightData.Height;
        byte[] right_bytes = new byte[right_num_bytes];

        System.Runtime.InteropServices.Marshal.Copy(right_ptr, right_bytes, 0, right_num_bytes);       // Get Byte array from bitmap
        right2.UnlockBits(rightData);
        DataBox box2 = context.MapSubresource(rightText2, 0, MapMode.Write, SlimDX.Direct3D11.MapFlags.None);
        box2.Data.Write(right_bytes, 0, right_bytes.Length);
        context.UnmapSubresource(rightText2, 0);

        Texture2DDescription stereoDesc = new Texture2DDescription()
        {
            ArraySize = 1,
            Width = 3840,
            Height = 1081,
            BindFlags = BindFlags.None,
            CpuAccessFlags = CpuAccessFlags.Write,
            Format = SlimDX.DXGI.Format.R8G8B8A8_UNorm,
            OptionFlags = ResourceOptionFlags.None,
            Usage = ResourceUsage.Staging,
            MipLevels = 1,
            SampleDescription = new SampleDescription(1, 0)
        };
        Texture2D stereoTexture = new Texture2D(device, stereoDesc);    // Texture2D to contain stereo images and Nvidia 3DVision Signature

        // Identify the source texture region to copy (all of it) 
        ResourceRegion stereoSrcBox = new ResourceRegion { Front = 0, Back = 1, Top = 0, Bottom = 1080, Left = 0, Right = 1920 };

        // Copy it to the stereo texture 
        context.CopySubresourceRegion(leftText2, 0, stereoSrcBox, stereoTexture, 0, 0, 0, 0);
        context.CopySubresourceRegion(rightText2, 0, stereoSrcBox, stereoTexture, 0, 1920, 0, 0);   // Offset by 1920 pixels

        // Open the staging texture for reading and go to last row
        DataBox box = context.MapSubresource(stereoTexture, 0, MapMode.Write, SlimDX.Direct3D11.MapFlags.None);
        box.Data.Seek(stereoTexture.Description.Width * (stereoTexture.Description.Height - 1) * 4, System.IO.SeekOrigin.Begin);
        box.Data.Write(stereo_data, 0, stereo_data.Length);            // Write the NVSTEREO header 
        context.UnmapSubresource(stereoTexture, 0);

        left2.Dispose();
        leftText2.Dispose();
        right2.Dispose();
        rightText2.Dispose();
        return stereoTexture;
    } 

}

}

Probé varios métodos para copiar la textura 2D de la imagen estéreo, incluida la firma (3840x1081) en el backbuffer, pero ninguno de los métodos que probé muestra ambas imágenes... Cualquier ayuda o comentario será muy apreciado, Ryan

preguntado el 30 de junio de 12 a las 20:06

Intenté volver a Direct3D 9 (para poder usar stretchrect), pero ahora tengo problemas para que el programa se ejecute en modo de pantalla completa. Tan pronto como configuro presentparams.Windowed = false, el programa falla cuando creo mi cadena de intercambio. Recibo el siguiente error: D3DERR_INVALIDCALL (-2005530516). Si sirve de algo, estoy usando una computadora portátil Dell XPS17 con un transmisor 3D incorporado... -

Ok, he logrado que funcione usando SlimDX y Direct3D 9. Solo creo un dispositivo usando mis presentes parámetros, y no creo una cadena de intercambio (lo que estaba causando que mi programa fallara cuando intentaba iniciarse en modo de pantalla completa). Pensé que se requería una cadena de intercambio al crear un dispositivo, pero parece que no. Por ahora, me apegaré a Direct3D 9 y haré que el resto de mi programa funcione (conectar las dos cámaras y sincronizar todo, etc.). Todavía será bueno que funcione en Direct3D11, pero eso tendrá que esperar. -

En el ciclo principal, tiene ResourceRegion Bottom = 1080 y Right = 1920, ¿no debería haber right = 1920 * 2? -

4 Respuestas

Si usar DirectX11.1 es una opción, hay una manera mucho más fácil de habilitar las funciones estereoscópicas, sin tener que depender de la magia de bytes de nVidia. Básicamente, creas un SwapChan1 en lugar de un regular SwapChain, entonces es tan simple como establecer Stereo a la verdad

Echa un vistazo a esto post Hice, te muestra cómo crear un Stereo swapChain. El código es una migración a C# de la propia muestra estéreo de MS. Entonces tendrás dos objetivos de renderizado y es mucho más simple. Antes de renderizar tienes que:

void RenderEye(bool rightEye, ITarget target)
{
    RenderTargetView currentTarget = rightEye ? target.RenderTargetViewRight : target.RenderTargetView;
    context.OutputMerger.SetTargets(target.DepthStencilView, currentTarget);
    [clean color/depth]
    [render scene]
    [repeat for each eye]
}

donde ITarget es una interfaz para una clase que brinda acceso al backbuffer, rendertargets, etc. Eso es todo, DirectX se encargará de todo. Espero que esto ayude.

Respondido 15 Jul 13, 10:07

Intente crear el backbufer con ancho = 1920 y no 3840. estire cada imagen a la mitad del ancho y colóquelas una al lado de la otra.

Respondido 19 Jul 12, 09:07

Gracias por el consejo. Como mencioné en los comentarios anteriores, lo tengo funcionando con D3D 9. Mi backbuffer está configurado con ancho = 1920, pero el problema que tengo es que no hay una función equivalente a StretchRectangle() en D3D 10 y 11. ¿Cómo ¿Estiras la imagen de 3840 píxeles de ancho hasta un búfer trasero de 1920 píxeles de ancho sin StretchRectangle ()? Intenté usar CopySubResourceRegion(), pero solo se puede especificar el tamaño de la fuente y no consigo que funcione... - ryan luke

Recuerdo haber visto exactamente esta misma pregunta mientras buscaba hace un par de días en los foros de desarrolladores de Nvidia. Desafortunadamente, los foros están caídos debido a un reciente ataque de piratas informáticos. Recuerdo que el OP en ese hilo pudo hacerlo funcionar con DX11 y Slimdx usando el truco de la firma. No usas el método stretchRectangle, era algo así como createResuroseRegion() o pero no exactamente, no lo recuerdo. Podrían ser estos métodos CopyResource() o CopySubresourceRegion() que se encuentran en este hilo similar en stack over flow. Copiar textura a textura

contestado el 23 de mayo de 17 a las 13:05

¿También estás renderizando la imagen continuamente o al menos unas cuantas veces? Estaba haciendo lo mismo en DX9 y tuve que decirle a DX que renderizara 3 fotogramas antes de que el controlador lo reconociera como visión 3D. ¿Se te encendieron las gafas? ¿Es su backbuffer = (ancho * 2), (Altura + 1) y está escribiendo el backbuffer así:

_________________________
|           |            |      
|  img1     |     img2   |
|           |            |
--------------------------
|_______signature________| where this last row = 1 pix tall

Respondido 21 Jul 12, 13:07

Hola. Sí, estoy renderizando continuamente. Como mencioné en mis comentarios anteriores, lo hice funcionar con DX9. Ahora también tengo las dos cámaras funcionando y funciona muy bien. El búfer intermedio con el que trabajo se ve como lo describiste anteriormente, pero el búfer trasero en el que escribo esto usando StretchRectangle() es solo 1920x1080, y no duplica el ancho y el alto + 1. Creo que el controlador nvidia lo detecta de inmediato, pero toma un rato para que el transmisor IR se encienda y las gafas empiecen a funcionar. No me voy a molestar más en esta etapa para intentar que funcione en DX11. - ryan luke

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.