Skip to content

Commit a56af48

Browse files
committed
Program startup: performance improvements on cold start
GDI+ font rendering is out. My previous workarounds for faster GDI+ font creation proved unreliable. The testing matrix of GDI+ against various Windows versions (let alone redist possibilities, which I haven't been testing) is simply too large for me to make concrete perf decisions, especially since there are hidden changes on Win 10 (to explain why GDI+ can suddenly handle OpenType fonts). PD's startup perf performance is extremely sensitive to random lags when the GDI+ font renderer decides it needs to cache enormous chunks of data, so rather than try and fix it, I'm just gonna revert to GDI fonts. Across all OS versions, it's more code - but much faster - to simply use a GDI font on a 24-bpp surface, then manually upsample to 32-bpp and paint that onto our screen overlay. This removes variability and while it's a little ugly (and the font aliasing is not as good), it's "good enough" for a splash screen, and it removes the potential for random lags at startup.
1 parent ac2772d commit a56af48

File tree

1 file changed

+40
-89
lines changed

1 file changed

+40
-89
lines changed

Forms/Startup_Splash.frm

Lines changed: 40 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ Attribute VB_Exposed = False
4242
'PhotoDemon Splash Screen
4343
'Copyright 2001-2020 by Tanner Helland
4444
'Created: 15/April/01
45-
'Last updated: 10/June/20
46-
'Last update: rewrite as a proper layered window with alpha effects
45+
'Last updated: 18/December/20
46+
'Last update: remove GDI+ font code from splash rendering; GDI+ font creation functions have
47+
' unpredictable perf impacts, and it's reliably faster to use GDI on 24-bpp surfaces
48+
' (then produce a 32-bpp copy ourselves)
4749
'
4850
'Unless otherwise noted, all source code in this file is shared under a simplified BSD license.
4951
' Full license details are available in the LICENSE.md file, or at https://photodemon.org/license/
@@ -57,20 +59,6 @@ Private Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, ByRef l
5759
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
5860
Private Declare Function UpdateLayeredWindow Lib "user32" (ByVal hWnd As Long, ByVal hdcDst As Long, ByVal pptDst As Long, ByVal psize As Long, ByVal hdcSrc As Long, ByVal pptSrc As Long, ByVal crKey As Long, ByVal pblend As Long, ByVal dwFlags As Long) As Long
5961

60-
'Unfortunately, GDI doesn't render text onto 32-bpp surfaces correctly (the alpha channel gets ignored).
61-
' We need to use a more convoluted GDI+ approach, and because I don't have a safe wrapper class for
62-
' GDI+ font rendering, everything gets called manually.
63-
'Private Declare Function GdipCreateFont Lib "gdiplus" (ByVal srcFontFamily As Long, ByVal srcFontSize As Single, ByVal srcFontStyle As Long, ByVal srcMeasurementUnit As Long, ByRef dstCreatedFont As Long) As GP_Result
64-
'Private Declare Function GdipCreateFontFamilyFromName Lib "gdiplus" (ByVal ptrToSrcFontName As Long, ByVal srcFontCollection As Long, ByRef dstFontFamily As Long) As GP_Result
65-
Private Declare Function GdipCreateFontFromDC Lib "gdiplus" (ByVal srcDC As Long, ByRef dstCreatedFont As Long) As GP_Result
66-
Private Declare Function GdipCreateStringFormat Lib "gdiplus" (ByVal formatAttributes As Long, ByVal srcLanguage As Long, ByRef dstStringFormat As Long) As GP_Result
67-
Private Declare Function GdipDeleteFont Lib "gdiplus" (ByVal srcFont As Long) As GP_Result
68-
'Private Declare Function GdipDeleteFontFamily Lib "gdiplus" (ByVal srcFontFamily As Long) As GP_Result
69-
Private Declare Function GdipDeleteStringFormat Lib "gdiplus" (ByVal srcStringFormat As Long) As GP_Result
70-
Private Declare Function GdipDrawString Lib "gdiplus" (ByVal dstGraphics As Long, ByVal srcStringPtr As Long, ByVal strLength As Long, ByVal gdipFontHandle As Long, ByRef layoutRect As RectF, ByVal gdipStringFormat As Long, ByVal gdipBrush As Long) As GP_Result
71-
Private Declare Function GdipSetStringFormatAlign Lib "gdiplus" (ByVal dstStringFormat As Long, ByVal newAlignment As Long) As GP_Result
72-
Private Declare Function GdipSetTextRenderingHint Lib "gdiplus" (ByVal dstGraphics As Long, ByVal newRenderHintMode As Long) As GP_Result
73-
7462
'A logo, drop shadow and screen backdrop are used to generate the splash. These DIBs are released once m_splashDIB (below)
7563
' has been successfully assembled.
7664
Private m_logoDIB As pdDIB, m_shadowDIB As pdDIB
@@ -162,92 +150,55 @@ Public Sub PrepareRestOfSplash()
162150
Dim logoFontName As String
163151
If OS.IsVistaOrLater Then logoFontName = "Segoe UI" Else logoFontName = "Tahoma"
164152

165-
'Next, we need to create various GDI+ font objects. These may fail (particularly on
166-
' non-standard configs like Wine), so abandon font rendering if anything goes badly.
167-
Dim fontOK As Boolean, gpFontHandle As Long
168-
169-
'YIKES! For ages I have used the normal GDI+ pathway of GdipCreateFontFamily > GdipCreateFont,
170-
' but after experiencing random startup stutters on Win 10, I profiled and tracked down the
171-
' CreateFontFamily call as the culprit. It occasionally takes hundreds of ms, with occasional
172-
' lurches above ONE SECOND for that single function call! I have no idea when this changed,
173-
' but I've got a reliable workaround - turns out it's much much MUCH faster (e.g. < 10 ms vs
174-
' 500+ ms) to initialize a GDI font, attach it to a GDI DC, then indirectly create a GDI+ font
175-
' from said DC. Go figure.
176-
177-
'Original code here in case I ever need to revert this approach:
178-
'Dim gpFontFamily As Long
179-
'fontOK = (GdipCreateFontFamilyFromName(StrPtr(logoFontName), 0&, gpFontFamily) = GP_OK)
180-
'Const GP_FONTBOLD As Long = 1
181-
'If fontOK Then fontOK = (GdipCreateFont(gpFontFamily, logoFontSize, GP_FONTBOLD, GP_U_Point, gpFontHandle) = GP_OK)
153+
'Font color varies by build version.
154+
' (As a convenience, non-production builds are tagged RED; normal builds, BLUE.)
155+
Dim logoFontColor As Long
156+
If (PD_BUILD_QUALITY <> PD_PRODUCTION) Then logoFontColor = RGB(255, 50, 50) Else logoFontColor = RGB(50, 127, 255)
182157

183-
'New approach: use pdFont to create a font, because it's ~50x faster!
158+
'Create a GDI font with the desired settings
184159
Dim tmpFont As pdFont
185160
Set tmpFont = New pdFont
186161
tmpFont.SetFontFace logoFontName
187162
tmpFont.SetFontSize logoFontSize
188163
tmpFont.SetFontBold True
189164
tmpFont.CreateFontObject
190-
tmpFont.AttachToDC m_splashDIB.GetDIBDC
191-
fontOK = (GdipCreateFontFromDC(m_splashDIB.GetDIBDC, gpFontHandle) = GP_OK)
192165

193-
'Kill the temporary GDI font copy
166+
'Assemble the current version and description strings
167+
Dim versionString As String
168+
versionString = Trim$(g_Language.TranslateMessage("version %1", Updates.GetPhotoDemonVersion()))
169+
170+
'Create a dummy 24-bpp DIB and paint the version to it. (This is required for reliable
171+
' antialiasing behavior; GDI can't render text to 32-bpp surfaces if antialiasing is active,
172+
' so we need to paitn to a 24-bpp surface, then upsample to 32-bpp and fill in alpha manually.)
173+
Dim fntWidth As Long, fntHeight As Long
174+
fntWidth = tmpFont.GetWidthOfString(versionString)
175+
fntHeight = tmpFont.GetHeightOfString(versionString)
176+
177+
'Create a temporary 24-bpp target for the text. (We'll use white-on-black text to
178+
' simplify the process of manually upsampling to 32-bpp.)
179+
Dim tmpDIB As pdDIB
180+
Set tmpDIB = New pdDIB
181+
tmpDIB.CreateBlank fntWidth, fntHeight, 24, vbBlack
182+
183+
'Paint the version string
184+
tmpFont.SetFontColor vbWhite
185+
tmpFont.AttachToDC tmpDIB.GetDIBDC
186+
tmpFont.FastRenderText 0, 0, versionString
194187
tmpFont.ReleaseFromDC
195188
Set tmpFont = Nothing
196189

197-
'Next, we need a text formatter. Minimal options are used, but we do need right-alignment
198-
Dim gpStringFormat As Long
199-
If fontOK Then fontOK = (GdipCreateStringFormat(0&, 0&, gpStringFormat) = GP_OK)
200-
201-
Const GP_STRINGALIGNFAR As Long = 2
202-
If fontOK Then fontOK = (GdipSetStringFormatAlign(gpStringFormat, GP_STRINGALIGNFAR) = GP_OK)
203-
204-
'Everything else uses prebuilt PD classes
205-
Dim cSurface As pd2DSurface, cBrush As pd2DBrush
190+
'Convert the temporary DIB to 32-bpp, using the grayscale channel as the alpha guide
191+
tmpDIB.ConvertTo32bpp
192+
Dim grayArray() As Byte
193+
ReDim grayArray(0 To fntWidth - 1, 0 To fntHeight - 1) As Byte
194+
DIBs.GetDIBGrayscaleMap tmpDIB, grayArray, False
206195

207-
'If we created everything correctly, render version text
208-
If fontOK And (m_splashDIB.GetDIBDC <> 0) Then
209-
210-
'Next, we need a layout rect
211-
Dim fontRect As RectF
212-
fontRect.Top = pdLogoBottom + Interface.FixDPI(8)
213-
fontRect.Left = 0
214-
fontRect.Width = pdLogoRight
215-
fontRect.Height = fontRect.Top + 100
216-
217-
'Assemble the current version and description strings
218-
Dim versionString As String
219-
versionString = Trim$(g_Language.TranslateMessage("version %1", Updates.GetPhotoDemonVersion()))
220-
221-
'Wrap a GDI+ surface around the destination DIB
222-
Set cSurface = New pd2DSurface
223-
cSurface.WrapSurfaceAroundPDDIB m_splashDIB
224-
225-
'Activate grayscale antialiasing + hinting
226-
Const GP_TextRenderingHintAntiAliasGridFit As Long = 3
227-
GdipSetTextRenderingHint cSurface.GetHandle, GP_TextRenderingHintAntiAliasGridFit
228-
229-
'Next, font color.
230-
' (As a convenience, non-production builds are tagged RED; normal builds, BLUE.)
231-
Dim logoFontColor As Long
232-
If (PD_BUILD_QUALITY <> PD_PRODUCTION) Then logoFontColor = RGB(255, 50, 50) Else logoFontColor = RGB(50, 127, 255)
233-
234-
'Text gets painted by a stock GDI+ brush
235-
Set cBrush = New pd2DBrush
236-
cBrush.SetBrushColor logoFontColor
237-
238-
'Render the finished text!
239-
GdipDrawString cSurface.GetHandle, StrPtr(versionString), Len(versionString), gpFontHandle, fontRect, gpStringFormat, cBrush.GetHandle
240-
241-
End If
242-
243-
'Free various font and string objects
244-
Set cBrush = Nothing
245-
Set cSurface = Nothing
246-
If (gpStringFormat <> 0) Then GdipDeleteStringFormat gpStringFormat
247-
If (gpFontHandle <> 0) Then GdipDeleteFont gpFontHandle
196+
tmpDIB.CreateBlank fntWidth, fntHeight, 32, logoFontColor, 255
197+
DIBs.ApplyTransparencyTable tmpDIB, grayArray
198+
tmpDIB.SetAlphaPremultiplication True
248199

249-
'A GDI+ font family object is no longer allocated; see above comments for details
250-
'If (gpFontFamily <> 0) Then GdipDeleteFontFamily gpFontFamily
200+
'Paint the final 32-bpp version image onto the splash screen
201+
tmpDIB.AlphaBlendToDC m_splashDIB.GetDIBDC, dstX:=pdLogoRight - fntWidth, dstY:=pdLogoBottom + Interface.FixDPI(8)
251202

252203
'We now have a back buffer with everything the splash screen requires
253204
' (except the progress bar, which will be drawn later). Create a front buffer

0 commit comments

Comments
 (0)