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.
This commit is contained in:
Vadim Zeitlin 2021-02-07 01:27:00 +01:00
parent a5afa85c0a
commit b53f7ac904
6 changed files with 208 additions and 10 deletions

View File

@ -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 event handlers if you want the standard key combinations such as Alt-Space or
Alt-F4 to work. 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 - wxOSX port uses default button margins for wxBitmapButton by default, for
consistency with the other ports. You now need to call SetMargins(0, 0) 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. explicitly if you really don't want to have any margins in your buttons.

View File

@ -43,12 +43,14 @@ public:
void SetDashes(int nb_dashes, const wxDash *dash) wxOVERRIDE; void SetDashes(int nb_dashes, const wxDash *dash) wxOVERRIDE;
void SetJoin(wxPenJoin join) wxOVERRIDE; void SetJoin(wxPenJoin join) wxOVERRIDE;
void SetCap(wxPenCap cap) wxOVERRIDE; void SetCap(wxPenCap cap) wxOVERRIDE;
void SetQuality(wxPenQuality quality) wxOVERRIDE;
wxColour GetColour() const wxOVERRIDE; wxColour GetColour() const wxOVERRIDE;
int GetWidth() const wxOVERRIDE; int GetWidth() const wxOVERRIDE;
wxPenStyle GetStyle() const wxOVERRIDE; wxPenStyle GetStyle() const wxOVERRIDE;
wxPenJoin GetJoin() const wxOVERRIDE; wxPenJoin GetJoin() const wxOVERRIDE;
wxPenCap GetCap() const wxOVERRIDE; wxPenCap GetCap() const wxOVERRIDE;
wxPenQuality GetQuality() const wxOVERRIDE;
int GetDashes(wxDash** ptr) const wxOVERRIDE; int GetDashes(wxDash** ptr) const wxOVERRIDE;
wxDash* GetDash() const; wxDash* GetDash() const;
int GetDashCount() const; int GetDashCount() const;

View File

@ -14,6 +14,14 @@
#include "wx/gdiobj.h" #include "wx/gdiobj.h"
#include "wx/peninfobase.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 // wxPenInfo contains all parameters describing a wxPen
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -27,6 +35,7 @@ public:
: wxPenInfoBase<wxPenInfo>(colour, style) : wxPenInfoBase<wxPenInfo>(colour, style)
{ {
m_width = width; m_width = width;
m_quality = wxPEN_QUALITY_DEFAULT;
} }
// Setters // Setters
@ -34,12 +43,20 @@ public:
wxPenInfo& Width(int width) wxPenInfo& Width(int width)
{ m_width = width; return *this; } { 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 // Accessors
int GetWidth() const { return m_width; } int GetWidth() const { return m_width; }
wxPenQuality GetQuality() const { return m_quality; }
private: private:
int m_width; int m_width;
wxPenQuality m_quality;
}; };
@ -57,12 +74,14 @@ public:
virtual void SetDashes(int nb_dashes, const wxDash *dash) = 0; virtual void SetDashes(int nb_dashes, const wxDash *dash) = 0;
virtual void SetJoin(wxPenJoin join) = 0; virtual void SetJoin(wxPenJoin join) = 0;
virtual void SetCap(wxPenCap cap) = 0; virtual void SetCap(wxPenCap cap) = 0;
virtual void SetQuality(wxPenQuality quality) { wxUnusedVar(quality); }
virtual wxColour GetColour() const = 0; virtual wxColour GetColour() const = 0;
virtual wxBitmap *GetStipple() const = 0; virtual wxBitmap *GetStipple() const = 0;
virtual wxPenStyle GetStyle() const = 0; virtual wxPenStyle GetStyle() const = 0;
virtual wxPenJoin GetJoin() const = 0; virtual wxPenJoin GetJoin() const = 0;
virtual wxPenCap GetCap() const = 0; virtual wxPenCap GetCap() const = 0;
virtual wxPenQuality GetQuality() const { return wxPEN_QUALITY_DEFAULT; }
virtual int GetWidth() const = 0; virtual int GetWidth() const = 0;
virtual int GetDashes(wxDash **ptr) const = 0; virtual int GetDashes(wxDash **ptr) const = 0;

View File

@ -70,6 +70,31 @@ enum wxPenStyle
/**< Last of the hatch styles (inclusive). */ /**< 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. The possible join values of a wxPen.
@ -138,11 +163,42 @@ public:
wxPenInfo& Cap(wxPenCap cap); 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; wxColour GetColour() const;
wxBitmap GetStipple() const; wxBitmap GetStipple() const;
wxPenStyle GetStyle() const; wxPenStyle GetStyle() const;
wxPenJoin GetJoin() const; wxPenJoin GetJoin() const;
wxPenCap GetCap() const; wxPenCap GetCap() const;
wxPenQuality GetQuality() const;
int GetDashes(wxDash **ptr); int GetDashes(wxDash **ptr);
int GetDashCount() const; int GetDashCount() const;
wxDash* GetDash() const; wxDash* GetDash() const;
@ -279,6 +335,15 @@ public:
*/ */
virtual wxPenCap GetCap() const; 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. Returns a reference to the pen colour.
@ -375,6 +440,19 @@ public:
*/ */
virtual void SetCap(wxPenCap capStyle); 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. The pen's colour is changed to the given colour.

View File

@ -53,6 +53,7 @@ public:
m_width == data.m_width && m_width == data.m_width &&
m_join == data.m_join && m_join == data.m_join &&
m_cap == data.m_cap && m_cap == data.m_cap &&
m_quality == data.m_quality &&
m_colour == data.m_colour && m_colour == data.m_colour &&
(m_style != wxPENSTYLE_STIPPLE || m_stipple.IsSameAs(data.m_stipple)) && (m_style != wxPENSTYLE_STIPPLE || m_stipple.IsSameAs(data.m_stipple)) &&
(m_style != wxPENSTYLE_USER_DASH || (m_style != wxPENSTYLE_USER_DASH ||
@ -69,6 +70,7 @@ public:
wxPenStyle GetStyle() const { return m_style; } wxPenStyle GetStyle() const { return m_style; }
wxPenJoin GetJoin() const { return m_join; } wxPenJoin GetJoin() const { return m_join; }
wxPenCap GetCap() const { return m_cap; } wxPenCap GetCap() const { return m_cap; }
wxPenQuality GetQuality() const { return m_quality; }
wxDash* GetDash() const { return m_dash; } wxDash* GetDash() const { return m_dash; }
int GetDashCount() const { return m_nbDash; } int GetDashCount() const { return m_nbDash; }
wxBitmap* GetStipple() const { return const_cast<wxBitmap *>(&m_stipple); } wxBitmap* GetStipple() const { return const_cast<wxBitmap *>(&m_stipple); }
@ -94,6 +96,7 @@ public:
void SetJoin(wxPenJoin join) { Free(); m_join = join; } void SetJoin(wxPenJoin join) { Free(); m_join = join; }
void SetCap(wxPenCap cap) { Free(); m_cap = cap; } void SetCap(wxPenCap cap) { Free(); m_cap = cap; }
void SetQuality(wxPenQuality quality) { Free(); m_quality = quality; }
// HPEN management // HPEN management
@ -119,6 +122,7 @@ private:
{ {
m_join = wxJOIN_ROUND; m_join = wxJOIN_ROUND;
m_cap = wxCAP_ROUND; m_cap = wxCAP_ROUND;
m_quality = wxPEN_QUALITY_DEFAULT;
m_nbDash = 0; m_nbDash = 0;
m_dash = NULL; m_dash = NULL;
m_hPen = 0; m_hPen = 0;
@ -128,6 +132,7 @@ private:
wxPenStyle m_style; wxPenStyle m_style;
wxPenJoin m_join; wxPenJoin m_join;
wxPenCap m_cap; wxPenCap m_cap;
wxPenQuality m_quality;
wxBitmap m_stipple; wxBitmap m_stipple;
int m_nbDash; int m_nbDash;
wxDash * m_dash; wxDash * m_dash;
@ -161,6 +166,7 @@ wxPenRefData::wxPenRefData(const wxPenRefData& data)
m_width = data.m_width; m_width = data.m_width;
m_join = data.m_join; m_join = data.m_join;
m_cap = data.m_cap; m_cap = data.m_cap;
m_quality = data.m_quality;
m_nbDash = data.m_nbDash; m_nbDash = data.m_nbDash;
m_dash = data.m_dash; m_dash = data.m_dash;
m_hPen = 0; m_hPen = 0;
@ -176,6 +182,7 @@ wxPenRefData::wxPenRefData(const wxPenInfo& info)
m_width = info.GetWidth(); m_width = info.GetWidth();
m_join = info.GetJoin(); m_join = info.GetJoin();
m_cap = info.GetCap(); m_cap = info.GetCap();
m_quality = info.GetQuality();
m_nbDash = info.GetDashes(&m_dash); m_nbDash = info.GetDashes(&m_dash);
} }
@ -278,10 +285,54 @@ bool wxPenRefData::Alloc()
const COLORREF col = m_colour.GetPixel(); const COLORREF col = m_colour.GetPixel();
// check if it's a standard kind of pen which can be created with just // check if it's a standard kind of pen which can be created with just
// CreatePen() // CreatePen(), which always creates cosmetic pens that don't support all
if ( m_join == wxJOIN_ROUND && // wxPen features and are less precise (e.g. draw dotted lines as dashes
m_cap == wxCAP_ROUND && // rather than real dots), but much, much faster than geometric pens created
m_style == wxPENSTYLE_SOLID ) // 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); m_hPen = ::CreatePen(ConvertPenStyle(m_style), m_width, col);
} }
@ -505,6 +556,13 @@ void wxPen::SetCap(wxPenCap cap)
M_PENDATA->SetCap(cap); M_PENDATA->SetCap(cap);
} }
void wxPen::SetQuality(wxPenQuality quality)
{
AllocExclusive();
M_PENDATA->SetQuality(quality);
}
wxColour wxPen::GetColour() const wxColour wxPen::GetColour() const
{ {
wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid pen") ); wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid pen") );
@ -540,6 +598,13 @@ wxPenCap wxPen::GetCap() const
return M_PENDATA->GetCap(); 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 int wxPen::GetDashes(wxDash** ptr) const
{ {
wxCHECK_MSG( IsOk(), -1, wxT("invalid pen") ); wxCHECK_MSG( IsOk(), -1, wxT("invalid pen") );

View File

@ -52,6 +52,7 @@ struct GraphicsBenchmarkOptions
mapMode = 0; mapMode = 0;
penWidth = 0; penWidth = 0;
penStyle = wxPENSTYLE_INVALID; penStyle = wxPENSTYLE_INVALID;
penQuality = wxPEN_QUALITY_DEFAULT;
width = 800; width = 800;
height = 600; height = 600;
@ -87,6 +88,7 @@ struct GraphicsBenchmarkOptions
numIters; numIters;
wxPenStyle penStyle; wxPenStyle penStyle;
wxPenQuality penQuality;
bool testBitmaps, bool testBitmaps,
testImages, testImages,
@ -410,16 +412,29 @@ private:
{ {
if ( opts.mapMode != 0 ) if ( opts.mapMode != 0 )
dc.SetMapMode((wxMappingMode)opts.mapMode); dc.SetMapMode((wxMappingMode)opts.mapMode);
bool setPen = false;
wxPenInfo penInfo(*wxWHITE);
if ( opts.penWidth != 0 ) if ( opts.penWidth != 0 )
dc.SetPen(wxPen(*wxWHITE, opts.penWidth)); {
penInfo.Width(opts.penWidth);
setPen = true;
}
if ( opts.penStyle != wxPENSTYLE_INVALID ) if ( opts.penStyle != wxPENSTYLE_INVALID )
{ {
wxPen pen = dc.GetPen(); penInfo.Style(opts.penStyle);
if ( !pen.IsOk() ) setPen = true;
pen = wxPen(*wxWHITE, 1);
pen.SetStyle(opts.penStyle);
dc.SetPen(pen);
} }
if ( opts.penQuality != wxPEN_QUALITY_DEFAULT )
{
penInfo.Quality(opts.penQuality);
setPen = true;
}
if ( setPen )
dc.SetPen(penInfo);
} }
void BenchmarkLines(const wxString& msg, wxDC& dc) 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, "m", "map-mode", "", wxCMD_LINE_VAL_NUMBER },
{ wxCMD_LINE_OPTION, "p", "pen-width", "", 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, "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, "w", "width", "", wxCMD_LINE_VAL_NUMBER },
{ wxCMD_LINE_OPTION, "h", "height", "", wxCMD_LINE_VAL_NUMBER }, { wxCMD_LINE_OPTION, "h", "height", "", wxCMD_LINE_VAL_NUMBER },
{ wxCMD_LINE_OPTION, "I", "images", "", 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 ) if ( parser.Found("w", &opts.width) && opts.width < 1 )
return false; return false;
if ( parser.Found("h", &opts.height) && opts.height < 1 ) if ( parser.Found("h", &opts.height) && opts.height < 1 )