Share via


Configurare le risorse DirectX e visualizzare un'immagine

Qui mostreremo come creare un dispositivo Direct3D, una catena di scambio e una visualizzazione del target di rendering e come presentare l'immagine renderizzata sul display.

Obiettivo: configurare le risorse DirectX in un'app UWP (Universal Windows Platform) C++ e visualizzare un colore a tinta unita.

Prerequisiti

Si presuppone che l'utente abbia familiarità con C++. È anche necessaria un'esperienza di base con i concetti di programmazione grafica.

Tempo di completamento: 20 minuti.

Istruzioni

1. Dichiarazione delle variabili di interfaccia Direct3D con ComPtr

Dichiariamo variabili di interfaccia Direct3D con il modello di puntatore intelligente ComPtr dalla libreria di modelli C++ di Windows Runtime, in modo da poter gestire la durata di tali variabili in modo molto sicuro. È quindi possibile utilizzare tali variabili per accedere alla classe ComPtr e ai relativi membri. Ad esempio:

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

Se si dichiara ID3D11RenderTargetView con ComPtr, è poi possibile utilizzare il metodo GetAddressOf di ComPtr per ottenere l'indirizzo del puntatore a ID3D11RenderTargetView (**ID3D11RenderTargetView) per passare a ID3D11DeviceContext::OMSetRenderTargets. OMSetRenderTargets associa il target di rendering allo stadio di unione dell'output per specificare il target di rendering come target di output.

Dopo l'avvio dell'app di esempio, si inizializza e si carica ed è quindi pronto per l'esecuzione.

2. Creazione del dispositivo Direct3D

Per utilizzare l'API Direct3D per eseguire il rendering di una scena, è prima necessario creare un dispositivo Direct3D che rappresenti la scheda video. Per creare il dispositivo Direct3D, chiamiamo la funzione D3D11CreateDevice. Specifichiamo i livelli da 9.1 a 11.1 nella matrice dei valori D3D_FEATURE_LEVEL. Direct3D guida la matrice in ordine e restituisce il livello di funzionalità supportato più alto. Quindi, per ottenere il livello di funzionalità più alto disponibile, elenchiamo le voci della matrice D3D_FEATURE_LEVEL dalla più alta alla più bassa. Passiamo il flag D3D11_CREATE_DEVICE_BGRA_SUPPORT al parametro Flags per far interagire le risorse Direct3D con Direct2D. Se si utilizza la build di debug, passiamo anche il flag D3D11_CREATE_DEVICE_DEBUG. Per maggiori informazioni sul debug delle app, vedere Uso del livello di debug per eseguire il debug di app.

Otteniamo il dispositivo Direct3D 11.1 (ID3D11Device1) e il contesto del dispositivo (ID3D11DeviceContext1) eseguendo query sul dispositivo Direct3D 11 e sul contesto del dispositivo che vengono restituite da 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. Creazione della catena di scambio

Successivamente, creiamo una catena di scambio che il dispositivo utilizza per il rendering e la visualizzazione. Dichiariamo e inizializziamo una struttura DXGI_SWAP_CHAIN_DESC1 per descrivere la catena di scambio. Quindi, impostiamo la catena di scambio come modello flip-(ovvero una catena di scambio con il valore DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL impostato nel membro SwapEffect) e impostiamo il membro Format su DXGI_FORMAT_B8G8R8A8_UNORM. Impostiamo il membro Count della struttura DXGI_SAMPLE_DESC che il membro SampleDesc specifica su 1 e il membro Quality di DXGI_SAMPLE_DESC su zero perché il modello flip-non supporta il Multiple Sample Antialiasing (MSAA). Impostiamo il membro BufferCount su 2 in modo che la catena di scambio possa utilizzare un buffer anteriore da presentare al dispositivo di visualizzazione e un buffer posteriore che funge da target di rendering.

Otteniamo il dispositivo DXGI sottostante eseguendo una query sul dispositivo Direct3D 11.1. Per ridurre al minimo il consumo di energia, che è importante per dispositivi a batteria quali portatili e tablet, chiamiamo il metodo IDXGIDevice1::SetMaximumFrameLatency con 1 come numero massimo di frame di buffer posteriore che DXGI può mettere in coda. In questo modo si garantisce che il rendering dell'app venga eseguito solo dopo il blank verticale.

Per creare infine la catena di scambio, è necessario ottenere la parent factory dal dispositivo DXGI. Chiamiamo IDXGIDevice::GetAdapter per ottenere l'adattatore per il dispositivo e quindi chiamiamo IDXGIObject::GetParent sulla scheda per ottenere la parent factory (IDXGIFactory2). Per creare la catena di scambio, chiamiamo IDXGIFactory2::CreateSwapChainForCoreWindow con il descrittore della catena di scambio e la finestra principale dell'app.

            // 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. Creazione della visualizzazione del target di rendering

Per eseguire il rendering della grafica nella finestra, dobbiamo creare una visualizzazione del target di rendering. Chiamiamo IDXGISwapChain::GetBuffer per ottenere il buffer posteriore della catena di scambio da utilizzare quando creiamo la visualizzazione del target di rendering. Specifichiamo il buffer nascosto come trama 2D (ID3D11Texture2D). Per creare la visualizzazione del target di rendering, chiamiamo ID3D11Device::CreateRenderTargetView con il buffer posteriore della catena di scambio. Dobbiamo specificare di disegnare nell'intera finestra principale specificando la viewport (D3D11_VIEWPORT) come dimensione completa del buffer posteriore della catena di scambio. Utilizziamo la viewport in una chiamata a ID3D11DeviceContext::RSSetViewports per associare la porta di visualizzazione allo stadio di rasterizzazione della pipeline. Lo stadio di rasterizzazione converte le informazioni vettoriali in un'immagine raster. In questo caso, non richiediamo una conversione in quanto stiamo solo visualizzando un colore a tinta unita.

        // 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. Presentazione dell'immagine renderizzata

Immettiamo un ciclo infinito per eseguire il rendering e visualizzare la scena in modo continuo.

In questo ciclo, chiamiamo:

  1. ID3D11DeviceContext::OMSetRenderTargets per specificare il target di rendering come target di output.
  2. ID3D11DeviceContext::ClearRenderTargetView per cancellare il target di rendering su un colore a tinta unita.
  3. IDXGISwapChain::Present per presentare l'immagine renderizzata alla finestra.

Poiché in precedenza abbiamo impostato la latenza massima dei frame su 1, Windows in genere rallenta il ciclo di rendering fino alla frequenza di aggiornamento dello schermo, in genere intorno ai 60 Hz. Windows rallenta il ciclo di rendering ponendo l'app in sospensione quando l'app chiama Present. Windows pone l'app in sospensione fino a quando non viene aggiornata la schermata.

        // 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. Ridimensionamento della finestra dell'app e del buffer della catena di scambio

Se le dimensioni della finestra dell'app cambiano, l'app deve ridimensionare i buffer della catena di scambio, ricreare la visualizzazione di target di rendering e quindi presentare l'immagine renderizzata ridimensionata. Per ridimensionare i buffer della catena di scambio, chiamiamo IDXGISwapChain::ResizeBuffers. In questa chiamata, lasciamo invariati il numero di buffer e il formato dei buffer (il parametro BufferCount a due e il parametro NewFormat a DXGI_FORMAT_B8G8R8A8_UNORM). Poniamo le dimensioni del buffer posteriore della catena di scambio uguali a quelle della finestra ridimensionata. Dopo aver ridimensionato i buffer della catena di scambio, creiamo il nuovo target di rendering e presentiamo la nuova immagine renderizzata in modo simile a quando abbiamo inizializzato l'app.

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

Riepilogo e passaggi successivi

Abbiamo creato un dispositivo Direct3D, una catena di scambio e una visualizzazione del target di rendering e presentato l'immagine renderizzata sul display.

Successivamente, disegniamo anche un triangolo sullo schermo.

Creazione di shader e primitive di disegno