Obtendo dados do backbuffer D3D11 usando Mapa [duplicado]

Nov 13 2020

Estou tendo problemas para usar ID3D11DeviceContext :: Map () corretamente.

Aqui está toda a função até agora:

 Color Graphics::GetPixel(int x, int y) const
{
    //Temp frame buffer descriptor;
    CD3D11_TEXTURE2D_DESC pFrameDesc;
    pFrameDesc.Width = WindowWidth;
    pFrameDesc.Height = WindowHeight;
    pFrameDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    pFrameDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    pFrameDesc.MipLevels = 1u;
    pFrameDesc.ArraySize = 1u;
    pFrameDesc.SampleDesc.Count = 1u;
    pFrameDesc.SampleDesc.Quality = 0u;
    pFrameDesc.Usage = D3D11_USAGE_DEFAULT;
    pFrameDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
    pFrameDesc.MiscFlags = 0u;

    // Create temp frame buffer(2d texture)
    wrl::ComPtr<ID3D11Texture2D>pFrame = nullptr;
    HRESULT hr = pDevice->CreateTexture2D(&pFrameDesc, nullptr, &pFrame);
    GFX_THROW_INFO(hr);

    hr = pSwapChain->GetBuffer(0, __uuidof(pFrame), &pFrame);
    GFX_THROW_INFO(hr);
    
    
    D3D11_MAPPED_SUBRESOURCE map;
    map.RowPitch = WindowWidth * 4;
    map.DepthPitch = WindowHeight * 4;
    
    //Throwing here, CPU access flags problem. 
    hr = pContext->Map(pFrame.Get(), 0u, D3D11_MAP_READ, 0u, &map);
    GFX_THROW_INFO(hr);

    pContext->Unmap(pFrame.Get(), 0u);
    


    return { 0.0f, 0.4f, 0.0f, 0.0f };
}

Estou recebendo uma exceção lançada em pContext-> Map ()

"O mapa não pode ser chamado com acesso MAP_READ, porque o recurso não foi criado com o sinalizador D3D11_CPU_ACCESS_READ."

O que é confuso, pois o recurso recém-criado tem o sinalizador D3D11_CPU_ACCESS_READ habilitado. Isso também é verdade se eu tentar MAP_WRITE e alterar os sinalizadores de acesso para corresponder. Eu também tentei ORing as bandeiras juntos, sem sorte.

O uso final dessa função é ser capaz de criar uma matriz lateral da CPU de objetos Color (as cores são atualmente uma estrutura de quatro flutuadores) que posso enviar para o SDK do NewTek NDI. Mas acho que também seria útil para salvar capturas de tela, criar seletores de cores etc.

Todas as ideias são bem-vindas!

Respostas

5 ChuckWalbourn Nov 14 2020 at 08:33

Seu código não usa a textura criada com D3D11_CPU_ACCESS_READ.

Ele cria uma textura:

// Create temp frame buffer(2d texture)
wrl::ComPtr<ID3D11Texture2D>pFrame = nullptr;
HRESULT hr = pDevice->CreateTexture2D(&pFrameDesc, nullptr, &pFrame);
GFX_THROW_INFO(hr);

Em seguida, ele libera essa textura e faz referência a uma do swapchain (que nunca é definido para CPU READ):

hr = pSwapChain->GetBuffer(0, __uuidof(pFrame), &pFrame);
GFX_THROW_INFO(hr);

Em seguida, você tenta mapear o buffer da cadeia de troca:

hr = pContext->Map(pFrame.Get(), 0u, D3D11_MAP_READ, 0u, &map);
GFX_THROW_INFO(hr);
1 PandaPajama Nov 14 2020 at 15:08

A resposta de Chuck Walbourn está correta, mas, para dar um pouco mais de contexto, a textura que você está criando pDevice->CreateTexture2Dnão é a mesma que você está obtendo pSwapChain->GetBuffer.

Com pDevice->CreateTexture2D, você está criando uma nova textura na memória (que está configurada para ser lida na CPU). A nova textura é armazenada em pFrame.

Então, com pSwapChain->GetBuffer, você está obtendo o back buffer existente da cadeia de troca (que não foi criada com acesso de leitura da CPU). Isso é armazenado em pFrame, descartando efetivamente o valor anterior.

Você tem duas opções aqui (eu não tocava no D3D11 há anos. Isso é baseado no meu D3D12, Vulkan e conhecimento de console da geração atual)

A opção A é descartar a nova textura na memória e, em vez disso, definir o buffer traseiro na cadeia de troca para acesso à CPU. Eu não recomendo que você faça isso, uma vez que as texturas destinadas à leitura da CPU não terão necessariamente um desempenho tão bom quanto aquelas que são criadas sem acesso à CPU.

Além disso, enquanto você mapeou a memória, a GPU terá que liberar o conteúdo da textura e interromper todas as operações da GPU até que você desfaça o mapeamento da memória. Dependendo de onde você estiver fazendo isso no processo de desenho, esta operação será um grande gargalo.

A propósito, nem tenho certeza se o Direct3D permitirá que você crie uma cadeia de troca com acesso de leitura à CPU, então você provavelmente não conseguirá testá-la.

A opção B é fazer uma operação de desenho com um quad texturizado de tela inteira, onde a textura é o buffer traseiro e o destino de renderização é a textura legível pela CPU. Geralmente, esse é o método preferido, pois resolve todos os problemas que a Opção A tinha, às custas de uma operação de desenho de tela inteira.

Lembre-se de que é possível (não me lembro com D3D11) que você não consiga definir o buffer traseiro como uma textura para desenho. Nesse caso, em vez de fazer chamadas de desenho do seu jogo diretamente para o buffer traseiro, você pode ter que criar outro alvo de renderização, desenhar tudo para ele e então fazer duas cópias: uma para o buffer traseiro da cadeia de troca e outra para sua textura legível pela CPU.

Este método é um pouco mais complexo, mas é garantido que funcione no D3D12, bem como provavelmente em qualquer outra plataforma moderna.