入手Direct2D QT工程

        最近的项目还是有点难度,因为要用CAN/CANFD通信,而是还是连接多个设备的,每秒钟大概上万条消息这样,这种情况跟服务器差不多一个级别了,但服务端一般都是没有界面的就是一个后台程序,而我的程序要把这上万条消息用高刷的形式显示出来,特别是信号字段要能看到及时变动,之前的项目是用QT做的,所以现在准备也用QT做,因为MFC是老产物了,C#又不安全,老板为了快点搞出来只能用QT这种快速开发工具了,但QT的引擎默认是不支持显卡的,所以这种高刷模式会不会太耗费CPU,之前上家公司做股票软件就是打开窗口太多且控件太多,导致刷新效率极其低下,动不动主UI线程就卡死了,这次有点害怕了(当然之前公司的项目不是我开发的),所以不敢用QPainter那套绘图引擎了,虽然QT也有支持显卡的绘图引擎(考虑到后面signal信号画图都要用显卡,因为信号可能很多),之前也有项目使用QT在里面用GDI画图,有锯齿,所以这次也不想用GDI,而且GDI没有封装,调用起来非常不方便,QT里面当然也有QWindowsDirect2DPaintEngine引擎但我不确定QPainter底层是不是使用这套引擎,而且封装后效率堪忧,所以比较了一下只能使用Direct2D了,也是因为我比较熟悉,之前D3D游戏就搞过这些,Direct2D基于Direct3D做了一套快速调用绘制2D图像或文字,所以底层实际还是D3D,但这些不是关键,关键是D2D支持显卡加速,这样CPU的负载就少了。

步骤1:包含Direct2D头文件和库

#include <qwidget.h>

#include <D3D11.h>
#include <d2d1_1.h>
#include <d2d1_3.h>
#include <wincodec.h>
#include <dxgi1_2.h>

//#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
//#pragma comment(lib, "dwrite.lib" )
//#pragma comment(lib, "d2d1.lib" )

#include <d2d1.h>
#include <dwrite.h>
#include <dwrite_1.h>
#include <dwmapi.h>
#include <fstream>
#include <comdef.h>
#include <iostream>
#include <ctime>
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "dwrite.lib")

步骤2:创建一个ID2D1Factory接口对象

任何Direct2D应用程序首先都需要创建 ID2D1Factory接口对象。

 

ID2D1Factory* factory= NULL;

HRESULT hr = D2D1CreateFactory(

D2D1_FACTORY_TYPE_SINGLE_THREADED,

&factory

);

ID2D1Factory接口是使用Direct2D的起点;所有的Direct2D资源都是通过ID2D1Factory接口创建的。

在创建ID2D1Factory接口对象时,可以指定它的工作方式是多线程的还是单线程的。在这里我们使用单线程模式。

通常,您的应用程序应该只创建ID2D1Factory 接口对象一次,并在应用程序的生命周期内保留它,直到程序销毁。

步骤3:创建一个ID2D1HwndRenderTarget接口对象

创建完工厂对象(ID2D1Factory *)后,使用它来创建渲染目标对象

factory->CreateHwndRenderTarget(
        D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT,
            D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED)),
        D2D1::HwndRenderTargetProperties(overlayWindow, D2D1::SizeU(200, 200), 
            D2D1_PRESENT_OPTIONS_IMMEDIATELY), &target);

其中D2D1_RENDER_TARGET_TYPE_DEFAULT是缺省的模式,让系统自己选,或者指定D2D1_RENDER_TARGET_TYPE_HARDWARE使用GPU渲染

第二个参数是一个D2D1_HWND_RENDER_TARGET_PROPERTIES结构体,它指定绘制内容的窗口(HWND)、绘制目标的初始大小(以像素为单位)及其presentation options。这个例子使用了D2D1::HwndRenderTargetProperties helper函数来指定HWND和初始大小。它使用默认的表示选项。overlayWindow是我通过winId()接口获取的窗口句柄,而MFC这个HWND大家都很熟悉,QT的话就是winId(),不管使用GDI还是GDI++,都需要HWND。

当GPU硬件加速可用时,你创建一个渲染目标对象将在GPU上分配资源。通过一次性创建渲染目标对象并尽可能长时间地保留它,您可以获得更好的性能。你的应用程序应该创建渲染目标对象一次,并在应用程序的生命周期内保留它,直到程序关闭或者收到D2DERR_RECREATE_TARGET错误再释放它。当您收到此错误时,您需要重新创建渲染目标对象(以及它创建的任何资源)。

步骤4:创建一个画刷对象

与工厂对象(ID2D1Factory)一样,渲染目标对象(ID2D1HwndRenderTarget)可以创建绘图资源。在这个例子中,渲染目标对象创建了一个画刷对象(ID2D1SolidColorBrush)。ID2D1SolidColorBrush* solid_brush= NULL;

if (SUCCEEDED(hr)){

target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black),&solid_brush);

}

画刷是绘制区域的对象,如形状的描边或几何图形的填充。本例中的画刷使用预定义的纯黑色绘制区域。

Direct2D还提供了其他类型的画刷:渐变画刷用于绘制线性和径向渐变,位图画刷用于绘制位图和模式。

一些绘图API(如GDI)提供了用于绘制轮廓的画笔和用于填充形状的画刷。Direct2D没有画笔对象,只有画刷对象,绘制轮廓线和填充形状都是用画刷对象。在绘制轮廓线时,使用画刷对象和 ID2D1StrokeStyle接口对象一起控制线条的形状

画刷只能由创建它的渲染目标对象和相同资源域中的其他渲染目标对象调用。一般来说,你创建了一个画刷对象以后,应该保留它们直到渲染目标对象的生命周期结束。但是 ID2D1SolidColorBrush画刷对象比较特殊,因为创建它的成本比较低,你可以在每次绘制一帧的时候重新创建一个ID2D1SolidColorBrush对象。你可以使用一个ID2D1SolidColorBrush对象,并且在每次绘制的时候修改它的颜色。

步骤5:绘制矩形

接下来,使用渲染目标绘制矩形。

target->BeginDraw();

target->DrawRectangle(

D2D1::RectF(rc.left + 100.0f,rc.top + 100.0f,rc.right - 100.0f,rc.bottom - 100.0f),

solid_brush);

HRESULT hr = target->EndDraw();

步骤6: 创建WriteFactory

IDWriteFactory* w_factory;
IDWriteTextFormat* w_format;

DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&w_factory));

上面的调用创建输出工厂,目的是为了输出字体:

    w_factory->CreateTextFormat(fontname.c_str(), NULL, DWRITE_FONT_WEIGHT_NORMAL, 
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 10.0f, L"en-us", &w_format);

这样我们通过工厂创建了一种格式的字体w_format,包含了字体的权重,风格,字体形态等,下面我们使用他来输出文字:

IDWriteTextLayout* w_layout;

void DrawString(std::string str, float fontSize, float x, float y, float r, float g, float b, float a)
{
    RECT re;
    GetClientRect(overlayWindow, &re);
    FLOAT dpix, dpiy;
    dpix = static_cast<float>(re.right - re.left);
    dpiy = static_cast<float>(re.bottom - re.top);
    HRESULT res = w_factory->CreateTextLayout(std::wstring(str.begin(), str.end()).c_str(), str.length() + 1, w_format, dpix, dpiy, &w_layout);
    if (SUCCEEDED(res))
    {
        DWRITE_TEXT_RANGE range = { 0, str.length() };
        w_layout->SetFontSize(fontSize, range);
        solid_brush->SetColor(D2D1::ColorF(r, g, b, a));
        target->DrawTextLayout(D2D1::Point2F(x, y), w_layout, solid_brush);
        w_layout->Release();
        w_layout = NULL;
    }
}

使用w_factory对str调用CreateTextLayout并传递参数w_format,我们创建了这个待绘制字符串的布局信息w_layout,这里我们改变了之前创建的画刷的颜色,并调用target->DrawTextLayout输出文字,当然最后别忘了调用Release释放掉布局。

下面我将展示一下在GDI上很常见的绘制接口,除了前面的矩形文字,还有线,圆等,做一些封装以便于调用:

void DrawBox(float x, float y, float width, float height, float thickness, float r, float g, float b, float a, bool filled)
{
    solid_brush->SetColor(D2D1::ColorF(r, g, b, a));
    if (filled)  target->FillRectangle(D2D1::RectF(x, y, x + width, y + height), solid_brush);
    else target->DrawRectangle(D2D1::RectF(x, y, x + width, y +height), solid_brush, thickness);
}

void DrawLine(float x1, float y1, float x2, float y2, float thickness, float r, float g, float b, float a) {
    solid_brush->SetColor(D2D1::ColorF(r, g, b, a));
    target->DrawLine(D2D1::Point2F(x1, y1), D2D1::Point2F(x2, y2), solid_brush, thickness);
}

void DrawCircle(float x, float y, float radius, float thickness, float r, float g, float b, float a, bool filled)
{
    solid_brush->SetColor(D2D1::ColorF(r, g, b, a));
    if (filled) target->FillEllipse(D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius), solid_brush);
    else target->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius), solid_brush, thickness);
}

void DrawEllipse(float x, float y, float width, float height, float thickness, float r, float g, float b, float a, bool filled)
{
    solid_brush->SetColor(D2D1::ColorF(r, g, b, a));
    if (filled) target->FillEllipse(D2D1::Ellipse(D2D1::Point2F(x, y), width, height), solid_brush);
    else target->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(x, y), width, height), solid_brush, thickness); 
}

是不是很容易,这样太像QPainter的调用了,而且与调用GDI接口一样简单,更友好的是通过只创建一个画刷在实际DrawXXX之前任意改变他的颜色,这样就不用像GDI或QPainter那样需要对不同颜色创建一个不同的画刷类了,这样就太方便了,只要定义一些常用颜色的D2D1::ColoF常量就能任意使用这个唯一的画刷,当然如果在前面调用D2D1CreateFactory时指定了参数D2D1_FACTORY_TYPE_MULTI_THREADED使用多线程方式那就不一样了。

步骤7:释放资源

当绘制结束以后或者收到 D2DERR_RECREATE_TARGET错误以后,释放渲染目标对象以及它创建的所有资源。

SafeRelease(w_format);SafeRelease(target);SafeRelease(solid_brush);

当应用程序准备退出之前,释放ID2D1Factory对象和IDWriteFactory对象:

SafeRelease(factory);SafeRelease(w_factory);

在此声明:以上代码都不是我写的,都是从CSDN和Github上Ctrl+C Ctrl+V过来的

不过以上使用ID2D1HwndRenderTarget进行内容呈现在win7或vista系统使用的,win8及之后的win10都使用交换链技术,使用参见下文msdn链接,所以在win10上面不建议使用ID2D1HwndRenderTarget方式

如何使用 Direct2D 设备上下文呈现 - Win32 apps | Microsoft Leajinx

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值