Настройка ресурсов DirectX и отображение изображения

Здесь мы покажем, как создать устройство Direct3D, цепочку буферов и представление целевого объекта отрисовки, а также как представить отображаемое изображение на экране.

Цель. Настройка ресурсов DirectX в приложении универсальная платформа Windows C++ (UWP) и отображение сплошного цвета.

Необходимые компоненты

Предположим, что вы знакомы с C++. Вам также нужен базовый опыт работы с концепциями программирования графики.

Время завершения: 20 минут.

Instructions

1. Объявление переменных интерфейса Direct3D с помощью ComPtr

Мы объявляем переменные интерфейса Direct3D с помощью шаблона смарт-указателя ComPtr из библиотеки шаблонов C++ среда выполнения Windows C++, чтобы управлять временем существования этих переменных в безопасном режиме. Затем эти переменные можно использовать для доступа к классу ComPtr и его членам. Например:

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

Если вы объявляете ID3D11RenderTargetView с comPtr, можно использовать метод GetAddressOf ComPtr для получения адреса указателя на ID3D11RenderTargetView (**ID3D11RenderTargetView) для передачи в ID3D11DeviceContext::OMSetRenderTargets. OMSetRenderTargets привязывает целевой объект отрисовки к этапу слияния выходных данных, чтобы указать целевой объект отрисовки в качестве целевого объекта вывода.

После запуска примера приложения он инициализирует и загружает, а затем готов к выполнению.

2. Создание устройства Direct3D

Чтобы использовать API Direct3D для отрисовки сцены, сначала необходимо создать устройство Direct3D, представляющее адаптер дисплея. Чтобы создать устройство Direct3D, мы вызываем функцию D3D11CreateDevice . Мы указываем уровни 9.1–11.1 в массиве значений D3D_FEATURE_LEVEL . Direct3D проходит по массиву по порядку и возвращает самый высокий поддерживаемый уровень компонентов. Таким образом, чтобы получить самый высокий уровень возможностей, мы перечислим записи массива D3D_FEATURE_LEVEL от самого высокого до самого низкого. Мы передаем флаг D3D11_CREATE_DEVICE_BGRA_SUPPORT параметру Flags, чтобы обеспечить взаимодействие ресурсов Direct3D с Direct2D. Если мы используем отладочную сборку, мы также передадим флаг D3D11_CREATE_DEVICE_DEBUG . Дополнительные сведения об отладке приложений см. в разделе "Использование слоя отладки для отладки приложений".

Мы получаем устройство Direct3D 11.1 (ID3D11Device1) и контекст устройства (ID3D11DeviceContext1), запрашивая контекст устройства и устройства Direct3D 11, возвращаемый из 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. Создание цепочки буферов

Затем мы создадим цепочку буферов, которую устройство использует для отрисовки и отображения. Мы объявляем и инициализируем структуру DXGI_SWAP_CHAIN_DESC1 для описания цепочки буферов. Затем мы настроим цепочку буферов как модель переверки (т. е. цепочку буферов, которая имеет значение DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL в члене SwapEffect) и задайте для элемента Format значение DXGI_FORMAT_B8G8R8A8_UNORM. Мы зададим элемент Count структуры DXGI_SAMPLE_DESC, в котором элемент SampleDesc указывает значение 1, а элемент "Качество" DXGI_SAMPLE_DESC равно нулю, так как модель flip-model не поддерживает несколько антиалисов выборки (MSAA). Для элемента BufferCount задано значение 2, поэтому цепочка буферов может использовать передний буфер для представления устройства отображения и заднего буфера, который служит целевым объектом отрисовки.

Мы получаем базовое устройство DXGI, запрашивая устройство Direct3D 11.1. Чтобы свести к минимуму потребление энергии, что важно сделать на устройствах с питанием от батареи, таких как ноутбуки и планшеты, мы вызываем метод IDXGIDevice1::SetMaximumFrameLatency с 1 в качестве максимального количества кадров заднего буфера, которые DXGI может в очереди. Это гарантирует, что приложение отрисовывается только после вертикального пустого.

Чтобы, наконец, создать цепочку буферов, необходимо получить родительскую фабрику с устройства DXGI. Мы вызываем IDXGIDevice::GetAdapter, чтобы получить адаптер для устройства, а затем вызвать IDXGIObject::GetParent на адаптере, чтобы получить родительскую фабрику (IDXGIFactory2). Чтобы создать цепочку буферов, мы вызываем IDXGIFactory2::CreateSwapChainForCoreWindow с дескриптором цепочки буферов и основным окном приложения.

            // 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. Создание представления целевого объекта отрисовки

Чтобы отрисовка графики в окне, необходимо создать представление целевого объекта отрисовки. Мы вызываем IDXGISwapChain::GetBuffer , чтобы получить обратный буфер цепочки буферов, используемый при создании представления целевого объекта отрисовки. Мы указываем обратный буфер в виде трехмерной текстуры (ID3D11Texture2D). Чтобы создать представление целевого объекта отрисовки, мы вызываем ID3D11Device::CreateRenderTargetView с обратным буфером цепочки буферов. Необходимо указать, чтобы нарисовать все основное окно, указав порт представления (D3D11_VIEWPORT) в качестве полного размера обратного буфера цепочки буферов. Мы используем порт представления в вызове ID3D11DeviceContext::RSSetViewports для привязки порта представления к этапу растеризатора конвейера. Этап растризатора преобразует векторные данные в растровое изображение. В этом случае нам не требуется преобразование, так как мы просто отображаем сплошной цвет.

        // 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. Представление отрисованного изображения

Мы введем бесконечный цикл для постоянной отрисовки и отображения сцены.

В этом цикле мы вызываем:

  1. ID3D11DeviceContext::OMSetRenderTargets , чтобы указать целевой объект отрисовки в качестве целевого объекта вывода.
  2. ID3D11DeviceContext::ClearRenderTargetView , чтобы очистить целевой объект отрисовки до сплошного цвета.
  3. IDXGISwapChain::P resent , чтобы представить отображаемое изображение в окне.

Так как ранее мы установили максимальную задержку кадров на 1, Windows обычно замедляет цикл отрисовки на частоту обновления экрана, как правило, около 60 Гц. Windows замедляет цикл отрисовки, делая приложение спящий режим при вызове приложения Present. Windows делает приложение спящий режим до обновления экрана.

        // 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. Изменение размера окна приложения и буфера цепочки буферов

Если размер окна приложения изменяется, приложение должно изменить размер буферов цепочки буферов, повторно создать представление целевого объекта отрисовки, а затем представить измененный отрисованный образ. Чтобы изменить размер буферов цепочки буферов, мы называем IDXGISwapChain::ResizeBuffers. В этом вызове мы оставим количество буферов и формат буферов без изменений (параметр BufferCount до двух и параметр NewFormat для DXGI_FORMAT_B8G8R8A8_UNORM). Мы делаем размер обратного буфера цепочки буферов таким же размером, что и окно изменения размера. После изменения размера буферов цепочки буферов мы создадим новый целевой объект отрисовки и представим новый отрисованный образ так же, как и при инициализации приложения.

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

Сводка и дальнейшие действия

Мы создали устройство Direct3D, цепочку буферов и представление целевого объекта отрисовки и представили отображаемое изображение.

Затем мы также нарисуем треугольник на дисплее.

Создание шейдеров и примитивов рисования