- PVSM.RU - https://www.pvsm.ru -
В начале марта я имел удовольствие посетить команду разработки Direct3D в главном офисе Microsoft в Редмонде. По ходу одной из дискуссий об отладке 3D приложений они посоветовали мне использовать новую возможность DirectX1011 для отладки шейдеров.
Я использовал эту технику для отладки кода тесселяции под DirectX 11 (этот код приведён ниже), но и DirectX 10 обладает теми же возможностями и портирование будет достаточно тривиальным.
Нам интересно получить результаты работы выполняемых на GPU шейдеров (вершинных, геометрических, тесселяции) для последующей обработки этих данных с помощью CPU. При этом мы хотим и видеть результаты просчёта графики на экране, и иметь все координаты в виде буферов и структур в оперативной памяти, откуда мы их уже сможем прочитать, записать в лог, использовать для дальнейших расчётов.
Вам нужно выполнить 4 базовых шага:
Модифицировать ваши шейдеры
Нужно добавить к выводу шейдера дополнительные поля, которые мы хотим получить. К примеру, в обычном состоянии ваш шейдер может не выводить world-space координаты, но для отладочного вывода через Stream Out-стадию вы можете их добавить.
Изменить способ создания геометрического шейдера
Конструирование ID3D11GeometryShader (или ID3D10GeometryShader) и добавление его в пайплайн будет происходить иначе.
Создать буфер для получения выходных данных
Достаточно логично — вам же нужно где-то хранить полученные результаты.
Расшифровать результаты
Полученные данные в буфере представляют собой массив структур, каждая из которых содержит информацию о вершине в определённом шейдером формате. Самый простой способ декодировать буфер — объявить структуру в том же формате, а затем привести указатель на начало буффера к указателю на массив вышеуказанных структур.
Итак, модифицируем шейдеры
Как вы, возможно, знаете, Direct3D поддерживает механизм «pass forward». Это означает, что результаты вывода предыдущей стадии пайплайна [1] передаются следующей стадии (и уже никак не возвращаются назад). Таким образом, если вы хотите вывести какие-то дополнительные данные из вершинного шейдера — вам придётся «протянуть» их через HS/DS/GS стадии пайплайна.
Давайте посмотрим на вот такой геометрический шейдер:
struct DS_OUTPUT
{
float4 position : SV_Position;
float3 colour : COLOUR;
float3 uvw : DOMAIN_SHADER_LOCATION;
float3 wPos : WORLD_POSITION;
};
[maxvertexcount(3)]
void gsMain( triangle DS_OUTPUT input[3], inout TriangleStream<DS_OUTPUT> TriangleOutputStream )
{
TriangleOutputStream.Append( input[0] );
TriangleOutputStream.Append( input[1] );
TriangleOutputStream.Append( input[2] );
TriangleOutputStream.RestartStrip();
}
Этот геометрический шейдер полностью «прозрачен» — просто перенаправляет вход на выход. Обратите внимание на структуру DS_OUTPUT — в дальнейшем мы выберем, какие элементы этой структуры мы хотим получить.
Нужно отметить, что ваши пиксельные шейдеры не требуют изменений. В примере выше пиксельный шейдер будет получать только второй параметр структуры — float3 colour: COLOUR и игнорировать все остальные параметры. Таким образом мы будем использовать простейшую идею: все новые поля, которые мы хотим вывести на Stream Out-стадии будут просто добавляться в конец структуры DS_OUTPUT.
Теперь модифицируем процедуру создания геометрического шейдера. Нужно вызвать метод CreateGeometryShaderWithStreamOutput() вместо CreateGeometryShader(), передав ему кроме шейдера структуру D3D11_SO_DECLARATION_ENTRY (или D3D10_SO_DECLARATION_ENTRY — смотря какую версию DirectX вы используете), описывающую формат вершин.
D3D11_SO_DECLARATION_ENTRY soDecl[] =
{
{ 0, "COLOUR", 0, 0, 3, 0 }
, { 0, "DOMAIN_SHADER_LOCATION", 0, 0, 3, 0 }
, { 0, "WORLD_POSITION", 0, 0, 3, 0 }
};
UINT stride = 9 * sizeof(float); // *NOT* sizeof the above array!
UINT elems = sizeof(soDecl) / sizeof(D3D11_SO_DECLARATION_ENTRY);
Нужно обратить внимание на три вещи:
Теперь нужно создать буфер для получения результатов. Он создаётся примерно так же, как вы создаёте вершинные и индексные буферы. Нам нужно два буфера — один доступный для записи с GPU, второй — доступный для чтения с CPU.
D3D11_BUFFER_DESC soDesc;
soDesc.BindFlags = D3D11_BIND_STREAM_OUTPUT;
soDesc.ByteWidth = 10 * 1024 * 1024; // 10mb
soDesc.CPUAccessFlags = 0;
soDesc.Usage = D3D11_USAGE_DEFAULT;
soDesc.MiscFlags = 0;
soDesc.StructureByteStride = 0;
if( FAILED( hr = g_pd3dDevice->CreateBuffer( &soDesc, NULL, &g_pStreamOutBuffer ) ) )
{
/* handle the error here */
return hr;
}
// Simply re-use the above struct
soDesc.BindFlags = 0;
soDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
soDesc.Usage = D3D11_USAGE_STAGING;
if( FAILED( hr = g_pd3dDevice->CreateBuffer( &soDesc, NULL, &g_pStagingStreamOutBuffer ) ) )
{
/* handle the error here */
return hr;
}
Вы не можете вызвать метод Map() на буфере, созданном с флагом D3D11_USAGE_DEFAULT и вы не можете привязать буфер с флагом D3D11_CPU_ACCESS_READ к Stream Out-стадии пайплайна, так что вы создаёте по одному буферу каждого типа и копируете данные из одного в другой.
Теперь привязываем буфер к Stream Out-стадии:
UINT offset = 0;
g_pContext->SOSetTargets( 1, &g_pStreamOutBuffer, &offset );
Ну и давайте наконец прочитаем результаты из буфера:
g_pContext->CopyResource( g_pStagingStreamOutBuffer, g_pStreamOutBuffer );
D3D11_MAPPED_SUBRESOURCE data;
if( SUCCEEDED( g_pContext->Map( g_pStagingStreamOutBuffer, 0, D3D11_MAP_READ, 0, &data ) ) )
{
struct GS_OUTPUT
{
D3DXVECTOR3 COLOUR;
D3DXVECTOR3 DOMAIN_SHADER_LOCATION;
D3DXVECTOR3 WORLD_POSITION;
};
GS_OUTPUT *pRaw = reinterpret_cast< GS_OUTPUT* >( data.pData );
/* Work with the pRaw[] array here */
// Consider StringCchPrintf() and OutputDebugString() as simple ways of printing the above struct, or use the debugger and step through.
g_pContext->Unmap( g_pStagingStreamOutBuffer, 0 );
}
Всё вышеуказанное нужно выполнять после вызова рисования. Нужно быть внимательными со структурой, к указателю на которую вы преобразовываете содержимое буфера (учитывать выравнивание).
Сколько данных получено? Мы можем написать код с использованием запроса D3D11_QUERY_PIPELINE_STATISTICS для того, чтобы это выяснить.
// When initializing/loading
D3D11_QUERY_DESC queryDesc;
queryDesc.Query = D3D11_QUERY_PIPELINE_STATISTICS;
queryDesc.MiscFlags = 0;
if( FAILED( hr = g_pd3dDevice->CreateQuery( &queryDesc, &g_pDeviceStats ) ) )
{
return hr;
}
// When rendering
g_pContext->Begin(g_pDeviceStats);
g_pContext->DrawIndexed( 3, 0, 0 ); // one triangle only
g_pContext->End(g_pDeviceStats);
D3D11_QUERY_DATA_PIPELINE_STATISTICS stats;
while( S_OK != g_pContext->GetData(g_pDeviceStats, &stats, g_pDeviceStats->GetDataSize(), 0 ) );
К сожалению, да.
Автор: tangro
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/otladka/68417
Ссылки в тексте:
[1] пайплайна: http://msdn.microsoft.com/ru-ru/library/windows/desktop/ff476882(v=vs.85).aspx
[2] Источник: http://habrahabr.ru/post/234707/
Нажмите здесь для печати.