Compartilhar via


Configurar recursos do DirectX e exibir uma imagem

Aqui, mostramos como criar um dispositivo Direct3D, uma cadeia de troca e uma exibição de destino de renderização e como apresentar a imagem renderizada à exibição.

Objetivo: configurar recursos do DirectX em um aplicativo UWP (Plataforma Universal do Windows) C++ e exibir uma cor sólida.

Pré-requisitos

Presumimos que você esteja familiarizado com C++. Você também precisa de experiência básica com conceitos de programação gráfica.

Tempo de conclusão: 20 minutos.

Instruções

1. Declarando variáveis de interface do Direct3D com ComPtr

Declaramos variáveis de interface do Direct3D com o modelo de ponteiro inteligente ComPtr da WRL (Biblioteca de Modelos C++) do Tempo de Execução do Windows, para que possamos gerenciar o tempo de vida dessas variáveis de maneira segura contra exceções. Em seguida, podemos usar essas variáveis para acessar a classe ComPtr e seus membros. Por exemplo:

    ComPtr<ID3D11RenderTargetView> m_renderTargetView;
    m_d3dDeviceContext->OMSetRenderTargets(
        1,
        m_renderTargetView.GetAddressOf(),
        nullptr // Use no depth stencil.
        );

Se você declarar ID3D11RenderTargetView com ComPtr, poderá usar o método GetAddressOf do ComPtr para obter o endereço do ponteiro para ID3D11RenderTargetView (**ID3D11RenderTargetView) para passar para ID3D11DeviceContext::OMSetRenderTargets. OMSetRenderTargets associa o destino de renderização ao estágio de fusão de saída para especificar o destino de renderização como o destino de saída.

Depois que o aplicativo de exemplo é iniciado, ele é inicializado e carregado e está pronto para ser executado.

2. Criando o dispositivo Direct3D

Para usar a API do Direct3D para renderizar uma cena, devemos primeiro criar um dispositivo Direct3D que represente o adaptador de vídeo. Para criar o dispositivo Direct3D, chamamos a função D3D11CreateDevice . Especificamos os níveis 9.1 a 11.1 na matriz de valores D3D_FEATURE_LEVEL. O Direct3D percorre a matriz em ordem e retorna o nível de recurso com suporte mais alto. Portanto, para obter o nível de recurso mais alto disponível, listamos as D3D_FEATURE_LEVEL entradas de matriz do mais alto para o mais baixo. Passamos o sinalizador D3D11_CREATE_DEVICE_BGRA_SUPPORT para o parâmetro Flags para fazer com que os recursos do Direct3D interoperem com o Direct2D. Se usarmos a compilação de depuração, também passaremos o sinalizador D3D11_CREATE_DEVICE_DEBUG. Para obter mais informações sobre como depurar aplicativos, consulte Usando a camada de depuração para depurar aplicativos.

Obtemos o dispositivo Direct3D 11.1 (ID3D11Device1) e o contexto do dispositivo (ID3D11DeviceContext1) consultando o dispositivo Direct3D 11 e o contexto do dispositivo que são retornados de D3D11CreateDevice.

        // First, create the Direct3D device.

        // This flag is required in order to enable compatibility with Direct2D.
        UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
        // If the project is in a debug build, enable debugging via SDK Layers with this flag.
        creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

        // This array defines the ordering of feature levels that D3D should attempt to create.
        D3D_FEATURE_LEVEL featureLevels[] =
        {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
            D3D_FEATURE_LEVEL_9_3,
            D3D_FEATURE_LEVEL_9_1
        };

        ComPtr<ID3D11Device> d3dDevice;
        ComPtr<ID3D11DeviceContext> d3dDeviceContext;
        DX::ThrowIfFailed(
            D3D11CreateDevice(
                nullptr,                    // Specify nullptr to use the default adapter.
                D3D_DRIVER_TYPE_HARDWARE,
                nullptr,                    // leave as nullptr if hardware is used
                creationFlags,              // optionally set debug and Direct2D compatibility flags
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,          // always set this to D3D11_SDK_VERSION
                &d3dDevice,
                nullptr,
                &d3dDeviceContext
                )
            );

        // Retrieve the Direct3D 11.1 interfaces.
        DX::ThrowIfFailed(
            d3dDevice.As(&m_d3dDevice)
            );

        DX::ThrowIfFailed(
            d3dDeviceContext.As(&m_d3dDeviceContext)
            );

3. Criando a cadeia de troca

Em seguida, criamos uma cadeia de troca que o dispositivo usa para renderização e exibição. Declaramos e inicializamos uma estrutura DXGI_SWAP_CHAIN_DESC1 para descrever a cadeia de troca. Em seguida, configuramos a cadeia de troca como modelo de inversão (ou seja, uma cadeia de troca que tem o valor DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL definido no membro SwapEffect) e definimos o membro Format como DXGI_FORMAT_B8G8R8A8_UNORM. Definimos o membro Count da estrutura DXGI_SAMPLE_DESC que o membro SampleDesc especifica como 1 e o membro Quality de DXGI_SAMPLE_DESC como zero porque o modelo de inversão não dá suporte a MSAA (suavização de várias amostras). Definimos o membro BufferCount como 2 para que a cadeia de troca possa usar um buffer frontal para apresentar ao dispositivo de exibição e um buffer traseiro que serve como destino de renderização.

Obtemos o dispositivo DXGI subjacente consultando o dispositivo Direct3D 11.1. Para minimizar o consumo de energia, o que é importante fazer em dispositivos alimentados por bateria, como laptops e tablets, chamamos o método IDXGIDevice1::SetMaximumFrameLatency com 1 como o número máximo de quadros de buffer traseiro que o DXGI pode enfileirar. Isso garante que o aplicativo seja renderizado somente após o espaço em branco vertical.

Para finalmente criar a cadeia de troca, precisamos obter a fábrica pai do dispositivo DXGI. Chamamos IDXGIDevice::GetAdapter para obter o adaptador para o dispositivo e, em seguida, chamamos IDXGIObject::GetParent no adaptador para obter a fábrica pai (IDXGIFactory2). Para criar a cadeia de troca, chamamos IDXGIFactory2::CreateSwapChainForCoreWindow com o descritor de cadeia de troca e a janela principal do aplicativo.

            // If the swap chain does not exist, create it.
            DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};

            swapChainDesc.Stereo = false;
            swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
            swapChainDesc.Scaling = DXGI_SCALING_NONE;
            swapChainDesc.Flags = 0;

            // Use automatic sizing.
            swapChainDesc.Width = 0;
            swapChainDesc.Height = 0;

            // This is the most common swap chain format.
            swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;

            // Don't use multi-sampling.
            swapChainDesc.SampleDesc.Count = 1;
            swapChainDesc.SampleDesc.Quality = 0;

            // Use two buffers to enable the flip effect.
            swapChainDesc.BufferCount = 2;

            // We recommend using this swap effect for all applications.
            swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;


            // Once the swap chain description is configured, it must be
            // created on the same adapter as the existing D3D Device.

            // First, retrieve the underlying DXGI Device from the D3D Device.
            ComPtr<IDXGIDevice2> dxgiDevice;
            DX::ThrowIfFailed(
                m_d3dDevice.As(&dxgiDevice)
                );

            // Ensure that DXGI does not queue more than one frame at a time. This both reduces
            // latency and ensures that the application will only render after each VSync, minimizing
            // power consumption.
            DX::ThrowIfFailed(
                dxgiDevice->SetMaximumFrameLatency(1)
                );

            // Next, get the parent factory from the DXGI Device.
            ComPtr<IDXGIAdapter> dxgiAdapter;
            DX::ThrowIfFailed(
                dxgiDevice->GetAdapter(&dxgiAdapter)
                );

            ComPtr<IDXGIFactory2> dxgiFactory;
            DX::ThrowIfFailed(
                dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory))
                );

            // Finally, create the swap chain.
            CoreWindow^ window = m_window.Get();
            DX::ThrowIfFailed(
                dxgiFactory->CreateSwapChainForCoreWindow(
                    m_d3dDevice.Get(),
                    reinterpret_cast<IUnknown*>(window),
                    &swapChainDesc,
                    nullptr, // Allow on all displays.
                    &m_swapChain
                    )
                );

4. Criando a visualização de destino de renderização

Para renderizar gráficos na janela, precisamos criar uma visualização de destino de renderização. Chamamos IDXGISwapChain::GetBuffer para obter o buffer traseiro da cadeia de troca a ser usado quando criarmos a exibição de destino de renderização. Especificamos o buffer traseiro como uma textura 2D (ID3D11Texture2D). Para criar a exibição de destino de renderização, chamamos ID3D11Device::CreateRenderTargetView com o buffer traseiro da cadeia de troca. Devemos especificar para desenhar em toda a janela principal especificando a porta de exibição (D3D11_VIEWPORT) como o tamanho total do buffer traseiro da cadeia de troca. Usamos a porta de exibição em uma chamada para ID3D11DeviceContext::RSSetViewports para associar a porta de exibição ao estágio do rasterizador do pipeline. O estágio do rasterizador converte informações vetoriais em uma imagem rasterizada. Nesse caso, não exigimos uma conversão porque estamos apenas exibindo uma cor sólida.

        // Once the swap chain is created, create a render target view.  This will
        // allow Direct3D to render graphics to the window.

        ComPtr<ID3D11Texture2D> backBuffer;
        DX::ThrowIfFailed(
            m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
            );

        DX::ThrowIfFailed(
            m_d3dDevice->CreateRenderTargetView(
                backBuffer.Get(),
                nullptr,
                &m_renderTargetView
                )
            );


        // After the render target view is created, specify that the viewport,
        // which describes what portion of the window to draw to, should cover
        // the entire window.

        D3D11_TEXTURE2D_DESC backBufferDesc = {0};
        backBuffer->GetDesc(&backBufferDesc);

        D3D11_VIEWPORT viewport;
        viewport.TopLeftX = 0.0f;
        viewport.TopLeftY = 0.0f;
        viewport.Width = static_cast<float>(backBufferDesc.Width);
        viewport.Height = static_cast<float>(backBufferDesc.Height);
        viewport.MinDepth = D3D11_MIN_DEPTH;
        viewport.MaxDepth = D3D11_MAX_DEPTH;

        m_d3dDeviceContext->RSSetViewports(1, &viewport);

5. Apresentando a imagem renderizada

Entramos em um loop infinito para renderizar e exibir continuamente a cena.

Neste loop, chamamos:

  1. ID3D11DeviceContext::OMSetRenderTargets para especificar o destino de renderização como o destino de saída.
  2. ID3D11DeviceContext::ClearRenderTargetView para limpar o destino de renderização para uma cor sólida.
  3. IDXGISwapChain::P resent para apresentar a imagem renderizada à janela.

Como definimos anteriormente a latência máxima do quadro como 1, o Windows geralmente diminui o loop de renderização para a taxa de atualização da tela, normalmente em torno de 60 Hz. O Windows retarda o loop de renderização fazendo com que o aplicativo durma quando o aplicativo chama Present. O Windows faz o aplicativo dormir até que a tela seja atualizada.

        // Enter the render loop.  Note that UWP apps should never exit.
        while (true)
        {
            // Process events incoming to the window.
            m_window->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

            // Specify the render target we created as the output target.
            m_d3dDeviceContext->OMSetRenderTargets(
                1,
                m_renderTargetView.GetAddressOf(),
                nullptr // Use no depth stencil.
                );

            // Clear the render target to a solid color.
            const float clearColor[4] = { 0.071f, 0.04f, 0.561f, 1.0f };
            m_d3dDeviceContext->ClearRenderTargetView(
                m_renderTargetView.Get(),
                clearColor
                );

            // Present the rendered image to the window.  Because the maximum frame latency is set to 1,
            // the render loop will generally be throttled to the screen refresh rate, typically around
            // 60 Hz, by sleeping the application on Present until the screen is refreshed.
            DX::ThrowIfFailed(
                m_swapChain->Present(1, 0)
                );
        }

6. Redimensionando a janela do aplicativo e o buffer da cadeia de troca

Se o tamanho da janela do aplicativo for alterado, o aplicativo deverá redimensionar os buffers da cadeia de troca, recriar a exibição de destino de renderização e apresentar a imagem renderizada redimensionada. Para redimensionar os buffers da cadeia de troca, chamamos IDXGISwapChain::ResizeBuffers. Nesta chamada, deixamos o número de buffers e o formato dos buffers inalterados (o parâmetro BufferCount como dois e o parâmetro NewFormat como DXGI_FORMAT_B8G8R8A8_UNORM). Tornamos o tamanho do buffer traseiro da cadeia de troca do mesmo tamanho que a janela redimensionada. Depois de redimensionarmos os buffers da cadeia de troca, criamos o novo destino de renderização e apresentamos a nova imagem renderizada da mesma forma que inicializamos o aplicativo.

            // If the swap chain already exists, resize it.
            DX::ThrowIfFailed(
                m_swapChain->ResizeBuffers(
                    2,
                    0,
                    0,
                    DXGI_FORMAT_B8G8R8A8_UNORM,
                    0
                    )
                );

Resumo e próximas etapas

Criamos um dispositivo Direct3D, uma cadeia de troca e uma exibição de destino de renderização e apresentamos a imagem renderizada à exibição.

Em seguida, também desenhamos um triângulo na tela.

Criando sombreadores e desenhando primitivos