Падение производительности ExtTextOutW x50 на экранах QHD/4K после обновления редакции Windows Creators

По какой-то странной причине вызов функции WinAPI ExtTextOutW для рисования обрезанного текста на растровом изображении с высоким разрешением (2560x1440/3840x2160) приводит к падению производительности ~x50 после обновления Windows 10 с помощью обновления Creators Edition. Из журналов тестирования и отладки моего пользователя видно, что небольшая разница в растровом изображении или, возможно, в размере шрифта может привести к снижению производительности.

Вот журнал отладки, показывающий падение производительности:

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%, чтобы убедиться, что триггер не связан с изменениями DPI, представленными в Creators Edition.

Кто-нибудь знает, что вызывает это? и есть ли обходной путь?

***** обновление 1 *****

По крайней мере, при первоначальном тестировании на "DrawTextExW" также повлияла потеря производительности. Во время тестирования используется шрифт Arial, и проблемы с производительностью, по-видимому, связаны с размером шрифта, поскольку пользователь сообщил, что добавление на экран большего количества строк меньшего размера (больше текста отображается с более низким разрешением) значительно повышает производительность.

***** обновление 2 *****

Я написал небольшой инструмент для профилирования этой проблемы, который вы можете найти в этом репозитории GitHub: https://github.com/bLightZP/WindowsTextRenderingProfiler

Кажется, проблема зависит от размера шрифта, например, на экране 2560x1440 рендеринг строки текста шрифта «Arial» с размером «35» занимает 21 мс, а при размере «34» — 2 мс.

Это визуализируется в HDC Delphi TBitmap с 32-битным форматом пикселей, и отключение отсечения оказывает лишь незначительное влияние на производительность.

***** обновление 3 *****

Ответ Себастьяна Зи ниже действительно восстанавливает уровень производительности версии до создателей, и я обновил пример кода на GitHub, чтобы отразить его ответ, но с тех пор я смог воспроизвести проблему с 64-битной Windows 7 и на экране 1920x1080, так что это не ограничивается выпуском Windows 10 Creators Edition или дисплеями с высоким разрешением, просто порог срабатывания выше, когда для качества шрифта установлено значение ANTIALIASED. В моем тесте под Windows 7 с использованием шрифта Arial точкой срабатывания был размер шрифта «109» (быстрый) по сравнению с размером шрифта «110» (в 10 раз медленнее или хуже производительность). И этот же порог срабатывания существует в Windows 10 после использования ответа Себастьяна З. для отключения прозрачного типа.


person bLight    schedule 10.05.2017    source источник
comment
Получаете ли вы такое же падение производительности при удалении флага ETO_CLIPPED? Какая глубина цвета у растрового изображения (например, 24 бита на пиксель или 32 бита на пиксель)? Это DIB или DDB? Было бы лучше, если бы вы добавили минимально воспроизводимый пример, чтобы пользователи SO могли попытаться воспроизвести.   -  person zett42    schedule 10.05.2017
comment
Я обновил исходный пост ссылкой на проект GitHub, который включает исходный код и исполняемый файл, который вы можете использовать для проверки. Обновление включает в себя другие детали.   -  person bLight    schedule 11.05.2017


Ответы (1)


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 не всегда подходит и может привести к неожиданным результатам.

person Sebastian Z    schedule 15.05.2017
comment
Ваш ответ работает и возвращает уровни производительности до создателей, но при более глубоком тестировании кажется, что проблема все еще существует еще в Windows 7, но с более высоким пороговым уровнем, см. Обновление 3 (примерно через 5 минут) - person bLight; 15.05.2017