Выполнение считывания текстур и поверхностей Direct3D
-
02-07-2019 - |
Вопрос
Мне нужно выяснить, как вернуть данные из текстур и поверхностей D3D обратно в системную память.Какой самый быстрый способ сделать такие вещи и как?
Кроме того, если мне нужен только один подзаголовок, как можно прочитать только эту часть без необходимости считывать все в системную память?
Короче говоря, я ищу краткое описание того, как скопировать следующее в системная память:
- а текстура
- а подмножество из текстура
- а поверхность
- а подмножество из поверхность
- а Текстура D3DUSAGE_RENDERTARGET
- а подмножество из Текстура D3DUSAGE_RENDERTARGET
Это Direct3D 9, но ответы о более новых версиях D3D также будут признательны.
Решение
Самая сложная часть — чтение с некоторой поверхности, находящейся в видеопамяти («пул по умолчанию»).Чаще всего это цели рендеринга.
Давайте сначала разберемся с простыми частями:
- чтение из текстуры аналогично чтению с поверхности нулевого уровня этой текстуры.См. ниже.
- то же самое для подмножества текстуры.
- чтение с поверхности, которая находится в пуле памяти, отличном от стандартного («системного» или «управляемого»), означает просто ее блокировку и чтение байтов.
- то же самое для подмножества поверхности.Просто заблокируйте соответствующую часть и прочитайте ее.
Итак, теперь у нас остались поверхности, находящиеся в видеопамяти («пул по умолчанию»).Это может быть любая поверхность/текстура, помеченная как цель рендеринга, или любая обычная поверхность/текстура, созданная вами в пуле по умолчанию, или сам обратный буфер.Сложность здесь в том, что вы не можете его заблокировать.
Короткий ответ: GetRenderTargetData метод на устройстве D3D.
Более длинный ответ (приблизительный вариант кода, который будет ниже):
- рт = получить целевую поверхность рендеринга (это может быть поверхность текстуры, задний буфер и т. д.)
- если рт мультисэмплируется (GetDesc, проверьте D3DSURFACE_DESC.MultiSampleType), тогда:а) создать другую целевую поверхность рендеринга того же размера, того же формата, но без мультисэмплинг;б) StretchRect из рт в эту новую поверхность;в) рт = эта новая поверхность (т.е.продолжить движение по этой новой поверхности).
- выключенный = создать закадровую плоскую поверхность (CreateOffscreenPlainSurface, пул D3DPOOL_SYSTEMMEM)
- устройство->GetRenderTargetData( рт, выключенный )
- сейчас выключенный содержит целевые данные рендеринга.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.
Редактировать:удален фрагмент кода, который выполняет фактическое чтение пикселей и преобразование формата.Не имело прямого отношения к вопросу, а код был длинным.
Редактировать:обновлено в соответствии с отредактированным вопросом.