Отображение разных изображений на монитор 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) и созерцания, чтобы заставить его работать так, как вам нравится - другими словами - он должен дать вам упрощенный обзор, а не производственное решение.
Удачи и счастливого кодирования! Боже, это огромно.