使用 D3D11on12 的 D2D

D3D1211on12範例示範如何透過 D3D12 內容轉譯 D2D 內容,方法是在 11 型裝置與以 12 為基礎的裝置之間共用資源。

建立 ID3D11On12Device

第一個步驟是在建立ID3D11Device之後建立ID3D11On12Device,這牽涉到透過 API D3D111On12CreateDevice建立ID3D11Device 此 API 也會接受 ID3D12CommandQueue 的其他參數,讓 11On12 裝置可以提交其命令。 建立 ID3D11Device 之後,您可以從該介面查詢 ID3D11On12Device 介面。 這是將用來設定 D2D 的主要裝置物件。

LoadPipeline 方法中,設定裝置。

 // Create an 11 device wrapped around the 12 device and share
    // 12's command queue.
    ComPtr<ID3D11Device> d3d11Device;
    ThrowIfFailed(D3D11On12CreateDevice(
        m_d3d12Device.Get(),
        d3d11DeviceFlags,
        nullptr,
        0,
        reinterpret_cast<IUnknown**>(m_commandQueue.GetAddressOf()),
        1,
        0,
        &d3d11Device,
        &m_d3d11DeviceContext,
        nullptr
        ));

    // Query the 11On12 device from the 11 device.
    ThrowIfFailed(d3d11Device.As(&m_d3d11On12Device));
通話流程 參數
ID3D11Device
D3D11On12CreateDevice

 

建立 D2D 處理站

既然我們有 11On12 裝置,我們會使用它來建立 D2D 處理站和裝置,就像一般使用 D3D11 一樣。

將 新增至 LoadAssets 方法。

 // Create D2D/DWrite components.
    {
        D2D1_DEVICE_CONTEXT_OPTIONS deviceOptions = D2D1_DEVICE_CONTEXT_OPTIONS_NONE;
        ThrowIfFailed(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory3), &d2dFactoryOptions, &m_d2dFactory));
        ComPtr<IDXGIDevice> dxgiDevice;
        ThrowIfFailed(m_d3d11On12Device.As(&dxgiDevice));
        ThrowIfFailed(m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice));
        ThrowIfFailed(m_d2dDevice->CreateDeviceContext(deviceOptions, &m_d2dDeviceContext));
        ThrowIfFailed(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &m_dWriteFactory));
    }
通話流程 參數
D2D1_DEVICE_CONTEXT_OPTIONS
D2D1CreateFactory D2D1_FACTORY_TYPE
IDXGIDevice
ID2D1Factory3::CreateDevice
ID2D1Device::CreateDeviceCoNtext
DWriteCreateFactory DWRITE_FACTORY_TYPE

 

建立 D2D 的轉譯目標

D3D12 擁有交換鏈結,因此,如果我們想要使用 11On12 裝置轉譯回後端緩衝區, (D2D 內容) ,則我們需要從ID3D11Resource 類型的後端緩衝區建立 ID3D11Resource的包裝資源。 這會連結 ID3D12Resource 與以 D3D11 為基礎的介面,以便與 11On12 裝置搭配使用。 在擁有包裝的資源之後,我們接著可以在 LoadAssets 方法中建立 D2D 的轉譯目標介面來轉譯。

// Initialize *hwnd* with the handle of the window displaying the rendered content.
HWND hwnd;

// Query the window's dpi settings, which will be used to create
// D2D's render targets.
float dpi = GetDpiForWindow(hwnd);
D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1(
    D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
    D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),
    dpi,
    dpi);  

// Create frame resources.
{
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());

    // Create a RTV, D2D render target, and a command allocator for each frame.
    for (UINT n = 0; n < FrameCount; n++)
    {
        ThrowIfFailed(m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n])));
        m_d3d12Device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle);

        // Create a wrapped 11On12 resource of this back buffer. Since we are 
        // rendering all D3D12 content first and then all D2D content, we specify 
        // the In resource state as RENDER_TARGET - because D3D12 will have last 
        // used it in this state - and the Out resource state as PRESENT. When 
        // ReleaseWrappedResources() is called on the 11On12 device, the resource 
        // will be transitioned to the PRESENT state.
        D3D11_RESOURCE_FLAGS d3d11Flags = { D3D11_BIND_RENDER_TARGET };
        ThrowIfFailed(m_d3d11On12Device->CreateWrappedResource(
            m_renderTargets[n].Get(),
            &d3d11Flags,
            D3D12_RESOURCE_STATE_RENDER_TARGET,
            D3D12_RESOURCE_STATE_PRESENT,
            IID_PPV_ARGS(&m_wrappedBackBuffers[n])
            ));

        // Create a render target for D2D to draw directly to this back buffer.
        ComPtr<IDXGISurface> surface;
        ThrowIfFailed(m_wrappedBackBuffers[n].As(&surface));
        ThrowIfFailed(m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
            surface.Get(),
            &bitmapProperties,
            &m_d2dRenderTargets[n]
            ));

        rtvHandle.Offset(1, m_rtvDescriptorSize);

        ThrowIfFailed(m_d3d12Device->CreateCommandAllocator(
            D3D12_COMMAND_LIST_TYPE_DIRECT,
            IID_PPV_ARGS(&m_commandAllocators[n])));
    }
}
通話流程 參數
GetDpiForWindow 視窗控制碼
D2D1_BITMAP_PROPERTIES1
BitmapProperties1
[D2D1_BITMAP_OPTIONS] (/windows/desktop/api/d2d1_1/ne-d2d1_1-d2d1_bitmap_options)
[PixelFormat] (/windows/desktop/api/d2d1helper/nf-d2d1helper-pixelformat)
[DXGI_FORMAT] (/windows/desktop/api/dxgiformat/ne-dxgiformat-dxgi_format)
[D2D1_ALPHA_MODE] (/windows/desktop/api/dcommon/ne-dcommon-d2d1_Alpha_mode)
CD3DX12_CPU_DESCRIPTOR_HANDLE GetCPUDescriptorHandleForHeapStart
IDXGISwapChain::GetBuffer
CreateRenderTargetView
D3D11_RESOURCE_FLAGS D3D11_BIND_FLAG
CreateWrappedResource D3D12_RESOURCE_STATES
IDXGISurface
ID2D1DeviceCoNtext::CreateBitmapFromDxgiSurface
CreateCommandAllocator D3D12_COMMAND_LIST_TYPE

 

建立基本 D2D 文字物件

現在我們有一個 ID3D12Device 可轉譯 3D 內容,這是一個 ID2D1Device ,透過 ID3D11On12Device 與 12 裝置共用,我們可用來轉譯 2D 內容,而且兩者都設定為轉譯為相同的交換鏈結。 此範例只會使用 D2D 裝置在 3D 場景中轉譯文字,類似于遊戲如何轉譯其 UI。 因此,我們需要在 LoadAssets 方法中建立一些基本的 D2D 物件。

 // Create D2D/DWrite objects for rendering text.
    {
        ThrowIfFailed(m_d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &m_textBrush));
        ThrowIfFailed(m_dWriteFactory->CreateTextFormat(
            L"Verdana",
            NULL,
            DWRITE_FONT_WEIGHT_NORMAL,
            DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL,
            50,
            L"en-us",
            &m_textFormat
            ));
        ThrowIfFailed(m_textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
        ThrowIfFailed(m_textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
    }
通話流程 參數
ID2D1RenderTarget::CreateSolidColorBrush ColorF
IDWriteFactory::CreateTextFormat DWRITE_FONT_WEIGHT
IDWriteTextFormat::SetTextAlignment DWRITE_TEXT_ALIGNMENT
IDWriteTextFormat::SetParagraphAlignment DWRITE_PARAGRAPH_ALIGNMENT

 

更新主要轉譯迴圈

現在,範例的初始化已完成,我們可以移至主要轉譯迴圈。

// Render the scene.
void D3D1211on12::OnRender()
{
    // Record all the commands we need to render the scene into the command list.
    PopulateCommandList();

    // Execute the command list.
    ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
    m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

    RenderUI();

    // Present the frame.
    ThrowIfFailed(m_swapChain->Present(0, 0));

    MoveToNextFrame();
}
通話流程 參數
ID3D12CommandList
ExecuteCommandLists
IDXGISwapChain1::Present1

 

轉譯迴圈的唯一新功能是 RenderUI 呼叫,這會使用 D2D 來轉譯 UI。 請注意,我們會先執行所有 D3D12 命令清單來轉譯我們的 3D 場景,然後在其中轉譯 UI。 在深入探討 RenderUI之前,我們必須查看 PopulateCommandLists 的變更。 在其他範例中,我們通常會在命令清單上放置資源屏障,再關閉命令清單,以將背景緩衝區從轉譯目標狀態轉換為目前狀態。 不過,在此範例中,我們會移除該資源屏障,因為我們仍然需要使用 D2D 轉譯回後端緩衝區。 請注意,當我們建立後端緩衝區的包裝資源時,會將轉譯目標狀態指定為 「IN」 狀態,並將目前狀態指定為 「OUT」 狀態。

RenderUI 在 D2D 使用方面相當直接。 我們會設定轉譯目標並轉譯文字。 不過,在 11On12 裝置上使用任何包裝的資源之前,例如後端緩衝區轉譯目標,我們必須在 11On12 裝置上呼叫 AcquireWrappedResources API。 轉譯之後,我們會在 11On12 裝置上呼叫 ReleaseWrappedResources API。 藉由呼叫 ReleaseWrappedResources ,我們會在幕後產生資源屏障,將指定的資源轉換為建立時指定的「OUT」狀態。 在我們的案例中,這是目前的狀態。 最後,為了將所有在 11On12 裝置上執行的命令提交至共用ID3D12CommandQueue,我們必須在ID3D11DeviceCoNtext上呼叫Flush

// Render text over D3D12 using D2D via the 11On12 device.
void D3D1211on12::RenderUI()
{
    D2D1_SIZE_F rtSize = m_d2dRenderTargets[m_frameIndex]->GetSize();
    D2D1_RECT_F textRect = D2D1::RectF(0, 0, rtSize.width, rtSize.height);
    static const WCHAR text[] = L"11On12";

    // Acquire our wrapped render target resource for the current back buffer.
    m_d3d11On12Device->AcquireWrappedResources(m_wrappedBackBuffers[m_frameIndex].GetAddressOf(), 1);

    // Render text directly to the back buffer.
    m_d2dDeviceContext->SetTarget(m_d2dRenderTargets[m_frameIndex].Get());
    m_d2dDeviceContext->BeginDraw();
    m_d2dDeviceContext->SetTransform(D2D1::Matrix3x2F::Identity());
    m_d2dDeviceContext->DrawTextW(
        text,
        _countof(text) - 1,
        m_textFormat.Get(),
        &textRect,
        m_textBrush.Get()
        );
    ThrowIfFailed(m_d2dDeviceContext->EndDraw());

    // Release our wrapped render target resource. Releasing 
    // transitions the back buffer resource to the state specified
    // as the OutState when the wrapped resource was created.
    m_d3d11On12Device->ReleaseWrappedResources(m_wrappedBackBuffers[m_frameIndex].GetAddressOf(), 1);

    // Flush to submit the 11 command list to the shared command queue.
    m_d3d11DeviceContext->Flush();
}
通話流程 參數
D2D1_SIZE_F
D2D1_RECT_F RectF
AcquireWrappedResources
ID2D1DeviceCoNtext::SetTarget
ID2D1RenderTarget::BeginDraw
ID2D1RenderTarget::SetTransform Matrix3x2F
ID2D1RenderTarget::D rawTextW
ID2D1RenderTarget::EndDraw
ReleaseWrappedResources
ID3D11DeviceCoNtext::Flush

 

執行範例

12 上 11 個範例的最後輸出

D3D12 程式碼逐步解說

Direct3D 11 on 12

Direct3D 12 Interop

11on12 參考