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