每台显示器显示不同的图像directX 10

时间:2012-05-24 20:26:51

标签: c++ directx multiple-monitors

我是DirectX 10编程的新手,我一直尝试用我有限的技能做以下事情(虽然我有很强的OpenGL背景)

我正在尝试显示2个不同的纹理四边形,每个显示器1个。为此,我明白我需要一个D3D10设备,多个(2)交换链和单个VertexBuffer

虽然我认为我能够创造所有这些,但我仍然不确定如何处理所有这些。我需要多个ID3D10RenderTargetView吗?我应该如何以及在哪里使用OMSetRenderTargets(...)?

除了MSDN之外,这些概念的文档或解释相当有限,因此非常欢迎任何帮助。这是我的一些代码:

这是渲染代码

for(int i = 0; i < screenNumber; i++){
    //clear scene
    pD3DDevice->ClearRenderTargetView( pRenderTargetView, D3DXCOLOR(0,1,0,0) );

    //fill vertex buffer with vertices
    UINT numVertices = 4;   
    vertex* v = NULL;   

    //lock vertex buffer for CPU use
    pVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**) &v );

    v[0] = vertex( D3DXVECTOR3(-1,-1,0), D3DXVECTOR4(1,0,0,1), D3DXVECTOR2(0.0f, 1.0f) );
    v[1] = vertex( D3DXVECTOR3(-1,1,0), D3DXVECTOR4(0,1,0,1), D3DXVECTOR2(0.0f, 0.0f) );
    v[2] = vertex( D3DXVECTOR3(1,-1,0), D3DXVECTOR4(0,0,1,1), D3DXVECTOR2(1.0f, 1.0f) );
    v[3] = vertex( D3DXVECTOR3(1,1,0), D3DXVECTOR4(1,1,0,1), D3DXVECTOR2(1.0f, 0.0f) ); 

    pVertexBuffer->Unmap();

    // Set primitive topology 
    pD3DDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP );

    //set texture
    pTextureSR->SetResource( textureSRV[textureIndex] );

    //get technique desc
    D3D10_TECHNIQUE_DESC techDesc;
    pBasicTechnique->GetDesc( &techDesc );


    // This is where you actually use the shader code
    for( UINT p = 0; p < techDesc.Passes; ++p )
    {
        //apply technique
        pBasicTechnique->GetPassByIndex( p )->Apply( 0 );

        //draw
        pD3DDevice->Draw( numVertices, 0 );
    }

    //flip buffers
    pSwapChain[i]->Present(0,0);
}

这是创建渲染目标的代码,我不确定是不是很好

for(int i = 0; i < screenNumber; ++i){

    //try to get the back buffer
    ID3D10Texture2D* pBackBuffer;   
    if ( FAILED( pSwapChain[1]->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*) &pBackBuffer) ) ) return fatalError("Could not get back buffer");

    //try to create render target view
    if ( FAILED( pD3DDevice->CreateRenderTargetView(pBackBuffer, NULL, &pRenderTargetView) ) ) return fatalError("Could not create render target view");

    pBackBuffer->Release();
    pD3DDevice->OMSetRenderTargets(1, &pRenderTargetView, NULL);
}

return true;

}

1 个答案:

答案 0 :(得分:30)

我希望我明白你想做什么 - 在两个不同的显示器上呈现不同的内容,同时使用单个图形卡(图形适配器)将其输出映射到这些显示器。为此,您将需要一个设备(用于单个图形卡/适配器)并枚举用户机器上的输出数量。

因此,总的来说 - 这意味着一个设备,两个输出,两个窗口,因此 - 两个交换链。

这是我的小实验的快速结果:

The result

一点介绍

使用DirectX 10+,这属于DXGI(DirectX图形基础设施),它管理与DirectX 10+开发相关的常见低级物流,正如您可能知道的那样,抛弃了枚举功能集等的旧要求。 - 要求每张支持DX10 +的卡分享API定义的所有功能。唯一不同的是卡的范围和能力(换句话说,糟糕的性能比应用程序崩溃和刻录更好)。这一切都在DirectX 9中,但微软的人们决定将其推出并称之为DXGI。现在,我们可以使用DXGI功能来设置我们的多监视器环境。

  

我是否需要多个ID3D10RenderTargetView?

是的,您确实需要多个渲染目标视图,计数取决于您拥有的监视器数量(如交换链和窗口)。但是,为了避免出现呕吐词,让我们尽可能简单地写出来并在需要的地方写下其他信息:

  • 枚举系统上可用的所有适配器。
  • 对于每个适配器,枚举所有可用的输出(和活动的)并创建一个随附的设备。
  • 将枚举数据存储在合适的结构中(想想可以快速放弃大小信息的数组),使用它来创建n个窗口,交换链,渲染目标视图,深度/模板纹理以及它们各自的视图,其中n等于产出数量。
  • 创建所有内容后,对于要渲染的每个窗口,您可以定义特殊例程,这些例程将使用可用的几何(和其他)数据输出结果 - 这将解析为每个监视器在全屏中获取的内容(不要忘记相应地调整每个窗口的视口)。
  • 通过迭代链接到其各自窗口的每个交换链以及使用Present()
  • 交换缓冲区来呈现数据

现在,虽然字数很丰富,但有些代码值得更多。这旨在让您大致了解实现简单多监视器应用程序的内容。因此,假设只有一个适配器(现在是一个相当大胆的声明)和多个输出 - 并且没有故障。我会把有趣的部分留给你。 回答第二个问题是在楼下......

请注意,不涉及内存管理。我们假设在出于说明目的不需要时,所有内容都会被神奇地清理干净。做个好记忆的公民。

获取适配器

IDXGIAdapter* adapter = NULL;
void GetAdapter() // applicable for multiple ones with little effort
{
    // remember, we assume there's only one adapter (example purposes)
    for( int i = 0; DXGI_ERROR_NOT_FOUND != factory->EnumAdapters( i, &adapter ); ++i )
    {

        // get the description of the adapter, assuming no failure
        DXGI_ADAPTER_DESC adapterDesc;
        HRESULT hr = adapter->GetDesc( &adapterDesc );

        // Getting the outputs active on our adapter
        EnumOutputsOnAdapter();

}

获取适配器上的输出

std::vector<IDXGIOutput*> outputArray; // contains outputs per adapter
void EnumOutputsOnAdapter()
{
    IDXGIOutput* output = NULL;
    for(int i = 0; DXGI_ERROR_NOT_FOUND != adapter->EnumOutputs(i, &output); ++i)
    {

        // get the description
        DXGI_OUTPUT_DESC outputDesc;
        HRESULT hr = output->GetDesc( &outputDesc );

        outputArray.push_back( output );
    }

}

现在,我必须假设您至少了解Win32 API注意事项,创建窗口类,注册系统,创建窗口等等......因此,我不会限定其创建,只有精心制作它是如何与多个窗口相关的。此外,我将仅考虑全屏情况,但在窗口模式下创建它是可能的,而且相当简单。

为我们的输出创建实际窗口

由于我们假设只存在一个适配器,我们只考虑链接到该特定适配器的枚举输出。最好以简洁的小结构组织所有窗口数据,但是为了这个答案的目的,我们只是把它们推到一个简单的结构中,然后再推入另一个std :: vector对象,我的意思是处理各个窗口(HWND)及其大小(虽然对于我们的情况它是常量)。

但是,我们必须解决这样一个事实:我们有一个交换链,一个渲染目标视图,每个窗口一个深度/模板视图。那么,为什么不在描述我们每个窗口的那个小结构中提供所有这些呢?有道理,对吧?

struct WindowDataContainer
{
    //Direct3D 10 stuff per window data
    IDXGISwapChain* swapChain;
    ID3D10RenderTargetView* renderTargetView;
    ID3D10DepthStencilView* depthStencilView;

    // window goodies
    HWND hWnd;
    int width;
    int height;
}

尼斯。好吧,不是真的。但仍然......继续前进!现在为输出创建窗口:

std::vector<WindowDataContainer*> windowsArray;
void CreateWindowsForOutputs()
{
    for( int i = 0; i < outputArray.size(); ++i )
    {

        IDXGIOutput* output = outputArray.at(i);
        DXGI_OUTPUT_DESC outputDesc;
        p_Output->GetDesc( &outputDesc );
        int x = outputDesc.DesktopCoordinates.left;
        int y = outputDesc.DesktopCoordinates.top;
        int width = outputDesc.DesktopCoordinates.right - x;
        int height = outputDesc.DesktopCoordinates.bottom - y;

        // Don't forget to clean this up. And all D3D COM objects.
        WindowDataContainer* window = new WindowDataContainer;

        window->hWnd = CreateWindow( windowClassName,
                                        windowName,
                                        WS_POPUP,
                                        x,
                                        y,
                                        width,
                                        height,
                                        NULL,
                                        0,
                                        instance,
                                        NULL );

        // show the window
        ShowWindow( window->hWnd, SW_SHOWDEFAULT );

        // set width and height
        window->width = width;
        window->height = height;

        // shove it in the std::vector
        windowsArray.push_back( window );

        //if first window, associate it with DXGI so it can jump in
        // when there is something of interest in the message queue
        // think fullscreen mode switches etc. MSDN for more info.
        if(i == 0)
            factory->MakeWindowAssociation( window->hWnd, 0 );

    }
}

可爱,现在已经完成了。由于我们只有一个适配器,因此只有一个设备可以随身携带,所以请照常创建。在我的例子中,它只是一个全局接口指针,可以在任何地方访问。我们不打算在这里找到年度代码,为什么不呢,呃?

创建交换链,视图和深度/模板2D纹理

现在,我们友好的交换链......您可能习惯于通过调用&#34;裸体&#34;来实际创建它们。功能D3D10CreateDeviceAndSwapChain(...),但如您所知,我们已经制作了我们的设备。我们只想要一个。和多个交换链。嗯,这是一个泡菜。幸运的是,我们的DXGIFactory界面在其生产线上有交换链,我们可以通过补充的朗姆酒桶免费获得。然后在交换链上为每个窗口创建一个:

void CreateSwapChainsAndViews()
{
    for( int i = 0; i < windowsArray.size(); i++ )
    {
        WindowDataContainer* window = windowsArray.at(i);

        // get the dxgi device
        IDXGIDevice* DXGIDevice = NULL;
        device->QueryInterface( IID_IDXGIDevice, ( void** )&DXGIDevice ); // COM stuff, hopefully you are familiar

        // create a swap chain
        DXGI_SWAP_CHAIN_DESC swapChainDesc;

        // fill it in

        HRESULT hr = factory->CreateSwapChain( DXGIDevice, &swapChainDesc, &p_Window->swapChain );
        DXGIDevice->Release();
        DXGIDevice = NULL;

         // get the backbuffer
        ID3D10Texture2D* backBuffer = NULL;
        hr = window->swapChain->GetBuffer( 0, IID_ID3D10Texture2D, ( void** )&backBuffer );

        // get the backbuffer desc
        D3D10_TEXTURE2D_DESC backBufferDesc;
        backBuffer->GetDesc( &backBufferDesc );

        // create the render target view
        D3D10_RENDER_TARGET_VIEW_DESC RTVDesc;

        // fill it in

        device->CreateRenderTargetView( backBuffer, &RTVDesc, &window->renderTargetView );
        backBuffer->Release();
        backBuffer = NULL;

        // Create depth stencil texture
        ID3D10Texture2D* depthStencil = NULL;
        D3D10_TEXTURE2D_DESC descDepth;

        // fill it in


        device->CreateTexture2D( &descDepth, NULL, &depthStencil );

        // Create the depth stencil view
        D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;

        // fill it in

        device->CreateDepthStencilView( depthStencil, &descDSV, &window->depthStencilView );

    }

}

我们现在拥有所需的一切。您需要做的就是定义一个迭代所有窗口的函数,并适当地绘制不同的东西。

  

我应该如何以及在哪里使用OMSetRenderTargets(...)?

在刚才提到的函数中,迭代所有窗口并使用适当的渲染目标(由我们的每个窗口数据容器提供):

void MultiRender( )
{
    // Clear them all
    for( int i = 0; i < windowsArray.size(); i++ )
    {
        WindowDataContainer* window = windowsArray.at(i);

        // There is the answer to your second question:
        device->OMSetRenderTargets( 1, &window->renderTargetView, window->depthStencilView );

        // Don't forget to adjust the viewport, in fullscreen it's not important...
        D3D10_VIEWPORT Viewport;
        Viewport.TopLeftX = 0;
        Viewport.TopLeftY = 0;
        Viewport.Width = window->width;
        Viewport.Height = window->height;
        Viewport.MinDepth = 0.0f;
        Viewport.MaxDepth = 1.0f;
        device->RSSetViewports( 1, &Viewport );

        // TO DO: AMAZING STUFF PER WINDOW
    }
}

当然,不要忘记每个窗口运行所有交换链和交换缓冲区。这里的代码只是为了这个答案的目的,它需要更多的工作,错误检查(failsafes)和沉思让它按照你喜欢的方式工作 - 换句话说 - 它应该给你一个简化的概述,而不是生产解决方案。

祝你好运,编码愉快! Sheesh,这是巨大的。