From b53f7ac904dd56f5e2885c8e6d445a157bc69e07 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 7 Feb 2021 01:27:00 +0100 Subject: [PATCH] Restore support for using faster dotted pens in wxMSW Changes of d245dc9e1f (Fix drawing of dotted lines with wxDC in wxMSW, 2020-03-27) improved the appearance of dotted and dashed lines in wxMSW but at the expense of significant (up to a factor of 300) slowdown. Allow the applications for which the drawing performance is important to explicitly request the old behaviour, with uglier, but faster, pens by choosing to use low quality pens. Update the graphics benchmark to allow specifying the pen quality and verify that the performance when using it is the same as before 3.1.4. See https://github.com/wxWidgets/wxWidgets/pull/2218 See #7097. Closes #18875. --- docs/changes.txt | 5 +++ include/wx/msw/pen.h | 2 + include/wx/pen.h | 19 +++++++++ interface/wx/pen.h | 78 +++++++++++++++++++++++++++++++++++ src/msw/pen.cpp | 73 ++++++++++++++++++++++++++++++-- tests/benchmarks/graphics.cpp | 41 +++++++++++++++--- 6 files changed, 208 insertions(+), 10 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index a80274f466..c13206b73a 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -47,6 +47,11 @@ Changes in behaviour not resulting in compilation errors event handlers if you want the standard key combinations such as Alt-Space or Alt-F4 to work. +- wxMSW port now uses better appearing but much slower pens for dotted and + dashed lines. Use wxPenInfo::LowQuality() or wxPen::SetQuality() to return to + the previous version behaviour and performance characteristics if you are + drawing many lines using such pens. + - wxOSX port uses default button margins for wxBitmapButton by default, for consistency with the other ports. You now need to call SetMargins(0, 0) explicitly if you really don't want to have any margins in your buttons. diff --git a/include/wx/msw/pen.h b/include/wx/msw/pen.h index 00e021bd87..451da2d06f 100644 --- a/include/wx/msw/pen.h +++ b/include/wx/msw/pen.h @@ -43,12 +43,14 @@ public: void SetDashes(int nb_dashes, const wxDash *dash) wxOVERRIDE; void SetJoin(wxPenJoin join) wxOVERRIDE; void SetCap(wxPenCap cap) wxOVERRIDE; + void SetQuality(wxPenQuality quality) wxOVERRIDE; wxColour GetColour() const wxOVERRIDE; int GetWidth() const wxOVERRIDE; wxPenStyle GetStyle() const wxOVERRIDE; wxPenJoin GetJoin() const wxOVERRIDE; wxPenCap GetCap() const wxOVERRIDE; + wxPenQuality GetQuality() const wxOVERRIDE; int GetDashes(wxDash** ptr) const wxOVERRIDE; wxDash* GetDash() const; int GetDashCount() const; diff --git a/include/wx/pen.h b/include/wx/pen.h index 411e3b882d..8fbdeabad9 100644 --- a/include/wx/pen.h +++ b/include/wx/pen.h @@ -14,6 +14,14 @@ #include "wx/gdiobj.h" #include "wx/peninfobase.h" +// Possible values for pen quality. +enum wxPenQuality +{ + wxPEN_QUALITY_DEFAULT, // Select the appropriate quality automatically. + wxPEN_QUALITY_LOW, // Less good looking but faster. + wxPEN_QUALITY_HIGH // Best looking, at the expense of speed. +}; + // ---------------------------------------------------------------------------- // wxPenInfo contains all parameters describing a wxPen // ---------------------------------------------------------------------------- @@ -27,6 +35,7 @@ public: : wxPenInfoBase(colour, style) { m_width = width; + m_quality = wxPEN_QUALITY_DEFAULT; } // Setters @@ -34,12 +43,20 @@ public: wxPenInfo& Width(int width) { m_width = width; return *this; } + wxPenInfo& Quality(wxPenQuality quality) + { m_quality = quality; return *this; } + wxPenInfo& LowQuality() { return Quality(wxPEN_QUALITY_LOW); } + wxPenInfo& HighQuality() { return Quality(wxPEN_QUALITY_HIGH); } + // Accessors int GetWidth() const { return m_width; } + wxPenQuality GetQuality() const { return m_quality; } + private: int m_width; + wxPenQuality m_quality; }; @@ -57,12 +74,14 @@ public: virtual void SetDashes(int nb_dashes, const wxDash *dash) = 0; virtual void SetJoin(wxPenJoin join) = 0; virtual void SetCap(wxPenCap cap) = 0; + virtual void SetQuality(wxPenQuality quality) { wxUnusedVar(quality); } virtual wxColour GetColour() const = 0; virtual wxBitmap *GetStipple() const = 0; virtual wxPenStyle GetStyle() const = 0; virtual wxPenJoin GetJoin() const = 0; virtual wxPenCap GetCap() const = 0; + virtual wxPenQuality GetQuality() const { return wxPEN_QUALITY_DEFAULT; } virtual int GetWidth() const = 0; virtual int GetDashes(wxDash **ptr) const = 0; diff --git a/interface/wx/pen.h b/interface/wx/pen.h index ceeab7ad9e..5d549b544e 100644 --- a/interface/wx/pen.h +++ b/interface/wx/pen.h @@ -70,6 +70,31 @@ enum wxPenStyle /**< Last of the hatch styles (inclusive). */ }; +/** + Possible values for pen quality. + + Pen quality is currently only used in wxMSW, the other ports ignore it and + always use the same default pen quality. + + In wxMSW the choice of quality affects whether "cosmetic" or "geometric" + native pens are used in situations when both are usable. Notably, for + dotted and dashed pens of width 1, high quality geometric pens are used by + default since wxWidgets 3.1.4, while previous versions used lower quality + but much faster cosmetic pens. If drawing performance is more important + than the exact appearance of the lines drawn using this pen, low quality + may be explicitly selected. + + See wxPenInfo::Quality() and wxPen::SetQuality(). + + @since 3.1.5 + */ +enum wxPenQuality +{ + wxPEN_QUALITY_DEFAULT, ///< Select the appropriate quality automatically. + wxPEN_QUALITY_LOW, ///< Less good looking but faster. + wxPEN_QUALITY_HIGH ///< Best looking, at the expense of speed. +}; + /** The possible join values of a wxPen. @@ -138,11 +163,42 @@ public: wxPenInfo& Cap(wxPenCap cap); + /** + Set the pen quality. + + Using LowQuality() or HighQuality() is usually more convenient. + + @see wxPen::SetQuality() + + @since 3.1.5 + */ + wxPenInfo& Quality(wxPenQuality quality); + + /** + Set low pen quality. + + This is the same as calling Quality() with ::wxPEN_QUALITY_LOW. + + @since 3.1.5 + */ + wxPenInfo& LowQuality(); + + /** + Set high pen quality. + + This is the same as calling Quality() with ::wxPEN_QUALITY_HIGH. + + @since 3.1.5 + */ + wxPenInfo& HighQuality(); + + wxPenInfo& LowQuality(); wxColour GetColour() const; wxBitmap GetStipple() const; wxPenStyle GetStyle() const; wxPenJoin GetJoin() const; wxPenCap GetCap() const; + wxPenQuality GetQuality() const; int GetDashes(wxDash **ptr); int GetDashCount() const; wxDash* GetDash() const; @@ -279,6 +335,15 @@ public: */ virtual wxPenCap GetCap() const; + /** + Returns the pen quality. + + The default is ::wxPEN_QUALITY_DEFAULT. + + @see wxPenQuality, SetQuality() + */ + wxPenQuality GetQuality() const; + /** Returns a reference to the pen colour. @@ -375,6 +440,19 @@ public: */ virtual void SetCap(wxPenCap capStyle); + /** + Sets the pen quality. + + Explicitly selecting low pen quality may be useful in wxMSW if drawing + performance is more important than the exact appearance of the lines + drawn with this pen. + + @see wxPenQuality + + @since 3.1.5 + */ + void SetQuality(wxPenQuality quality); + //@{ /** The pen's colour is changed to the given colour. diff --git a/src/msw/pen.cpp b/src/msw/pen.cpp index f1a0966337..42e833e2ab 100644 --- a/src/msw/pen.cpp +++ b/src/msw/pen.cpp @@ -53,6 +53,7 @@ public: m_width == data.m_width && m_join == data.m_join && m_cap == data.m_cap && + m_quality == data.m_quality && m_colour == data.m_colour && (m_style != wxPENSTYLE_STIPPLE || m_stipple.IsSameAs(data.m_stipple)) && (m_style != wxPENSTYLE_USER_DASH || @@ -69,6 +70,7 @@ public: wxPenStyle GetStyle() const { return m_style; } wxPenJoin GetJoin() const { return m_join; } wxPenCap GetCap() const { return m_cap; } + wxPenQuality GetQuality() const { return m_quality; } wxDash* GetDash() const { return m_dash; } int GetDashCount() const { return m_nbDash; } wxBitmap* GetStipple() const { return const_cast(&m_stipple); } @@ -94,6 +96,7 @@ public: void SetJoin(wxPenJoin join) { Free(); m_join = join; } void SetCap(wxPenCap cap) { Free(); m_cap = cap; } + void SetQuality(wxPenQuality quality) { Free(); m_quality = quality; } // HPEN management @@ -119,6 +122,7 @@ private: { m_join = wxJOIN_ROUND; m_cap = wxCAP_ROUND; + m_quality = wxPEN_QUALITY_DEFAULT; m_nbDash = 0; m_dash = NULL; m_hPen = 0; @@ -128,6 +132,7 @@ private: wxPenStyle m_style; wxPenJoin m_join; wxPenCap m_cap; + wxPenQuality m_quality; wxBitmap m_stipple; int m_nbDash; wxDash * m_dash; @@ -161,6 +166,7 @@ wxPenRefData::wxPenRefData(const wxPenRefData& data) m_width = data.m_width; m_join = data.m_join; m_cap = data.m_cap; + m_quality = data.m_quality; m_nbDash = data.m_nbDash; m_dash = data.m_dash; m_hPen = 0; @@ -176,6 +182,7 @@ wxPenRefData::wxPenRefData(const wxPenInfo& info) m_width = info.GetWidth(); m_join = info.GetJoin(); m_cap = info.GetCap(); + m_quality = info.GetQuality(); m_nbDash = info.GetDashes(&m_dash); } @@ -278,10 +285,54 @@ bool wxPenRefData::Alloc() const COLORREF col = m_colour.GetPixel(); // check if it's a standard kind of pen which can be created with just - // CreatePen() - if ( m_join == wxJOIN_ROUND && - m_cap == wxCAP_ROUND && - m_style == wxPENSTYLE_SOLID ) + // CreatePen(), which always creates cosmetic pens that don't support all + // wxPen features and are less precise (e.g. draw dotted lines as dashes + // rather than real dots), but much, much faster than geometric pens created + // by ExtCreatePen(), see #18875, so we still prefer to use them if possible + // unless it's explicitly disabled by setting the quality to "high" + bool useCreatePen = m_quality != wxPEN_QUALITY_HIGH; + + if ( useCreatePen ) + { + switch ( m_style ) + { + case wxPENSTYLE_SOLID: + // No problem with using cosmetic pens for solid lines. + break; + + case wxPENSTYLE_DOT: + case wxPENSTYLE_LONG_DASH: + case wxPENSTYLE_SHORT_DASH: + case wxPENSTYLE_DOT_DASH: + if ( m_width > 1 ) + { + // Cosmetic pens with these styles would result in solid + // lines for pens wider than a single pixel, so never use + // them in this case. + useCreatePen = false; + } + else + { + // For the single pixel pens we can use cosmetic pens, but + // they look ugly, so we prefer to not do it by default, + // however this can be explicitly requested if speed is more + // important than the exact appearance. + useCreatePen = m_quality == wxPEN_QUALITY_LOW; + } + break; + + default: + // Other styles are not supported by cosmetic pens at all. + useCreatePen = false; + break; + } + } + + // Join and cap styles are also not supported for cosmetic pens. + if ( m_join != wxJOIN_ROUND || m_cap != wxCAP_ROUND ) + useCreatePen = false; + + if ( useCreatePen ) { m_hPen = ::CreatePen(ConvertPenStyle(m_style), m_width, col); } @@ -505,6 +556,13 @@ void wxPen::SetCap(wxPenCap cap) M_PENDATA->SetCap(cap); } +void wxPen::SetQuality(wxPenQuality quality) +{ + AllocExclusive(); + + M_PENDATA->SetQuality(quality); +} + wxColour wxPen::GetColour() const { wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid pen") ); @@ -540,6 +598,13 @@ wxPenCap wxPen::GetCap() const return M_PENDATA->GetCap(); } +wxPenQuality wxPen::GetQuality() const +{ + wxCHECK_MSG( IsOk(), wxPEN_QUALITY_DEFAULT, wxT("invalid pen") ); + + return M_PENDATA->GetQuality(); +} + int wxPen::GetDashes(wxDash** ptr) const { wxCHECK_MSG( IsOk(), -1, wxT("invalid pen") ); diff --git a/tests/benchmarks/graphics.cpp b/tests/benchmarks/graphics.cpp index bbbec0cd5a..cbe2be7f30 100644 --- a/tests/benchmarks/graphics.cpp +++ b/tests/benchmarks/graphics.cpp @@ -52,6 +52,7 @@ struct GraphicsBenchmarkOptions mapMode = 0; penWidth = 0; penStyle = wxPENSTYLE_INVALID; + penQuality = wxPEN_QUALITY_DEFAULT; width = 800; height = 600; @@ -87,6 +88,7 @@ struct GraphicsBenchmarkOptions numIters; wxPenStyle penStyle; + wxPenQuality penQuality; bool testBitmaps, testImages, @@ -410,16 +412,29 @@ private: { if ( opts.mapMode != 0 ) dc.SetMapMode((wxMappingMode)opts.mapMode); + + bool setPen = false; + wxPenInfo penInfo(*wxWHITE); if ( opts.penWidth != 0 ) - dc.SetPen(wxPen(*wxWHITE, opts.penWidth)); + { + penInfo.Width(opts.penWidth); + setPen = true; + } + if ( opts.penStyle != wxPENSTYLE_INVALID ) { - wxPen pen = dc.GetPen(); - if ( !pen.IsOk() ) - pen = wxPen(*wxWHITE, 1); - pen.SetStyle(opts.penStyle); - dc.SetPen(pen); + penInfo.Style(opts.penStyle); + setPen = true; } + + if ( opts.penQuality != wxPEN_QUALITY_DEFAULT ) + { + penInfo.Quality(opts.penQuality); + setPen = true; + } + + if ( setPen ) + dc.SetPen(penInfo); } void BenchmarkLines(const wxString& msg, wxDC& dc) @@ -865,6 +880,7 @@ public: { wxCMD_LINE_OPTION, "m", "map-mode", "", wxCMD_LINE_VAL_NUMBER }, { wxCMD_LINE_OPTION, "p", "pen-width", "", wxCMD_LINE_VAL_NUMBER }, { wxCMD_LINE_OPTION, "s", "pen-style", "solid | dot | long_dash | short_dash", wxCMD_LINE_VAL_STRING }, + { wxCMD_LINE_OPTION, "", "pen-quality", "default | low | high", wxCMD_LINE_VAL_STRING }, { wxCMD_LINE_OPTION, "w", "width", "", wxCMD_LINE_VAL_NUMBER }, { wxCMD_LINE_OPTION, "h", "height", "", wxCMD_LINE_VAL_NUMBER }, { wxCMD_LINE_OPTION, "I", "images", "", wxCMD_LINE_VAL_NUMBER }, @@ -913,6 +929,19 @@ public: } } } + wxString penQuality; + if ( parser.Found("pen-quality", &penQuality) ) + { + if ( penQuality == "low" ) + opts.penQuality = wxPEN_QUALITY_LOW; + else if ( penQuality == "high" ) + opts.penQuality = wxPEN_QUALITY_HIGH; + else if ( penQuality != "default" ) + { + wxLogError("Unsupported pen quality."); + return false; + } + } if ( parser.Found("w", &opts.width) && opts.width < 1 ) return false; if ( parser.Found("h", &opts.height) && opts.height < 1 )