Отображение разных изображений на монитор DirectX 10

Я довольно новичок в программировании на DirectX 10, и я пытался сделать следующее с моими ограниченными навыками (хотя у меня сильный опыт работы с OpenGL)

Я пытаюсь отобразить 2 разных текстурированных квадратора, по одному на монитор. Для этого я понял, что мне нужно одно устройство 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 ответ

Решение

Надеюсь, я понял суть того, что вы хотите сделать - визуализировать разный контент на двух разных мониторах, используя одну графическую карту (графический адаптер), которая отображает свои выходные данные на эти мониторы. Для этого вам понадобится одно устройство (для одной видеокарты / адаптера) и подсчитайте, сколько выходов имеется на компьютере пользователя.

Таким образом, всего - это означает одно устройство, два выхода, два окна и, следовательно, - две цепочки обмена.

Вот быстрый результат моего маленького эксперимента:

Результат

Небольшое введение

В DirectX 10+ это относится к DXGI (графической инфраструктуре DirectX), которая управляет общей низкоуровневой логистикой, связанной с разработкой DirectX 10+, которая, как вы, вероятно, знаете, избавилась от старого требования перечисления наборов функций и тому подобного - требующего каждого Карта с поддержкой DX10+, позволяющая использовать все функции, определенные API. Единственное, что меняется, - это степень и возможности карты (иными словами, паршивая производительность предпочтительнее, чем сбой и сгорание приложения). В прошлом все это было в DirectX 9, но люди в Microsoft решили вытащить его и назвать его DXGI. Теперь мы можем использовать функциональность DXGI для настройки среды с несколькими мониторами.

Нужно ли мне несколько ID3D10RenderTargetView(s)?

Да, вам нужно несколько целевых представлений рендеринга, количество зависит (например, от цепочек обмена и окон) от количества мониторов, которые у вас есть. Но, чтобы уберечь вас от произнесения слов, давайте выпишем это как можно проще и добавим дополнительную информацию, где это необходимо:

  • Перечислите все адаптеры, доступные в системе.
  • Для каждого адаптера перечислите все доступные (и активные) выходы и создайте устройство для его сопровождения.
  • Перечисленные данные хранятся в подходящей структуре (представьте себе массивы, которые могут быстро отказаться от информации о размере), используйте их для создания 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
    }
}

Конечно, не забудьте пройти через все цепочки подкачки и буферы подкачки для каждого окна. Код здесь только для целей этого ответа, он требует немного больше работы, проверки ошибок (failsafes) и созерцания, чтобы заставить его работать так, как вам нравится - другими словами - он должен дать вам упрощенный обзор, а не производственное решение.

Удачи и счастливого кодирования! Боже, это огромно.

Другие вопросы по тегам