Выполнение чтения из Direct3D текстур и поверхностей
Мне нужно выяснить, как получить данные из текстур и поверхностей D3D обратно в системную память. Какой самый быстрый способ сделать такие вещи и как?
Кроме того, если мне нужно только одно подчиненное, как можно прочитать только эту часть без необходимости считывать всю вещь обратно в системную память?
Короче говоря, я ищу краткие описания, как скопировать следующее в системную память:
- текстура
- подмножество текстуры
- поверхность
- подмножество поверхности
- текстура D3DUSAGE_RENDERTARGET
- подмножество текстуры D3DUSAGE_RENDERTARGET
Это Direct3D 9, но ответы о новых версиях D3D также будут оценены по достоинству.
1 ответ
Самая сложная часть - это чтение с некоторой поверхности, которая находится в видеопамяти ("пул по умолчанию"). Это чаще всего рендеринг целей.
Давайте сначала получим простые детали:
- чтение из текстуры аналогично чтению с поверхности уровня 0 этой текстуры. Увидеть ниже.
- то же самое для подмножества текстуры.
- чтение с поверхности, которая находится в пуле памяти не по умолчанию ("системный" или "управляемый"), просто блокирует ее и читает байты.
- то же самое для подмножества поверхности. Просто заблокируйте соответствующую часть и прочитайте ее.
Итак, теперь у нас остались поверхности, которые находятся в видеопамяти ("пул по умолчанию"). Это может быть любая поверхность / текстура, помеченная как цель рендеринга, или любая обычная поверхность / текстура, которую вы создали в пуле по умолчанию, или сам буфер. Сложная часть здесь в том, что вы не можете заблокировать это.
Краткий ответ: метод GetRenderTargetData на устройстве D3D.
Более длинный ответ (грубый набросок кода, который будет ниже):
- rt = получить целевую поверхность рендеринга (это может быть поверхность текстуры, или буфер, и т. д.)
- если rt является мультисэмплированным (GetDesc, проверьте D3DSURFACE_DESC.MultiSampleType), то: a) создайте еще одну целевую поверхность рендеринга того же размера, того же формата, но без мультисэмплинга; б) StretchRect из RT в эту новую поверхность; c) rt = эта новая поверхность (т.е. продолжить на этой новой поверхности).
- off = создать внеэкранную плоскую поверхность (CreateOffscreenPlainSurface, пул D3DPOOL_SYSTEMMEM)
- device-> GetRenderTargetData (rt, off)
- теперь off содержит целевые данные рендера. LockRect(), чтение данных, UnlockRect() на них.
- уборка
Еще более длинный ответ (вставьте из кодовой базы, над которой я работаю) следует. Он не будет компилироваться сразу после установки, потому что он использует некоторые классы, функции, макросы и утилиты из остальной кодовой базы; но это должно помочь вам начать. Я также пропустил большую часть проверки ошибок (например, вне зависимости от ширины / высоты). Я также пропустил часть, которая считывает фактические пиксели и, возможно, преобразует их в подходящий формат назначения (это довольно просто, но может занять много времени, в зависимости от количества преобразований формата, которые вы хотите поддерживать).
bool GfxDeviceD3D9::ReadbackImage( /* params */ )
{
HRESULT hr;
IDirect3DDevice9* dev = GetD3DDevice();
SurfacePointer renderTarget;
hr = dev->GetRenderTarget( 0, &renderTarget );
if( !renderTarget || FAILED(hr) )
return false;
D3DSURFACE_DESC rtDesc;
renderTarget->GetDesc( &rtDesc );
SurfacePointer resolvedSurface;
if( rtDesc.MultiSampleType != D3DMULTISAMPLE_NONE )
{
hr = dev->CreateRenderTarget( rtDesc.Width, rtDesc.Height, rtDesc.Format, D3DMULTISAMPLE_NONE, 0, FALSE, &resolvedSurface, NULL );
if( FAILED(hr) )
return false;
hr = dev->StretchRect( renderTarget, NULL, resolvedSurface, NULL, D3DTEXF_NONE );
if( FAILED(hr) )
return false;
renderTarget = resolvedSurface;
}
SurfacePointer offscreenSurface;
hr = dev->CreateOffscreenPlainSurface( rtDesc.Width, rtDesc.Height, rtDesc.Format, D3DPOOL_SYSTEMMEM, &offscreenSurface, NULL );
if( FAILED(hr) )
return false;
hr = dev->GetRenderTargetData( renderTarget, offscreenSurface );
bool ok = SUCCEEDED(hr);
if( ok )
{
// Here we have data in offscreenSurface.
D3DLOCKED_RECT lr;
RECT rect;
rect.left = 0;
rect.right = rtDesc.Width;
rect.top = 0;
rect.bottom = rtDesc.Height;
// Lock the surface to read pixels
hr = offscreenSurface->LockRect( &lr, &rect, D3DLOCK_READONLY );
if( SUCCEEDED(hr) )
{
// Pointer to data is lt.pBits, each row is
// lr.Pitch bytes apart (often it is the same as width*bpp, but
// can be larger if driver uses padding)
// Read the data here!
offscreenSurface->UnlockRect();
}
else
{
ok = false;
}
}
return ok;
}
SurfacePointer
в приведенном выше коде есть умный указатель на COM-объект (он освобождает объект при назначении или деструкторе). Упрощает обработку ошибок. Это очень похоже на _comptr_t
вещи в Visual C++.
Код выше читает всю поверхность. Если вы хотите эффективно прочитать только часть из них, то я считаю, что самый быстрый способ это примерно:
- создать поверхность пула по умолчанию необходимого размера.
- StretchRect от части оригинальной поверхности к той меньшей.
- продолжайте как обычно с меньшим.
На самом деле это очень похоже на то, что код выше делает для обработки поверхностей с несколькими выборками. Если вы хотите получить только часть поверхности с несколькими выборками, вы можете сделать разрешение с несколькими выборками и получить его часть в одном StretchRect, я думаю.
Редактировать: удаленный фрагмент кода, который выполняет фактическое чтение пикселей и преобразование формата. Не было напрямую связано с вопросом, а код был длинным.
Изменить: обновлено в соответствии с отредактированным вопросом.