首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

使用 C++ 進行 Windows 開發:Windows 運行時的 Direct2D 呈現實作

     編輯:關於C++

在上一期專欄中,我們探討了 Windows 運行時 (WinRT) 應用程式模型,並示範如何僅使用標準 C++ 和經典 COM 來編寫 Windows 應用商店或 Windows Phone 應用程式,而不依賴 C++/CX 或 C# 等語言投射。這種繞過抽象層的能力不僅強大,更是深入理解技術運作機制的最佳途徑。

先前我也介紹過 Direct2D 1.1 及其在桌面應用程式中的繪圖應用,以及能大幅簡化 C++ 中 DirectX 程式設計的 dx.h 函式庫。上一期的程式碼已足以實作基於 CoreWindow 的應用程式架構,但尚缺乏呈現功能。本月我將展示如何在這個基礎框架上加入呈現支援。

WinRT 應用程式模型與 DirectX 整合

WinRT 應用程式模型針對 DirectX 呈現進行了優化。無論目標平台是桌面還是 Windows 運行時,實際的 Direct2D 和 Direct3D 繪圖指令大體相同,但仍存在細微差異,且完整整合的過程截然不同。我將延續上一期內容,示範如何在 CoreWindow 基礎的 WinRT 應用程式中顯示像素。

處理視窗事件:可見性、大小變更與 DPI 變更

為了正確支援呈現,視窗必須感知特定事件:視窗可見性變更、大小變更,以及使用者選擇的邏輯顯示 DPI 設定變更。這些事件透過 COM 介面回呼報告給應用程式。ICoreWindow 介面提供註冊 VisibilityChanged 和 SizeChanged 事件的方法,對應的處理介面分別為:

typedef ITypedEventHandler<CoreWindow *, VisibilityChangedEventArgs *> IVisibilityChangedEventHandler;
typedef ITypedEventHandler<CoreWindow *, WindowSizeChangedEventArgs *> IWindowSizeChangedEventHandler;

第三個介面 IDisplayPropertiesEventHandler 已預先定義,只需包含相關標頭檔:

#include <Windows.Graphics.Display.h>
using namespace ABI::Windows::Graphics::Display;

更新 SampleWindow 類別,繼承這三個介面,並記得更新 QueryInterface 實作以支援它們。雖然 WinRT 不假設 IFrameworkView 實作也實作這些回呼介面,但我們需在 Load 方法中明確註冊事件:

EventRegistrationToken token;
HR(m_window->add_VisibilityChanged(this, &token));
HR(m_window->add_SizeChanged(this, &token));

LogicalDpiChanged 事件則透過 IDisplayPropertiesStatics 介面註冊:

ComPtr<IDisplayPropertiesStatics> m_displayProperties;
m_displayProperties = GetActivationFactory<IDisplayPropertiesStatics>(
    RuntimeClass_Windows_Graphics_Display_DisplayProperties);
HR(m_displayProperties->add_LogicalDpiChanged(this, &token));

DirectX 資源管理架構

需要標準的裝置資源處理常式集合:

void CreateDeviceIndependentResources() {}
void CreateDeviceSizeResources() {}
void CreateDeviceResources() {}
void ReleaseDeviceResources() {}

第一個方法建立非裝置特定資源,接著兩個建立裝置特定資源(區分視窗大小相關與否),最後釋放所有裝置資源。這四個方法提供了管理呈現資源的獨立切入點。

引入 dx.h 函式庫簡化 DirectX 程式設計

#include "dx.h"
using namespace KennyKerr;
using namespace KennyKerr::Direct2D;

每個 Direct2D 應用程式都從 Direct2D 工廠開始:

Factory1 m_factory;

在 Load 方法最後建立工廠與裝置無關資源:

m_factory = CreateFactory();
CreateDeviceIndependentResources();

實作動態呈現迴圈

Run 方法需根據視窗可見性決定呈現策略。新增成員變數追蹤可見狀態:

bool m_visible;
SampleWindow() : m_visible(true) {}

重寫 Run 方法實作動態呈現迴圈(圖 1):

auto __stdcall Run() -> HRESULT override {
    ComPtr<ICoreDispatcher> dispatcher;
    HR(m_window->get_Dispatcher(dispatcher.GetAddressOf()));
    while (true) {
        if (m_visible) {
            Render();
            HR(dispatcher->ProcessEvents(CoreProcessEventsOption_ProcessAllIfPresent));
        } else {
            HR(dispatcher->ProcessEvents(CoreProcessEventsOption_ProcessOneAndAllPending));
        }
    }
    return S_OK;
}

視窗可見時持續呈現並處理所有待處理事件;不可見時則阻塞等待訊息到達。IVisibilityChangedEventHandler 的 Invoke 方法更新可見狀態:

auto __stdcall Invoke(ICoreWindow *, IVisibilityChangedEventArgs * args) -> HRESULT override {
    unsigned char visible;
    HR(args->get_Visible(&visible));
    m_visible = 0 != visible;
    return S_OK;
}

Render 方法與呈現目標建立

Render 方法按需建立呈現堆疊並執行繪圖指令(圖 2):

void Render() {
    if (!m_target) {
        // 準備呈現目標...
    }
    m_target.BeginDraw();
    Draw();
    m_target.EndDraw();
    auto const hr = m_swapChain.Present();
    if (S_OK != hr && DXGI_STATUS_OCCLUDED != hr) {
        ReleaseDevice();
    }
}

需要的成員變數:

DeviceContext m_target;
Dxgi::SwapChain1 m_swapChain;

ReleaseDevice 方法負責釋放資源,對於可靠處理裝置遺失至關重要:

void ReleaseDevice() {
    m_target.Reset();
    m_swapChain.Reset();
    ReleaseDeviceResources();
}

建立呈現目標完整流程

準備呈現目標包含建立 Direct3D 裝置、Direct2D 裝置與裝置內容、DXGI 工廠、交換鏈,以及 Direct2D 位圖包裝交換鏈後台緩衝區(圖 3):

void Render() {
    if (!m_target) {
        auto device = Direct3D::CreateDevice();
        m_target = m_factory.CreateDevice(device).CreateDeviceContext();
        auto dxgi = device.GetDxgiFactory();
        m_swapChain = dxgi.CreateSwapChainForCoreWindow(device, m_window.Get());
        CreateDeviceSwapChainBitmap();
        float dpi;
        HR(m_displayProperties->get_LogicalDpi(&dpi));
        m_target.SetDpi(dpi);
        CreateDeviceResources();
        CreateDeviceSizeResources();
    }
    // 繪圖與呈現...
}

CreateDeviceSwapChainBitmap 方法建立包裝交換鏈後台緩衝區的 Direct2D 位圖:

void CreateDeviceSwapChainBitmap() {
    BitmapProperties1 props(BitmapOptions::Target | BitmapOptions::CannotDraw,
        PixelFormat(Dxgi::Format::B8G8R8A8_UNORM, AlphaMode::Ignore));
    auto bitmap = m_target.CreateBitmapFromDxgiSurface(m_swapChain, props);
    m_target.SetTarget(bitmap);
}

處理 DPI 變更與視窗大小調整

IDisplayPropertiesEventHandler 的 Invoke 方法處理 DPI 變更:

auto __stdcall Invoke(IInspectable *) -> HRESULT override {
    if (m_target) {
        float dpi;
        HR(m_displayProperties->get_LogicalDpi(&dpi));
        m_target.SetDpi(dpi);
        CreateDeviceSizeResources();
        Render();
    }
    return S_OK;
}

IWindowSizeChangedEventHandler 處理視窗大小變更:

auto __stdcall Invoke(ICoreWindow *, IWindowSizeChangedEventArgs *) -> HRESULT override {
    if (m_target) {
        ResizeSwapChainBitmap();
        Render();
    }
    return S_OK;
}

ResizeSwapChainBitmap 需謹慎處理:先釋放對緩衝區的引用,再調整交換鏈緩衝區大小(圖 4):

void ResizeSwapChainBitmap() {
    m_target.SetTarget();
    if (S_OK == m_swapChain.ResizeBuffers()) {
        CreateDeviceSwapChainBitmap();
        CreateDeviceSizeResources();
    } else {
        ReleaseDevice();
    }
}

簡單繪圖範例

在 CreateDeviceResources 建立純色筆刷:

SolidColorBrush m_brush;
m_brush = m_target.CreateSolidColorBrush(Color(1.0f, 0.0f, 0.0f));

Draw 方法清除背景並繪製紅色矩形:

m_target.Clear(Color(1.0f, 1.0f, 1.0f));
RectF rect(100.0f, 100.0f, 200.0f, 200.0f);
m_target.DrawRectangle(rect, m_brush);

確保裝置遺失時正確釋放筆刷:

void ReleaseDeviceResources() {
    m_brush.Reset();
}

這就是使用 DirectX 呈現基於 CoreWindow 應用程式的完整步驟。得益於 dx.h 函式庫,DirectX 相關程式碼已大幅簡化,雖然仍有大量 COM 介面實作的樣板程式碼,但 C++/CX 可進一步簡化 WinRT API 使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved