Obtendo dados do backbuffer D3D11 usando Mapa [duplicado]
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
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);
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.