Выполнение считывания с текстур и поверхностей Direct3D - PullRequest
15 голосов
/ 23 сентября 2008

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

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

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

  1. a текстура
  2. a подмножество текстуры
  3. a поверхность
  4. подмножество поверхности
  5. a D3DUSAGE_RENDERTARGET текстура
  6. подмножество D3DUSAGE_RENDERTARGET текстуры

Это Direct3D 9, но ответы о более новых версиях D3D также приветствуются.

1 Ответ

29 голосов
/ 23 сентября 2008

Наиболее важной частью является чтение с некоторой поверхности, которая находится в видеопамяти («пул по умолчанию»). Это чаще всего рендеринг целей.

Давайте сначала разберемся с простыми деталями:

  1. чтение из текстуры такое же, как чтение с поверхности 0-уровня этой текстуры. Смотри ниже.
  2. то же самое для подмножества текстуры.
  3. чтение с поверхности, которая находится в пуле памяти не по умолчанию («системный» или «управляемый»), просто блокирует ее и читает байты.
  4. то же самое для подмножества поверхности. Просто заблокируйте соответствующую часть и прочитайте ее.

Итак, теперь у нас остались поверхности, которые находятся в видеопамяти («пул по умолчанию»). Это может быть любая поверхность / текстура, помеченная как цель рендеринга, или любая обычная поверхность / текстура, которую вы создали в пуле по умолчанию, или сам буфер. Сложная часть здесь в том, что вы не можете заблокировать это.

Краткий ответ: GetRenderTargetData метод на устройстве D3D.

Более длинный ответ (грубый набросок кода, который будет ниже):

  1. rt = получить целевую поверхность рендеринга (это может быть поверхность текстуры, или буфер, и т. Д.)
  2. , если rt является мультисэмплированным (GetDesc, проверьте D3DSURFACE_DESC.MultiSampleType), то: a) создайте другую целевую поверхность рендеринга того же размера, того же формата, но без мультисэмплинга; б) StretchRect от rt на эту новую поверхность; c) rt = эта новая поверхность (т.е. перейти на эту новую поверхность).
  3. выкл. = создать неэкранную плоскую поверхность (CreateOffscreenPlainSurface, пул D3DPOOL_SYSTEMMEM)
  4. device-> GetRenderTargetData ( rt , off )
  5. сейчас off содержит целевые данные рендеринга. LockRect (), чтение данных, UnlockRect () для них.
  6. 1052 * чистка *

Далее следует более длинный ответ (вставьте из кодовой базы, над которой я работаю) Этот не будет компилироваться из коробки, потому что он использует некоторые классы, функции, макросы и утилиты из остальной кодовой базы; но это должно помочь вам начать. Я также пропустил большую часть проверки ошибок (например, вне зависимости от того, находится ли ширина / высота вне границ). Я также пропустил часть, которая считывает реальные пиксели и, возможно, преобразует их в подходящий формат назначения (это довольно просто, но может занять много времени, в зависимости от количества преобразований формата, которые вы хотите поддерживать).

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 ++.

Код выше читает всю поверхность. Если вы хотите эффективно прочитать только часть из них, то я считаю, что самый быстрый способ это примерно:

  1. создать поверхность пула по умолчанию необходимого размера.
  2. StretchRect от части исходной поверхности к той меньшей.
  3. действуйте как обычно с меньшим.

На самом деле это очень похоже на то, что код выше делает для обработки поверхностей с несколькими выборками. Если вы хотите получить только часть поверхности с несколькими выборками, вы можете сделать разрешение с несколькими выборками и получить его часть в одном StretchRect, я думаю.

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

Редактировать : обновлено, чтобы соответствовать отредактированному вопросу.

...