Ответ 1
Наиболее привлекательной частью является чтение с некоторой поверхности, которая находится в видеопамяти ( "пул по умолчанию" ). Это чаще всего отображает цели.
Позвольте сначала получить простые части:
- чтение из текстуры такое же, как чтение с 0-уровневой поверхности этой текстуры. См. Ниже.
- то же самое для подмножества текстуры.
- чтение с поверхности, которая находится в пуле памяти не по умолчанию ( "система" или "управляемая" ), просто блокирует его и считывает байты.
- то же самое для подмножества поверхности. Просто заблокируйте соответствующую часть и прочитайте ее.
Итак, теперь мы оставили поверхности, которые находятся в видеопамяти ( "пул по умолчанию" ). Это будет любая поверхность/текстура, помеченная как цель рендеринга, или любая регулярная поверхность/текстура, которые вы создали в пуле по умолчанию, или сам буферный буфер. Сложная часть здесь заключается в том, что вы не можете заблокировать ее.
Короткий ответ: метод GetRenderTargetData на устройстве D3D.
Более длинный ответ (приблизительный контур кода, который будет ниже):
- rt= получить визуализацию целевой поверхности (это может быть поверхность текстуры, буфера и т.д.).
- если rt мультисэмплирован (GetDesc, проверьте D3DSURFACE_DESC.MultiSampleType), затем: a) создайте другую целевую поверхность визуализации того же размера, в том же формате, но без мультисэмплинга; б) StretchRect от rt в эту новую поверхность; c) rt= эта новая поверхность (т.е. перейти на эту новую поверхность).
- off= создать прозрачную гладкую поверхность (CreateOffscreenPlainSurface, пул D3DPOOL_SYSTEMMEM)
- device- > GetRenderTargetData (rt, 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 С++.
Приведенный выше код читает всю поверхность. Если вы хотите достаточно просто прочитать часть его, то я считаю, что самый быстрый способ примерно:
- создать поверхность пула по умолчанию, которая имеет необходимый размер.
- StretchRect от части оригинальной поверхности до меньшей.
- выполняется как обычно с меньшим.
На самом деле это очень похоже на то, что делает код выше для обработки многодисковых поверхностей. Если вы хотите получить только часть многопроцессорной поверхности, вы можете сделать многопроцессорное решение и получить часть его в одном StretchRect, я думаю.
Изменить: удалена часть кода, которая действительно просматривает пиксели и преобразует формат. Не было напрямую связано с вопросом, и код был длинным.
Изменить: обновлено для соответствия редактируемому вопросу.