Pergunta

Eu preciso descobrir como obter os dados de texturas D3D e superfícies de volta a memória do sistema. Qual é a maneira mais rápida de fazer tais coisas e como?

Além disso, se eu só precisa de um subrect, como se pode ler de volta apenas a parte sem ter que ler de volta a coisa toda a memória do sistema?

Em suma, eu estou procurando descrições concisas de como copiar o seguinte para memória do sistema :

  1. textura
  2. subconjunto de um textura
  3. superfície
  4. subconjunto de um superfície
  5. D3DUSAGE_RENDERTARGET textura
  6. subconjunto de um D3DUSAGE_RENDERTARGET textura

Esta é Direct3D 9, mas respostas sobre versões mais recentes do D3D seria apreciada também.

Foi útil?

Solução

A parte mais envolvido é a leitura de alguma superfície que está na memória de vídeo ( "pool default"). Isso geralmente é tornar alvos.

Vamos pegar as peças fáceis primeiro:

  1. a leitura de uma textura é o mesmo que ler a partir da superfície de nível 0 do que de textura. Veja abaixo.
  2. o mesmo para subconjunto de uma textura.
  3. leitura de uma superfície que está em não-padrão pool de memória ( "sistema" ou "administrado") é apenas trancando-a e lendo bytes.
  4. o mesmo para subconjunto de superfície. Apenas bloquear parte relevante e lê-lo.

Então, agora nós deixamos superfícies que estão na memória de vídeo ( "pool default"). Isso seria qualquer superfície / textura marcado como tornar alvo, ou qualquer superfície / textura regular que você criou na piscina padrão, ou o próprio backbuffer. A parte complexa aqui é que você não pode bloqueá-lo.

A resposta curta é: GetRenderTargetData método em D3D dispositivo.

resposta mais longa (um esboço do código que será abaixo):

  1. rt = get rendem superfície alvo (este pode ser de superfície da textura, ou backbuffer, etc.)
  2. se rt é Multisampled (GetDesc, D3DSURFACE_DESC.MultiSampleType cheque), então: a) criar um outro rendem superfície alvo do mesmo tamanho, mas mesmo formato sem multisampling; b) a partir de StretchRect rt para esta nova superfície; c) rt = esta nova superfície (isto é, proceder a esta nova superfície).
  3. off = criar offscreen superfície plana (CreateOffscreenPlainSurface, piscina D3DPOOL_SYSTEMMEM)
  4. dispositivo-> GetRenderTargetData ( rt , desligado )
  5. Agora off contém processar dados alvo. LockRect (), ler dados, UnlockRect () sobre ele.
  6. limpeza

Mesmo mais tempo de resposta (colar a partir da base de código que estou trabalhando) segue. Este não compilar fora da caixa, porque ele usa algumas classes, funções, macros e utilitários do resto da base de código; mas deve começar. Também ommitted mais de verificação de erros (por exemplo, se dada largura / altura está fora dos limites). Eu também omitiu a parte que lê pixels reais e, possivelmente, os converte em formato de destino adequado (que é muito fácil, mas pode ficar muito tempo, dependendo do número de conversões de formato que você quer suporte).

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 no código acima é um ponteiro inteligente para um objeto COM (ele libera objeto de cessão ou destruidor). Simplifica tratamento de erro muito. Isto é muito semelhante a coisas _comptr_t no Visual C ++.

O código acima lê superfície traseira inteira. Se você quiser ler apenas uma parte dela de forma eficiente, então eu acredito maneira mais rápida é aproximadamente:

  1. criar uma superfície piscina padrão que é do tamanho necessário.
  2. StretchRect de parte da superfície original para que um menor.
  3. prosseguir normalmente com o menor.

Na verdade isso é bastante semelhante ao que o código acima faz para lidar com superfícies multi-sampleados. Se você deseja obter apenas uma parte de uma superfície multi-amostradas, você pode fazer uma determinação multisample e obter parte dele em um StretchRect, eu acho.

Editar : peça removida do código que faz leitura real de pixels e conversões de formato. não estava diretamente relacionada à pergunta, eo código foi longo.

Editar :. Atualizado para corresponder questão editado

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top