我是靠谱客的博主 淡淡小蝴蝶,最近开发中收集的这篇文章主要介绍Direct2D 学习笔记Direct2D,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • Direct2D
    • D2D 是什么
    • D2D 适合谁
    • 开发环境
    • 发布平台
    • 入门
    • 我能找到例子吗
    • 一、第一个 D2D 程序——Hello, Direct2D
      • 1. 工厂
      • 2. 呈现器
      • 3. 渲染
      • 4. 运行结果
    • 二、Direct2D 画图实践——Random Graphics
      • 1. 创建渐变画刷
      • 2. 绘制椭圆
      • 3. Resize 函数
    • 三、PeekMessage——Process Ring
      • 1. 使用 `PeekMessageW` 搭建消息循环
      • 2. 运行结果

Direct2D

D2D 是什么

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

D2D 适合谁

适合我。

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

开发环境

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

发布平台

至少 Windows 7。推荐 Windows 10。

入门

包含以下头文件:

#include <d2d1.h>
#include <d2d1_1.h>

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

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

我能找到例子吗

这篇博客的完整源码可以在 Github 上获取!戳我。

一、第一个 D2D 程序——Hello, Direct2D

1. 工厂

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

ID2D1Factory * pFactory;

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

int D2DDemo::HelloDirect2D::Main::OnExecute()
{
CreateD2DFactory();
auto window{ std::make_unique<MainWindow>() };
window->Create(hInstance, nullptr);
ShowWindow(window->GetHwnd(), SW_SHOW);
UpdateWindow(window->GetHwnd());
return window->MessageLoop();
}
void D2DDemo::HelloDirect2D::Main::CreateD2DFactory()
{
if (FAILED(D2D1CreateFactory(
D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_MULTI_THREADED, &pFactory)))
throw std::runtime_error("Fail to D2D1CreateFactory.");
}

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

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

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

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

D2DDemo::HelloDirect2D::Main::~Main()
{
if (pFactory) pFactory->Release();
}

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

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

2. 呈现器

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

ID2D1HwndRenderTarget * pRenderTarget;

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

void D2DDemo::HelloDirect2D::Window::MainWindow::CreateD2DRenderTarget()
{
if (FAILED(Main::App().pFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(GetHwnd(), D2D1::SizeU(width, height)),
&pRenderTarget)))
throw std::runtime_error("Fail to CreateHwndRenderTarget.");
pRenderTarget->SetDpi(USER_DEFAULT_SCREEN_DPI, USER_DEFAULT_SCREEN_DPI); // 自己处理高 DPI 的情况,需要加上这句代码。
}
void D2DDemo::HelloDirect2D::Window::MainWindow::ReleaseD2DRenderTarget()
{
if (pRenderTarget)
{
pRenderTarget->Release();
pRenderTarget = nullptr;
}
}

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

类型使用函数
D2D1_RENDER_TARGET_PROPERTIESD2D1::RenderTargetProperties
D2D1_HWND_RENDER_TARGET_PROPERTIESD2D1::HwndRenderTargetProperties

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

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

3. 渲染

对于窗口呈现器,我们需要在 WM_PAINT 中编写代码。它的用法与 BeginPaintEndPaint 类似。首先调用 BeginDraw 方法,绘制完成后,再调用 EndDraw 方法:

LRESULT D2DDemo::HelloDirect2D::Window::MainWindow::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
HANDLE_MSG(hwnd, WM_CREATE, [this](HWND hwnd, LPCREATESTRUCT lpCreateStruct)->BOOL
{
CreateD2DRenderTarget();
return TRUE;
});
HANDLE_MSG(hwnd, WM_DESTROY, [this](HWND hwnd)->void
{
PostQuitMessage(0);
});
HANDLE_MSG(hwnd, WM_SIZE, [this](HWND hwnd, UINT state, int cx, int cy)->void
{
pRenderTarget->Resize(D2D1::SizeU(cx, cy));
});
HANDLE_MSG(hwnd, WM_PAINT, [this](HWND hwnd)->void
{
pRenderTarget->BeginDraw();
pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
DrawRectangle();
pRenderTarget->EndDraw();
ValidateRect(hwnd, NULL); // note
});
default:
return DefWindowProcW(hwnd, message, wParam, lParam);
}
return 0;
}
void D2DDemo::HelloDirect2D::Window::MainWindow::DrawRectangle()
{
ID2D1SolidColorBrush* brush{};
pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &brush);
pRenderTarget->DrawRectangle(D2D1::RectF(20, 20, width - 20, height - 20), brush);
brush->Release();
}

注意,需要宣布窗口区域有效,否则将不断收到 WM_PAINT 消息。

使用 Direct2D 是不需要双缓冲的,在调用 EndDraw 方法之前都没有画到窗口上。

4. 运行结果

二、Direct2D 画图实践——Random Graphics

这一节内容我们主要了解 Render Target 的一点点画图函数和画刷。先看程序的运行效果:

1. 创建渐变画刷

ID2D1LinearGradientBrush* brush{};
void RandomBrush(ID2D1HwndRenderTarget* pRenderTarget)
{
int maxx = GetSystemMetrics(SM_CXSCREEN);
int maxy = GetSystemMetrics(SM_CYSCREEN);
static std::default_random_engine engine;
std::uniform_real_distribution dis(0.0, 1.0);
D2D1_GRADIENT_STOP stops[2]{
D2D1::GradientStop(0, D2D1::ColorF(dis(engine), dis(engine), dis(engine), dis(engine))),
D2D1::GradientStop(1, D2D1::ColorF(dis(engine), dis(engine), dis(engine), dis(engine)))
};
ID2D1GradientStopCollection* collection{};
if (!SUCCEEDED(pRenderTarget->CreateGradientStopCollection(stops, std::size(stops), &collection)))
throw std::runtime_error("Fail to CreateGradientStopCollection.");
if (!SUCCEEDED(pRenderTarget->CreateLinearGradientBrush(D2D1::LinearGradientBrushProperties(D2D1::Point2F(0, 0), D2D1::Point2F(maxx, maxy)),
collection, &brush)))
throw std::runtime_error("Fail to CreateLinearGradientBrush.");
if (collection) // ID2D1GradientStopCollection 创建完 brush 就可以销毁了
{
collection->Release();
collection = nullptr;
}
}

要创建渐变画刷,在 Direct2D 中,需要提供渐变起点、终点和渐变点,渐变点用一个 ID2D1GradientStopCollection 指针指定,而 ID2D1GradientStopCollection 又需要使用 CreateGradientStopCollection 方法创建。

从上面的代码可以看出 D2D 的命名规则:I 开头的类型是需要 Release 的 COM 类型,不是 I 开头的类型是诸如点的简单类型,无需 Release,命名空间 D2D1 中的函数是用于创建简单类型的辅助函数。

2. 绘制椭圆

根据上面的命名思想,我们很容易就能找到绘制椭圆的函数。见下面的绘制代码:

void D2DDemo::RandomGraphics::Window::MainWindow::Paint()
{
pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
for (const auto& t : elements)
{
auto center = D2D1::Point2F(t.lt.x + t.rb.x >> 1, t.lt.y + t.rb.y >> 1);
int cx = t.rb.x - t.lt.x;
int cy = t.rb.y - t.lt.y;
switch (t.ele)
{
case RandomElement::Element::Ellipse:
{
pRenderTarget->FillEllipse(D2D1::Ellipse(center, cx / 2, cy / 2), t.brush);
break;
}
case RandomElement::Element::Rectangle:
{
pRenderTarget->FillRectangle(D2D1::RectF(t.lt.x, t.lt.y, t.rb.x, t.rb.y), t.brush);
break;
}
default:
break;
}
}
}

利用 IntelliSense,可以很容易地了解参数信息。

3. Resize 函数

这里稍微修改了一下 CreateD2DRenderTarget 函数:

void D2DDemo::RandomGraphics::Window::MainWindow::CreateD2DRenderTarget()
{
if (FAILED(Main::App().pFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(GetHwnd(), D2D1::SizeU(1920, 1080)), // 这里指定了初始大小
&pRenderTarget)))
throw std::runtime_error("Fail to CreateHwndRenderTarget.");
pRenderTarget->SetDpi(USER_DEFAULT_SCREEN_DPI, USER_DEFAULT_SCREEN_DPI);
}

本质上,由于下面代码的存在:

HANDLE_MSG(hwnd, WM_SIZE, [this](HWND hwnd, UINT state, int cx, int cy)->void
{
pRenderTarget->Resize(D2D1::SizeU(cx, cy)); // 窗口刚创建好就会受到这个消息
});

上面的修改就没有任何效果。但是我们可以注释 Resize 这句代码,可以发现图形会随着窗口大小的变化进行缩放。这说明了 D2D 的 HwndRenderTarget 会自动帮我们把绘画内容填充满整个窗口。它甚至会帮助我们处理 DPI 的问题,这也是为什么我们需要调用 SetDpi 方法。

三、PeekMessage——Process Ring

1. 使用 PeekMessageW 搭建消息循环

int D2DDemo::ProcessRing::Window::MainWindow::PeekMessageLoop()
{
MSG msg;
while (true)
{
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
else
OnPaint(GetHwnd());
}
return msg.wParam;
}

具体原理可以参考《Windows 程序设计(第 5 版)》。

2. 运行结果

最后

以上就是淡淡小蝴蝶为你收集整理的Direct2D 学习笔记Direct2D的全部内容,希望文章能够帮你解决Direct2D 学习笔记Direct2D所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部