As previously discussed at [feature-requests:#1427], defaultRenderingParams and customClearTypeRenderingParams created inside LoadD2DOnce() are steal after Windows settings change or when the window moved to a different monitor. [bugs:#2305] may be related.
CreateMonitorRenderingParams() can be used to fix the problem, there is sample code at https://docs.microsoft.com/en-us/windows/win32/DirectWrite/how-to-add-support-for-multiple-monitors
I find three ways to implement this:
1. move both parameters local to SurfaceD2D::SetFontQuality() by storing HWND or HMONITOR in Init() method. This change makes the method 100x slow and increased total paint time due to CreateMonitorRenderingParams() (which is slower than current used CreateRenderingParams()). The slow might be the reason they were created once, this is also not efficient as both parameters are recreated each time the window gets paint.
2. add void *defaultRenderingParams and void *customRenderingParams into SurfaceMode and EditModel, then create them in Message::SetTechnology, WM_SETTINGCHANGE and WM_WINDOWPOSCHANGED.
3. Change cbWndExtra to 3*sizeof(LONG_PTR) for all three window classes and store the two rendering parameters at index sizeof(LONG_PTR) and 2*sizeof(LONG_PTR).
Changes by move both parameters local to
SurfaceD2D::SetFontQuality(), can be used to measure paint performance impact.Patch (see https://github.com/zufuliu/notepad2/commit/8043a8fdeaaa88cf4c5470aa4f835f43129138fa) for adding
defaultRenderingParamsandcustomRenderingParamsintoSurfaceMode. The change addedCurrentSurfaceMode()andGetSurfaceMode(int codePage)intoEditModeland overrides the later for D2D insideScintillaWin(which holds twostd::unique_ptr<IDWriteRenderingParams, UnknownReleaser>).PS.
pRenderTargetandsysCaretBitmapcan be changed tostd::unique_ptrjust like the changes in GTK.Edit:
hPreviousMonitorshould be renamed tohCurrentMonitor.Last edit: Zufu Liu 2022-02-27
Updated patch: renamed
hPreviousMonitortohCurrentMonitor, inMessage::SetTechnologychanged the call toUpdateRenderingParams(true)as cleartype settings may be changed between changing technologies.Since this moved the define for
SPI_GETFONTSMOOTHINGCONTRAST, updated [ca94b3] the Windows API version to0x0A00(Windows 10) and removed all the definitions used to cover up old SDKs. Windows 10 was released in 2015. This has the small downside of not warning when newer APIs are called but that will be checked by building for XP.Related
Commit: [ca94b3]
Inlining
CurrentSurfaceModeandGetSurfaceModein headers doesn't appear justified as they aren't performance-sensitive, could become non-trivial, andGetSurfaceModeis virtual. Only definingScintillaWin::GetSurfaceModeforUSE_D2Dmode is confusing with GDI having two distinct implementations so should probably always be declared with an#ifin the implementation to perform the more complex case.Updated patch with suggested changes,
It appears that there could be pointer lifetime issues for the rendering params. Pixmap surfaces persist between paints and potentially past rendering param invalidation (window move or setting change) but they hold onto
default/customRenderingParams. If this occurs, the pointers may point to freed memory leading to crashes.Another issue here is that with this approach changes may be required to the platform-independent code for different platforms. An approach that allowed each platform to extend
SurfaceModeindependently would be more maintainable.At the moment these Pixmap surfaces (in EditView and MarginView) does not draw any text, so they just hold dangle pointers. All can be fixed by call
SetMode(model.CurrentMode())before drawing.Maybe Font like
SetMode(std::shared_ptr<SurfaceMode> mode)would fix the lifetime issue.pixmapLineis used to draw text in buffered drawing mode. Adding calls toInvalidateStyleData()orInvalidateStyleRedraw()after any effectualUpdateRenderingParams(not currently included forWM_WINDOWPOSCHANGED) would clear the pixmap which would then be recreated with the new settings. This still leaves bare untyped pointers inSurfaceModethat could later be a lifetime hazard.Changing the signature of
Surface::SetModebreaks platform compatibility. Adding ashared_ptr<PlatformMode>field toSurfaceModewould be less trouble. Platform code could subclass an emptyPlatformModeadding platform-specific fields.Another possibility would be to move more of
Surfacecreation intoEditorwhere theScintillaWinsubclass would override the method(s) and push rendering params into anySurfaceD2D.CallTip::CallTipStartwhich lays out text doesn't get the full surface mode as it doesn't have access toEditModel::CurrentSurfaceMode(). May not matter for measurement but this could be problem if other additions made toSurfaceMode. Should have the measuring surface passed in fromScintillaBase::CallTipShow.Updated patch:
1. add
surface->SetMode(CurrentSurfaceMode());inEditor::PaintSelMargin()as the surface may be used to draw text insideMarginView::PaintOneMargin().2. add
DropGraphics()afterUpdateRenderingParams()insideWM_SETTINGCHANGEandWM_WINDOWPOSCHANGED.3. change
CallTipStartinto following, no font creation (just use already created font fromStyleCallTiporStyleDefault)Committed changes to
CallTipsurface and font [eab98f] but omittedstd::moveon font.SetForeBack[f29604]. Minor cosmetic changes in [bbcf99].Related
Commit: [bbcf99]
Commit: [eab98f]
Commit: [f29604]
std::movewas suggested by clang-tidy:warning: parameter 'font_' is passed by value and only copied once; consider moving it to avoid unnecessary copies [performance-unnecessary-value-param].DropGraphics()inMessage::SetTechnology,Message::SetBidirectionalandcase WM_SETTINGCHANGEcan be omitted.Committed removal of redundant
DropGraphicswith [46652e].Committed basic implementation of
CurrentSurfaceModewithout rendering parameters additions in [cd45e4].Related
Commit: [46652e]
Commit: [cd45e4]
codePagefield inCallTipcan be removed then.I have tested that,
font = std::move(font_)andfont = font_both causeuse_count()forstyle.fontto be increased by one after callCallTipStart().The
codePagefield is used in the Qt implementation and possibly out-of-repo platform layers.There's likely a small performance and size code benefit to adding a
std::movethere but it's insignificant when a window is about to be displayed. Code that isn't performance-sensitive should be optimized for clarity and brevity.Here is an implementation (
ScintillaWin::SetRenderingParams) that hides the rendering params inside the platform layer. AddRef is used so the pointers inside SurfaceD2D won't dangle.Oh, not even thought about COM object are reference counted.
Some small changes that I think would improve the code:
1. in
DropGraphics()insideWM_SETTINGCHANGEcan be omitted.2.
technologyOpt.value_of(technology)seems more readable.3.
SurfaceD2D::AllocatePixMap()missedSetRenderingParams().I think at the moment there is no need to override
CreateMeasurementSurface()andCreateDrawingSurface()as rendering parameters are only useful for D2D drawing surface which only created fromScintillaWin::CTWndProc(),ScintillaWin::PaintDC()orSurfaceD2D::AllocatePixMap().SetRenderingParams()insideScintillaWincan be moved into respect else branches.ISetRenderingParams3.patchhas those changes.There should probably be a call to
RedrawinWM_SETTINGCHANGE. Without that, sometimes the window is redrawn with the new settings and sometimes it isn't. The change may be delayed until later like when focus changes to Scintilla. Redraw is less likely when the clear type and settings windows don't overlap Scintilla.RenderingParamsStdShared.patchchanges the implementation to use a separately allocatedRenderingParamsstruct which is shared betweenScintillaWinand it's surfaces. That way, there is less likelihood of stale rendering params in pixmaps and explicit reference counting is avoided. Uncertain which approach is better.Last edit: Neil Hodgson 2022-03-07
RenderingParamsStdShared.patch seems simpler and more extensible.
I don't understand this: as pixmap graphics are dropped inside in
InvalidateStyleData()next paint would allocate newsurfaceWindowand pixmaps with new settings.I think the problem is that there isn't always another paint.
To reproduce the issue, move the app hosting Scintilla to one side so it will not overlap other windows. Open font settings and ensure it doesn't overlap the Scintilla window, click on
Adjust ClearType text. TheClearType Text Tunermust also not overlap Scintilla. Choose distinctive settings - I flip between darkest and lightest text. PressFinishand focus/active should return to theFontssettings panel and not go to Scintilla. Scintilla may not show with the new font settings until it receives focus when it does a full redraw.(Run Notepad2 as console app) I see
PaintDCis called twice (after receivedWM_SETTINGCHANGE) after finish ClearType tuner without focus in. Observed the same behavior after move Notepad2 and the console window into virtual destop2 in task view, and change settings in destop1.It seems we missed a notification on check/uncheck "Enable ClearType", only app menu is repainted (but
PaintDCis actually called), while text in in windows Notepad did get repainted.With SciTE, the only
WM_SETTINGCHANGEis withwParam == 0x2013which isSPI_SETFONTSMOOTHINGORIENTATION,lParam == "". Two of those are received and SciTE forwards them to both the edit and output pane so total of 4 calls. There aren't any notifications forSPI_SETFONTSMOOTHING,SPI_SETFONTSMOOTHINGCONTRAST,SPI_SETCLEARTYPE, orSPI_SETFONTSMOOTHINGTYPEeven when ClearType is turned on/off.Redraw isn't too expensive so it's OK to call it for every
WM_SETTINGCHANGEalthough its possible there are badly behaved apps that set settings excessively.Committed
std::shared_ptrtechnique as [b1aa62] with call toRedrawwhen setting change notification received.Related
Commit: [b1aa62]
for redraw, what about call
InvalidateStyleRedraw()instead.