Поделиться через


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

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

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

Предпосылки

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

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

Инструкции

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

Мы объявляем переменные интерфейса Direct3D с помощью интеллектуального указателя шаблона из библиотеки шаблонов среды выполнения Windows C++ (WRL), чтобы управлять временем существования этих переменных в безопасном режиме. Затем мы можем использовать эти переменные для доступа к классу 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 в параметр Флаги, чтобы обеспечить взаимодействие ресурсов 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) и задайте для элемента формат значение DXGI_FORMAT_B8G8R8A8_UNORM. Мы устанавливаем элемент Count структуры DXGI_SAMPLE_DESC, который задает элемент SampleDesc, в значение 1, а элемент Quality структуры DXGI_SAMPLE_DESC в ноль, потому что модель с переворотом не поддерживает множественное сглаживание (MSAA). Мы задали элемент BufferCount равным 2, чтобы цепочка буферов могла использовать передний буфер для вывода на устройство отображения и задний буфер, который служит целевым объектом отрисовки.

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

Чтобы, наконец, создать swap chain, необходимо получить родительскую фабрику на устройстве 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). Чтобы создать представление объекта render-target, мы вызываем 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::Present, чтобы представить отображаемое изображение в окне.

Так как ранее мы установили максимальную задержку кадров на 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, цепочку буферов и вид на целевой объект отрисовки и представили отрендеренное изображение на дисплей.

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

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