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

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

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

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

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

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


person Baxissimo    schedule 23.09.2008    source источник


Ответы (1)


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

Сначала займемся легкими частями:

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

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

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

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

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

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

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.

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

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

person NeARAZ    schedule 23.09.2008
comment
Спасибо. Это указывает мне правильный путь. Я нашел комментарий в MSDN вскоре после публикации, что вы не можете LockRect на D3DUSAGE_RENDERTARGET и вместо этого должны использовать GetRenderTargetData. облом. Однако было бы неплохо иметь более чистый и компактный пример кода. :-) - person Baxissimo; 24.09.2008
comment
Определенно намного лучше без кода копирования пикселей. Как насчет замены типа SurfacePointer, отличного от D3D, на фактическое имя D3D? LPDIRECT3DSURFACE9 или IDirect3DSurface9* - person Baxissimo; 28.09.2008
comment
И что делать, если я хочу получить текстуру из DEFAULT pool, но это не rendertarget. Блокировка не работает, я не могу использовать GetRenderTargetData() для поверхностей без цели рендеринга. Иногда можно создать промежуточную цель рендеринга, но если исходная текстура в каком-то формате, например D3DFORMAT_L8 - я не могу создать цель рендеринга в этом формате. Получается, что получить такую ​​текстуру невозможно. Я прав ? - person Ezh; 26.12.2014