在DirectX 10中每个显示器显示不同的图片

6
我是一个相对较新的DirectX 10编程者,并且在我的有限技能下一直尝试着做以下事情(尽管我有OpenGL的强大背景)。
我正在尝试显示两个不同的纹理四边形,每个监视器一个。为此,我了解到需要一个单一的D3D10设备,多个(2)交换链和单个VertexBuffer。
虽然我认为我能够创建所有这些,但我仍然不确定如何处理它们。我是否需要多个ID3D10RenderTargetView(s)?我应该在哪里使用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个回答

33
我希望我理解您的意图——使用单个图形卡(图形适配器)将其输出映射到两个显示器上,以在两个不同的监视器上呈现不同的内容。为此,您需要一个设备(用于单个图形卡/适配器),并枚举用户机器上有多少输出。

因此,总共需要一个设备、两个输出、两个窗口,因此还需要两个交换链。

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

The result

简介

在DirectX 10+中,这属于DXGI(DirectX Graphics Infrastructure),它管理与DirectX 10+开发相关的常见低级后勤工作,您可能知道,这摆脱了列举特性集等旧要求,要求每个支持DX10+的卡都要分享API定义的所有功能。唯一变化的是卡的范围和能力(换句话说,糟糕的性能比应用程序崩溃和烧毁更可取)。过去在DirectX 9中就已经完成了所有这些工作,但Microsoft公司的人们决定将其拿出来并称之为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纹理

现在,我们友好的交换链……你可能习惯于通过调用“裸”函数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
    }
}

当然,不要忘记在每个窗口基础上运行所有交换链和交换缓冲区。这里的代码仅用于回答问题的目的,它需要更多的工作、错误检查(安全措施)和思考,以便让它按照您喜欢的方式正常工作 - 换句话说 - 它应该为您提供一个简化的概述,而不是一个生产解决方案。
祝你好运和愉快的编码!天啊,这太庞大了。

太棒了,远超出我的期望!非常感谢,我会尽快尝试整合所有这些有用的信息。 - zeroxgames
那份努力值得赞扬。谢谢 :) - G droid
@user1309389:我已经按照这篇文章的说明尝试将这些东西组合在一起,但我仍然无法在连接到我的电脑的不同显示器上显示不同的图像。 - G droid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接