Direct2D 学习笔记

Direct2D

D2D 是什么

Direct2D 是一种硬件加速的即时模式二维图形 API,可为二维几何对象、位图和文本提供高性能、高质量的呈现。Direct2D API 可与使用 GDI、GDI+ 或 Direct3D 的现有代码进行交互。(摘自百度百科)

D2D 适合谁

适合我。

想学就学吼啊。支持 GPU 加速吼啊。

开发环境

Visual Studio 2017 Community,安装了适用于桌面的 C++ 开发(仅安装了推荐选项)。

发布平台

至少 Windows 7。推荐 Windows 10。

入门

包含以下头文件:

#include <d2d1.h>

为了能够静态链接,链接以下库:

#pragma comment(lib, "d2d1.lib")

一、第一个 D2D 程序

1. 工厂

几乎所有 D2D 的资源都是工厂(factory)创建并维护的。它的数据类型叫作 ID2D1Factory

ID2D1Factory * pFactory;

只能保存工厂的指针,只能通过 D2D1CreateFactory 函数创建工厂:

OILearner::OILearner() : TApplication(L"OI Learner", L"6D0741A6-057B-4F88-A42B-FFA2280C4988")
{
    if (FAILED(D2D1CreateFactory(
        D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_MULTI_THREADED, &pFactory)))
        throw;
}

(由于是边开发边学,我又很懒,因此我直接把我项目里的代码拷贝过来了。相信大家能够看出来哪些是关键代码)

D2D1CreateFactory 有多个重载函数,上面是最简单的一个。其中第一个参数用于指定工厂的类型,工厂分为单线程和多线程两种类型。

类型 枚举名
单线程 D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_SINGLE_THREADED
多线程 D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_MULTI_THREADED

顾名思义,多线程的工厂会帮你确保线程安全,但单线程工厂需要你自己确保线程安全(但显然单线程工厂更灵活)。建议就用多线程工厂。

在程序退出时,需要调用成员函数 Release 释放资源。除了工厂,很多其它类型的资源也需要释放,以后不再强调:

OILearner::~OILearner()
{
#define SAFE_RELEASE(obj) if (obj) obj->Release(), obj = NULL 
    SAFE_RELEASE(pFactory);
#undef SAFE_RELEASE 
}

不过需要注意的是,既然资源是由工厂管理的,那么理所应当地,工厂应该最后释放。

一般来说,一个程序只需要一个工厂,所以我把工厂初始化的工作放在了应用程序初始化的地方,而没有放在窗口初始化的地方。

2. 呈现器

呈现器(render target)相当于一个缓冲画板。由于是第一个 D2D 程序,资料又比较缺乏,我又很弱,因此就找别人的教程来,用别人教程中的“窗口呈现器”(ID2D1HwndRenderTarget)为例。

ID2D1HwndRenderTarget * pRenderTarget;

工厂的存活周期应与应用程序的存活周期相同,而窗口呈现器应与窗口的存活周期相同。

BOOL MainWindow::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
    // 移动窗口位置
    auto cxS = GetSystemMetrics(SM_CXSCREEN);
    auto cyS = GetSystemMetrics(SM_CYSCREEN);
    int iLeft = (cxS - cxMin) / 2;
    int iTop = (cyS - cyMin) / 2;
    MoveWindow(hwnd, iLeft, iTop, cxMin, cyMin, TRUE);

    // 设置图标
    SendMessage(hwnd, WM_SETICON, NULL, (LPARAM)app.GetIconLarge());

    // 设置标题
    SetWindowTextW(hwnd, app.GetApplicationName().c_str());

    // 创建呈现器
    if (FAILED(app.pFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
        D2D1::HwndRenderTargetProperties(hwnd, D2D1::SizeU(cxMin, cyMin)),
        &pRenderTarget)))
        throw;

    return TRUE;
}
VOID MainWindow::OnDestroy(HWND hwnd)
{
    if (pRenderTarget)
    {
        pRenderTarget->Release();
        pRenderTarget = NULL;
    }
    PostQuitMessage(0);
}

必须使用工厂提供的成员函数 CreateHwndRenderTarget 来创建窗口呈现器。它有三个参数,前两个参数应该分别由以下两个函数创建:

类型 使用函数
D2D1_RENDER_TARGET_PROPERTIES D2D1::RenderTargetProperties
D2D1_HWND_RENDER_TARGET_PROPERTIES D2D1::HwndRenderTargetProperties

具体什么意思,现在我也不知道。可以随时在 IDE 中按下 F1 查询帮助。已经知道的是,这两个函数有很多默认参数。到目前为止我们全都使用默认参数即可(除了窗口句柄)。

窗口呈现器的内存开销是巨大的,可以通过诊断工具查看内存使用情况。

3. 渲染

对于窗口呈现器,我们需要在 WM_PAINT 中编写代码。它的用法与 BeginPaintEndPaint 类似:

VOID MainWindow::OnPaint(HWND hwnd)
{
    pRenderTarget->BeginDraw();

    pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));

    ID2D1SolidColorBrush * brush;
    pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &brush);
    pRenderTarget->DrawRectangle(D2D1::RectF(20, 20, iWidth - 20, iHeight - 20), brush);
    brush->Release();

    pRenderTarget->EndDraw();

    ValidateRect(hwnd, NULL); // note
}
VOID MainWindow::OnSize(HWND hwnd, UINT state, int cx, int cy)
{
    iWidth = cx;
    iHeight = cy;
    if (pRenderTarget)
        pRenderTarget->Resize(D2D1::SizeU(cx, cy));
}

注意,需要宣布窗口区域有效,否则将不断收到 OnPaint 消息。再次提醒,窗口呈现器的内存开销是巨大的。

4. 运行结果

(已在程序清单中将 DPI 识别等级修改为“高 DPI 识别”)

在 100% 的缩放比例下:

在 125% 的缩放比例下:

WTF?

参考资料

原来 D2D 需要你在绘图时忽视 DPI。在你设置 DPI 识别为“高 DPI 识别”后,D2D 会帮你自动缩放(至少对窗口呈现器来说是如此)。仔细研究一下窗口呈现器的原理,发现它的绘图大致分为两步(不准确,但足以让人理解为什么会这样):

  1. 在缓存中根据给定的 Size 绘图。如果 DPI 大于 96,将 Size 按比例放大,将绘图指令也按比例放大;
  2. 调用 EndDraw 后,绘制到窗口上。将缓存中的内容缩放成客户区大小。

换句话说,D2D 有以下特征:

  1. 是双缓冲;
  2. 对固定分辨率支持十分良好;
  3. 对一般的 GUI 很不友好。

也就是说如果用 D2D 写一般的界面,不仅资源占用高,而且对高 DPI 的支持很麻烦。所以第一个程序写下来,我就打消了继续的念头……

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/81458405