Выполнение чтения из текстур и поверхностей Direct3D

Мне нужно выяснить, как получить данные из текстур и поверхностей D3D обратно в системную память. Какой самый быстрый способ сделать такие вещи и как?

Также, если мне нужен только один пример, как можно отчитывать только эту часть без необходимости считывать всю информацию в системную память?

Вкратце я ищу краткие описания того, как скопировать следующее в системную память:

  • a текстура
  • a подмножество текстуры
  • a поверхность
  • a подмножество поверхности
  • a текстура D3DUSAGE_RENDERTARGET
  • a подмножество текстуры 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)
  • теперь выкл содержит данные рендеринга. 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, я думаю.

Изменить: удалена часть кода, которая действительно просматривает пиксели и преобразует формат. Не было напрямую связано с вопросом, и код был длинным.

Изменить: обновлено для соответствия редактируемому вопросу.