【问题标题】:Direct2D API calls stall at specific intervalsDirect2D API 调用以特定间隔停止
【发布时间】:2015-04-24 07:33:54
【问题描述】:

我正在将应用程序的绘图代码从 GDI/GDI+ 迁移到 Direct2D。到目前为止,一切进展顺利——然而,在测试新代码时,我注意到了一些奇怪的性能。我一直在调查的执行流程如下(我已尽力删除不相关的代码):

创建 D2D 工厂(在创建应用时)

HRESULT hr = S_OK;
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &m_pD2DFactory);
if (hr == S_FALSE) {
   ASSERT(FALSE);
   throw Exception(CExtString(_T("Failed to create Direct2D factory")));
}

OnDraw 回调

HWND hwnd = GetSafeHwnd();
RECT rc;
GetClientRect(&rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);

// Create a render target if it has been destroyed
if (!m_pRT) {
   D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
      D2D1_RENDER_TARGET_TYPE_DEFAULT,
      D2D1::PixelFormat(
      DXGI_FORMAT_B8G8R8A8_UNORM,
      D2D1_ALPHA_MODE_IGNORE),
      0,
      0,
      D2D1_RENDER_TARGET_USAGE_NONE,
      D2D1_FEATURE_LEVEL_DEFAULT);
   GetD2DFactory()->CreateHwndRenderTarget(props,
   D2D1::HwndRenderTargetProperties(hwnd, size),
   &m_pRT);
}

m_pRT->Resize(size);
m_pRT->BeginDraw();

// Begin drawing the layers, given the 
// transformation matrix and some geometric information
Draw(m_pRT, matrixD2D, rectClipWorld, rectClipDP);

HRESULT hr = m_pRT->EndDraw();

if (hr == D2DERR_RECREATE_TARGET) {
   SafeRelease(m_pRT);
}

Draw 方法的内容

draw 方法做了很多与这个测试无关的绒毛(因为我已经关闭了所有无关的层),但它最终绘制了一个执行该方法数千次的层:

void DrawStringWithEffects(ID2D1RenderTarget* m_pRT, const CString& text, const D2D1_POINT_2F& point, const COLORREF rgbFore, const COLORREF rgbBack, IDWriteTextFormat* pfont) {
// The text will be vertically centered around point.y, with point.x on the left hand side

// Create a TextLayout for the string
IDWriteTextLayout* textLayout = NULL;
GetDWriteFactory()->CreateTextLayout(text,
   text.GetLength(),
   pfont,
   std::numeric_limits<float>::infinity(),
   std::numeric_limits<float>::infinity(),
   &textLayout);
DWRITE_TEXT_METRICS metrics = {0};
textLayout->GetMetrics(&metrics);
D2D1_RECT_F rect = D2D1::RectF(point.x, point.y - metrics.height/2, point.x + metrics.width, point.y + metrics.height/2);
D2D1_POINT_2F pointDraw = point;
pointDraw.y -= metrics.height/2;


ID2D1SolidColorBrush* brush = NULL;
m_pRT->CreateSolidColorBrush(ColorD2DFromCOLORREF(rgbBack), &brush);

m_pRT->FillRectangle(rect, brush);
// ^^ this is sometimes very slow!

brush->SetColor(ColorD2DFromCOLORREF(rgbFore));
m_pRT->DrawTextLayout(pointDraw, textLayout, brush, D2D1_DRAW_TEXT_OPTIONS_NONE);
// ^^ this is also sometimes very slow!
SafeRelease(&brush);
SafeRelease(&textLayout);

在大多数情况下,Direct2D 调用的执行速度比 GDI+ 等效调用快约 3-4 倍,这很棒(通常为 0.1 毫秒,而约 0.35 毫秒)。但是,出于某种原因,函数调用偶尔会停顿很长一段时间 - 加起来超过 200 毫秒。有问题的调用直接来自 Direct2D API - FillRectangle 和 DrawTextLayout。奇怪的是,每次我运行应用程序时,这些停顿出现在同一个位置 - 第 73 次出现循环,然后是第 218 次,然后是第 290 次等等(差异中有一些模式,在每 ~ 73 次和每~145个周期)。这与它绘制的数据无关(当我告诉它跳过绘制第 73 个周期时,下一个周期只是变成第 73 个并因此停止)。

我认为这可能是 GPU/CPU 通信问题,所以我将渲染目标(我使用的是 HWnd 目标)设置为软件模式(D2D1_RENDER_TARGET_TYPE_SOFTWARE),结果更加奇怪。停顿时间从约 200 毫秒下降到约 20 毫秒(仍然不是很好,但是嘿),但有两个实例停顿超过 2500 毫秒! (这两个,就像其他的摊位一样,就作为第 n 个 API 调用而言是完全可重现的。

这相当令人沮丧,因为 99% 的循环速度比旧实现快几倍,但(不到)1% 的剩余循环时间异常长。

致所有 Direct2D 专家 - 这种停滞可能是什么类型的问题的征兆?一般来说,什么可能导致我的代码与 D2D 在后台执行的操作之间出现这种脱节?

【问题讨论】:

  • 您提供的代码示例不足。请分享整个渲染周期(RenderTarget.BeginDraw() - RenderTarget.EndDraw() 之间的代码。顺便说一下,RenderTarget.FillRectangle 应该立即执行 - 它只是向 direct2d 渲染器添加一个命令。所有命令在您调用时立即执行RenderTarget.Flush()RenderTarget.EndDraw().
  • @PeterKostov 我曾希望我的第一个问题不会是代码转储,但我确实看到它可能有点稀疏。问题已更新 - 感谢您的反馈。如果我还有什么遗漏,请告诉我!
  • “不幸的是”,您的代码看起来没问题。至少,我看不出您描述的问题的主要原因。无论如何,FillRectangle 没有理由造成任何停顿。你是如何衡量绩效的?尝试ID2D1RenderTarget::DrawText 而不是DrawTextLayout(测试目的)。另外,打开调试层到D2D1_DEBUG_LEVEL_INFORMATION 并嗅探一些错误或警告。
  • 如果在重新创建渲染目标时只调用m_pRT-&gt;Resize(size); 会发生什么?在每幅画上都这样做似乎是多余的。
  • @PeterKostov 我正在测试性能,方法是用一个调试宏包围有问题的代码,该宏执行一些 QueryPerformanceCounter/Frequency 调用并记录结果。这看起来确实很奇怪,但 FillRectangle 确实在软件和默认模式下都停止了。使用 ::DrawText 没有任何帮助(如果我记得,它只是做了一个临时布局)。感谢您迄今为止的帮助!

标签: performance direct2d


【解决方案1】:

Direct2D 缓冲绘图命令(大概是为了优化它们)。您不能查看单个绘图命令的性能,您必须查看BeginDraw()EndDraw() 之间的总时间。如果您想强制每个绘图命令立即执行,您必须在每个命令之后调用Flush()。不过,这对于性能来说可能是个坏主意。

https://msdn.microsoft.com/en-us/library/windows/desktop/dd371768(v=vs.85).aspx

在调用 BeginDraw 之后,渲染目标通常会建立一个 一批渲染命令,但推迟处理这些命令 直到内部缓冲区已满,才会调用 Flush 方法, 或直到调用 EndDraw。

【讨论】:

  • 因此,较长的绘制调用最可能的原因是缓冲区已被填充,并且正在自动刷新。考虑到当它在其余时间刷新缓冲区时,它不会经历如此多的延迟,我什至怎么能绕过它对性能的影响?
  • 你为什么还要担心这个?单独的绘图调用并不重要。从调用BeginDraw() 到结束EndDraw() 之间的时间是重要的基准。你专注于错误的事情。但即便如此,我已经给了你答案......在每次绘图电话后致电Flush()。哎呀,制作你自己的 ID2D1RenderTarget 包装器,它会为你做到这一点......
  • 啊,看来我可以把问题说得更清楚一点。本质上,如果视图被放大到足够大,那么与旧代码相比,性能是非常棒的。但是,如果它被缩小到足以绘制超过一定数量的对象,我遇到了这个停滞的问题,显然它试图以非常慢的方式刷新缓冲区,即使改变了也需要更长的时间来绘制zoom 只包含了少量的数字或对象(如果我有时间,我想我可以在上面找到我的数字)。
  • 好的。在不了解您实际绘制的内容的更多细节的情况下,我只是在猜测。您可以使用像 Windows 性能分析器这样的工具来真正深入了解消耗 CPU 时间的原因。当我使用该工具时,我通常会发现它在几何处理方面花费了大量时间。
  • 糟糕,回车太快了。无论如何,如果您对 DrawLine 或 DrawRectangle 之类的东西进行大量调用,那么 Direct2D 每次都必须进行描边和填充。预先缓存描边可能很有用。不过,让我先退后一步:将 DrawGeometry 视为 FillGeometry 之上的更高级别的实用程序。 “绘制”将采用几何图形并应用笔触,然后对其进行填充。因此,如果您使用ID2D1Geometry::Widen() 然后填充结果而不是绘制您插入的内容,那么您可以节省大量 CPU 时间。 (如果几何图形不改变每帧,效果很好)
猜你喜欢
  • 1970-01-01
  • 2018-10-30
  • 1970-01-01
  • 2012-04-15
  • 2018-05-10
  • 2011-02-25
  • 1970-01-01
  • 2015-11-19
  • 2011-01-09
相关资源
最近更新 更多