出于某些奇怪的原因,在使用Creators版本更新更新Windows 10后,调用WinAPI的ExtTextOutW函数在高分辨率位图(2560x1440/3840x2160)上绘制剪切的文本会导致〜x50的性能下降。从我的用户的测试和调试日志来看,似乎位图或字体大小的细微差异可能会触发性能下降。
这是显示性能影响的调试日志:
10/05/2017 15:51:50 [ 63227,186] : Calculate Rect
10/05/2017 15:51:50 [ 63227,190] : Rect : Left=263, Top=504, Right=3561, Bottom=2155
10/05/2017 15:51:50 [ 63227,193] : Set Shadow Color
10/05/2017 15:51:50 [ 63227,198] : Render Text Shadow
10/05/2017 15:51:50 [ 63236,650] : Set Text Color
10/05/2017 15:51:50 [ 63236,661] : Render Text "Kingdom come Deliverance"
10/05/2017 15:51:50 [ 63246,062] : Rendering complete
从日志中可以看到,对ExtTextgOutW的单个调用耗时约9.5毫秒,而同一调用在创建者更新之前花费了不到1毫秒。
这是可以与上面的调试输出进行比较的实际代码:
{$IFDEF TEXTRENDERTRACE}DebugMsgFT('c:\log\.TextRender.txt','Calculate Rect');{$ENDIF}
cRect := Rect(X,Y,Width+X,MainForm.Monitor.Height-(1+(MainForm.Monitor.Height div 540)));
{$IFDEF TEXTRENDERTRACE}DebugMsgFT('c:\log\.TextRender.txt','Rect : Left='+IntToStr(cRect.Left)+', Top='+IntToStr(cRect.Top)+', Right='+IntToStr(cRect.Right)+', Bottom='+IntToStr(cRect.Bottom));{$ENDIF}
{$IFDEF TEXTRENDERTRACE}DebugMsgFT('c:\log\.TextRender.txt','Set Shadow Color');{$ENDIF}
srcColor := txtCanvas.Font.Color;
txtCanvas.Font.Color := OutLineColor;
{$IFDEF TEXTRENDERTRACE}DebugMsgFT('c:\log\.TextRender.txt','Render Text Shadow');{$ENDIF}
Windows.ExtTextOutW(txtCanvas.Handle,X ,Y+(MainForm.Monitor.Height div 540),ETO_CLIPPED,@cRect,@S[1],I,nil);
{$IFDEF TEXTRENDERTRACE}DebugMsgFT('c:\log\.TextRender.txt','Set Text Color');{$ENDIF}
txtCanvas.Font.Color := srcColor;
{$IFDEF TEXTRENDERTRACE}DebugMsgFT('c:\log\.TextRender.txt','Render Text "'+S+'"');{$ENDIF}
Windows.ExtTextOutW(txtCanvas.Handle,X ,Y ,ETO_CLIPPED,@cRect,@S[1],I,nil);
{$IFDEF TEXTRENDERTRACE}DebugMsgFT('c:\log\.TextRender.txt','Rendering complete'+CRLF);{$ENDIF}
该代码通过两次渲染相同的文本(Y偏移和颜色略有不同)来实现非常简单的阴影效果。
这是与我的论坛用户的完整讨论,我们在其中尝试在各种硬件上调试问题(帖子中包含其他调试日志):
http://forum.inmatrix.com/index.php?showtopic=14995&page=2
我们对DPI设置为100%进行了测试,以确保触发器与Creators Edition中引入的DPI更改无关。
有谁知道是什么触发的?有解决方法吗?
*****更新1 *****
至少在初始测试中,“DrawTextExW”似乎也受到性能损失的影响。测试中使用的字体为Arial,性能问题似乎与字体大小有关,因为用户报告说,在屏幕上添加更多较小尺寸的行(以较低的分辨率渲染更多文本)可以极大地提高性能。
*****更新2 *****
我编写了一个小工具来描述此问题,您可以在以下GitHub存储库中找到该工具:
https://github.com/bLightZP/WindowsTextRenderingProfiler
似乎问题取决于字体大小,例如,在2560x1440屏幕上,渲染一行“Arial”字体文本,其大小为“35” 21ms,而要渲染为“34”,则需要2ms。
将其渲染为具有32位像素格式的Delphi TBitmap的HDC,并且禁用裁剪只会对性能产生较小的影响。
*****更新3 *****
Sebastian Z在下面的回答确实恢复了创作者版本的性能水平,并且我已经在GitHub上更新了示例代码以反射(reflect)他的回答,但是自此以后,我就能够在Windows 7 64bit和1920x1080屏幕上重现该问题,不限于Windows 10 Creators Edition或高分辨率显示器,当字体质量设置为ANTIALIASED时,触发阈值会更高。在Windows 7下的测试中,使用Arial字体,触发点是“109”(快速)的字体大小与“110”(较慢或较差的性能x10)的字体大小。使用Sebastian Z的答案禁用cleartype后,Windows 10中也存在相同的触发阈值。
最佳答案
Delphi使用lfQuality := DEFAULT_QUALITY;
创建字体。默认质量曾经是抗锯齿质量。但是,由于Windows 10 Creators更新了,因此现在默认为cleartype。这很慢。因此,解决方案是手动强制使用抗锯齿质量。
如果您使用的是当前的Delphi版本,则只需设置Font.Quality属性即可:
Procedure RenderText(oBitmap : TBitmap; X,Y : Integer; cRect : TRect; S : WideString; testFunction : Integer; TxtEffect : Integer; EffectColor : TColor; Clipping : Boolean);
// [...]
begin
obitmap.Canvas.Font.Quality := fqClearType;
在较早的Delphi版本中,它有点复杂:
var
lf: TLogFont;
begin
if GetObject(oBitmap.Canvas.Font.Handle, SizeOf(TLogFont), @lf) = sizeof(TLogFont) then
begin
lf.lfQuality := ANTIALIASED_QUALITY;
oBitmap.Canvas.Font.Handle := CreateFontIndirect(lf);
end;
这是Windows 10 Creators Update中的一个陷阱,因为ClearType文本并不总是合适的,并且可能导致意外的结果。