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
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.

View File

@ -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;

View File

@ -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<wxPenInfo>(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;

View File

@ -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.

View File

@ -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<wxBitmap *>(&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") );

View File

@ -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 )