direct2d: Optimize dashed [poly]line drawing
Move the optimized dash drawing into stroke() in order to apply the optimization to polygons in addition to lines. In the case of polygons/polylines, a vertex patch is redrawn using the original dash brush in order to respect the joinStyle of the line. As the line correction code flows through both the optimized dashed path and the standard geometry code path, line adjustment is now also performed for normal geometry. Task-number: QTBUG-40604 Done-with: Louai Al-Khanji <louai.al-khanji@theqtcompany.com> Done-with: Andrew Knight <andrew.knight@theqtcompany.com> Change-Id: I668369b4aadb6a1bbbd4d621cb8ce1e3b19fbbc9 Reviewed-by: Louai Al-Khanji <louai.al-khanji@theqtcompany.com>
This commit is contained in:
parent
97f451a600
commit
dee1998d4e
@ -68,8 +68,6 @@ enum {
|
||||
D2DDebugFillRectTag,
|
||||
D2DDebugDrawRectsTag,
|
||||
D2DDebugDrawRectFsTag,
|
||||
D2DDebugDrawLinesTag,
|
||||
D2DDebugDrawLineFsTag,
|
||||
D2DDebugDrawEllipseTag,
|
||||
D2DDebugDrawEllipseFTag,
|
||||
D2DDebugDrawImageTag,
|
||||
@ -102,22 +100,28 @@ static inline ID2D1Factory1 *factory()
|
||||
return QWindowsDirect2DContext::instance()->d2dFactory();
|
||||
}
|
||||
|
||||
static inline D2D1_MATRIX_3X2_F transformFromLine(const QLineF &line, qreal penWidth)
|
||||
static inline D2D1_MATRIX_3X2_F transformFromLine(const QLineF &line, qreal penWidth, qreal dashOffset)
|
||||
{
|
||||
const qreal halfWidth = penWidth / 2;
|
||||
const qreal angle = -qDegreesToRadians(line.angle());
|
||||
QTransform transform = QTransform::fromTranslate(line.p1().x() + qSin(angle) * halfWidth,
|
||||
line.p1().y() - qCos(angle) * halfWidth);
|
||||
const qreal sinA = qSin(angle);
|
||||
const qreal cosA = qCos(angle);
|
||||
QTransform transform = QTransform::fromTranslate(line.p1().x() + dashOffset * cosA + sinA * halfWidth,
|
||||
line.p1().y() + dashOffset * sinA - cosA * halfWidth);
|
||||
transform.rotateRadians(angle);
|
||||
return to_d2d_matrix_3x2_f(transform);
|
||||
}
|
||||
|
||||
static void adjustLine(QPointF *p1, QPointF *p2);
|
||||
static bool isLinePositivelySloped(const QPointF &p1, const QPointF &p2);
|
||||
|
||||
class Direct2DPathGeometryWriter
|
||||
{
|
||||
public:
|
||||
Direct2DPathGeometryWriter()
|
||||
: m_inFigure(false)
|
||||
, m_roundCoordinates(false)
|
||||
, m_adjustPositivelySlopedLines(false)
|
||||
{
|
||||
|
||||
}
|
||||
@ -152,6 +156,11 @@ public:
|
||||
m_roundCoordinates = enable;
|
||||
}
|
||||
|
||||
void setPositiveSlopeAdjustmentEnabled(bool enable)
|
||||
{
|
||||
m_adjustPositivelySlopedLines = enable;
|
||||
}
|
||||
|
||||
bool isInFigure() const
|
||||
{
|
||||
return m_inFigure;
|
||||
@ -164,11 +173,20 @@ public:
|
||||
|
||||
m_sink->BeginFigure(adjusted(point), D2D1_FIGURE_BEGIN_FILLED);
|
||||
m_inFigure = true;
|
||||
m_previousPoint = point;
|
||||
}
|
||||
|
||||
void lineTo(const QPointF &point)
|
||||
{
|
||||
m_sink->AddLine(adjusted(point));
|
||||
QPointF pt = point;
|
||||
if (m_adjustPositivelySlopedLines && isLinePositivelySloped(m_previousPoint, point)) {
|
||||
moveTo(m_previousPoint - QPointF(0, 1));
|
||||
pt -= QPointF(0, 1);
|
||||
}
|
||||
m_sink->AddLine(adjusted(pt));
|
||||
if (pt != point)
|
||||
moveTo(point);
|
||||
m_previousPoint = point;
|
||||
}
|
||||
|
||||
void curveTo(const QPointF &p1, const QPointF &p2, const QPointF &p3)
|
||||
@ -180,6 +198,7 @@ public:
|
||||
};
|
||||
|
||||
m_sink->AddBezier(segment);
|
||||
m_previousPoint = p3;
|
||||
}
|
||||
|
||||
void close()
|
||||
@ -212,6 +231,8 @@ private:
|
||||
|
||||
bool m_inFigure;
|
||||
bool m_roundCoordinates;
|
||||
bool m_adjustPositivelySlopedLines;
|
||||
QPointF m_previousPoint;
|
||||
};
|
||||
|
||||
struct D2DVectorPathCache {
|
||||
@ -257,6 +278,7 @@ public:
|
||||
ComPtr<ID2D1Brush> brush;
|
||||
ComPtr<ID2D1StrokeStyle1> strokeStyle;
|
||||
ComPtr<ID2D1BitmapBrush1> dashBrush;
|
||||
int dashLength;
|
||||
|
||||
inline void reset() {
|
||||
emulate = false;
|
||||
@ -264,6 +286,7 @@ public:
|
||||
brush.Reset();
|
||||
strokeStyle.Reset();
|
||||
dashBrush.Reset();
|
||||
dashLength = 0;
|
||||
}
|
||||
} pen;
|
||||
|
||||
@ -566,6 +589,7 @@ public:
|
||||
D2D1_BITMAP_BRUSH_PROPERTIES1 bitmapBrushProperties = D2D1::BitmapBrushProperties1(
|
||||
D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_CLAMP, D2D1_INTERPOLATION_MODE_LINEAR);
|
||||
hr = dc()->CreateBitmapBrush(bitmap.bitmap(), bitmapBrushProperties, &pen.dashBrush);
|
||||
pen.dashLength = bitmap.size().width();
|
||||
} else {
|
||||
hr = factory()->CreateStrokeStyle(props, NULL, 0, &pen.strokeStyle);
|
||||
}
|
||||
@ -795,6 +819,8 @@ public:
|
||||
|
||||
writer.setWindingFillEnabled(path.hasWindingFill());
|
||||
writer.setAliasingEnabled(alias);
|
||||
writer.setPositiveSlopeAdjustmentEnabled(path.shape() == QVectorPath::LinesHint
|
||||
|| path.shape() == QVectorPath::PolygonHint);
|
||||
|
||||
const QPainterPath::ElementType *types = path.elements();
|
||||
const int count = path.elementCount();
|
||||
@ -910,6 +936,90 @@ public:
|
||||
DWRITE_MEASURING_MODE_GDI_CLASSIC);
|
||||
}
|
||||
|
||||
void stroke(const QVectorPath &path)
|
||||
{
|
||||
Q_Q(QWindowsDirect2DPaintEngine);
|
||||
|
||||
// Default path (no optimization)
|
||||
if (!(path.shape() == QVectorPath::LinesHint || path.shape() == QVectorPath::PolygonHint)
|
||||
|| !pen.dashBrush || q->state()->renderHints.testFlag(QPainter::HighQualityAntialiasing)) {
|
||||
ComPtr<ID2D1Geometry> geometry = vectorPathToID2D1PathGeometry(path);
|
||||
if (!geometry) {
|
||||
qWarning("%s: Could not convert path to d2d geometry", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
dc()->DrawGeometry(geometry.Get(), pen.brush.Get(), pen.qpen.widthF(), pen.strokeStyle.Get());
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimized dash line drawing
|
||||
const bool isPolygon = path.shape() == QVectorPath::PolygonHint && path.elementCount() >= 3;
|
||||
const bool implicitClose = isPolygon && (path.hints() & QVectorPath::ImplicitClose);
|
||||
const bool skipJoin = !isPolygon // Non-polygons don't require joins
|
||||
|| (pen.qpen.joinStyle() == Qt::MiterJoin && qFuzzyIsNull(pen.qpen.miterLimit()));
|
||||
const qreal *points = path.points();
|
||||
const int lastElement = path.elementCount() - (implicitClose ? 1 : 2);
|
||||
qreal dashOffset = 0;
|
||||
QPointF jointStart;
|
||||
ID2D1Brush *brush = pen.dashBrush ? pen.dashBrush.Get() : pen.brush.Get();
|
||||
for (int i = 0; i <= lastElement; ++i) {
|
||||
QPointF p1(points[i * 2], points[i * 2 + 1]);
|
||||
QPointF p2 = implicitClose && i == lastElement ? QPointF(points[0], points[1])
|
||||
: QPointF(points[i * 2 + 2], points[i * 2 + 3]);
|
||||
if (!isPolygon) // Advance the count for lines
|
||||
++i;
|
||||
|
||||
// Match raster engine output
|
||||
if (p1 == p2 && pen.qpen.widthF() <= 1.0) {
|
||||
q->fillRect(QRectF(p1, QSizeF(pen.qpen.widthF(), pen.qpen.widthF())), pen.qpen.brush());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!q->antiAliasingEnabled())
|
||||
adjustLine(&p1, &p2);
|
||||
|
||||
q->adjustForAliasing(&p1);
|
||||
q->adjustForAliasing(&p2);
|
||||
|
||||
const QLineF line(p1, p2);
|
||||
const qreal lineLength = line.length();
|
||||
if (pen.dashBrush) {
|
||||
pen.dashBrush->SetTransform(transformFromLine(line, pen.qpen.widthF(), dashOffset));
|
||||
dashOffset = pen.dashLength - fmod(lineLength - dashOffset, pen.dashLength);
|
||||
}
|
||||
dc()->DrawLine(to_d2d_point_2f(p1), to_d2d_point_2f(p2),
|
||||
brush, pen.qpen.widthF(), NULL);
|
||||
|
||||
if (skipJoin)
|
||||
continue;
|
||||
|
||||
// Patch the join with the original brush
|
||||
const qreal patchSegment = pen.dashBrush ? qBound(0.0, (pen.dashLength - dashOffset) / lineLength, 1.0)
|
||||
: pen.qpen.widthF();
|
||||
if (i > 0) {
|
||||
Direct2DPathGeometryWriter writer;
|
||||
writer.begin();
|
||||
writer.moveTo(jointStart);
|
||||
writer.lineTo(p1);
|
||||
writer.lineTo(line.pointAt(patchSegment));
|
||||
writer.close();
|
||||
dc()->DrawGeometry(writer.geometry().Get(), pen.brush.Get(), pen.qpen.widthF(), pen.strokeStyle.Get());
|
||||
}
|
||||
// Record the start position of the next joint
|
||||
jointStart = line.pointAt(1 - patchSegment);
|
||||
|
||||
if (implicitClose && i == lastElement) { // Close the polygon
|
||||
Direct2DPathGeometryWriter writer;
|
||||
writer.begin();
|
||||
writer.moveTo(jointStart);
|
||||
writer.lineTo(p2);
|
||||
writer.lineTo(QLineF(p2, QPointF(points[2], points[3])).pointAt(patchSegment));
|
||||
writer.close();
|
||||
dc()->DrawGeometry(writer.geometry().Get(), pen.brush.Get(), pen.qpen.widthF(), pen.strokeStyle.Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ComPtr<IDWriteFontFace> fontFaceFromFontEngine(QFontEngine *fe)
|
||||
{
|
||||
const QFontDef fontDef = fe->fontDef;
|
||||
@ -1051,20 +1161,12 @@ void QWindowsDirect2DPaintEngine::setState(QPainterState *s)
|
||||
|
||||
void QWindowsDirect2DPaintEngine::draw(const QVectorPath &path)
|
||||
{
|
||||
Q_D(QWindowsDirect2DPaintEngine);
|
||||
|
||||
ComPtr<ID2D1Geometry> geometry = d->vectorPathToID2D1PathGeometry(path);
|
||||
if (!geometry) {
|
||||
qWarning("%s: Could not convert path to d2d geometry", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
const QBrush &brush = state()->brush;
|
||||
if (qbrush_style(brush) != Qt::NoBrush) {
|
||||
if (emulationRequired(BrushEmulation))
|
||||
rasterFill(path, brush);
|
||||
else
|
||||
fill(geometry.Get(), brush);
|
||||
fill(path, brush);
|
||||
}
|
||||
|
||||
const QPen &pen = state()->pen;
|
||||
@ -1072,7 +1174,7 @@ void QWindowsDirect2DPaintEngine::draw(const QVectorPath &path)
|
||||
if (emulationRequired(PenEmulation))
|
||||
QPaintEngineEx::stroke(path, pen);
|
||||
else
|
||||
stroke(geometry.Get(), pen);
|
||||
stroke(path, pen);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1102,18 +1204,6 @@ void QWindowsDirect2DPaintEngine::fill(const QVectorPath &path, const QBrush &br
|
||||
d->dc()->FillGeometry(geometry.Get(), d->brush.brush.Get());
|
||||
}
|
||||
|
||||
void QWindowsDirect2DPaintEngine::fill(ID2D1Geometry *geometry, const QBrush &brush)
|
||||
{
|
||||
Q_D(QWindowsDirect2DPaintEngine);
|
||||
D2D_TAG(D2DDebugFillTag);
|
||||
|
||||
ensureBrush(brush);
|
||||
if (!d->brush.brush)
|
||||
return;
|
||||
|
||||
d->dc()->FillGeometry(geometry, d->brush.brush.Get());
|
||||
}
|
||||
|
||||
void QWindowsDirect2DPaintEngine::stroke(const QVectorPath &path, const QPen &pen)
|
||||
{
|
||||
Q_D(QWindowsDirect2DPaintEngine);
|
||||
@ -1131,25 +1221,7 @@ void QWindowsDirect2DPaintEngine::stroke(const QVectorPath &path, const QPen &pe
|
||||
if (!d->pen.brush)
|
||||
return;
|
||||
|
||||
ComPtr<ID2D1Geometry> geometry = d->vectorPathToID2D1PathGeometry(path);
|
||||
if (!geometry) {
|
||||
qWarning("%s: Could not convert path to d2d geometry", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
d->dc()->DrawGeometry(geometry.Get(), d->pen.brush.Get(), d->pen.qpen.widthF(), d->pen.strokeStyle.Get());
|
||||
}
|
||||
|
||||
void QWindowsDirect2DPaintEngine::stroke(ID2D1Geometry *geometry, const QPen &pen)
|
||||
{
|
||||
Q_D(QWindowsDirect2DPaintEngine);
|
||||
D2D_TAG(D2DDebugFillTag);
|
||||
|
||||
ensurePen(pen);
|
||||
if (!d->pen.brush)
|
||||
return;
|
||||
|
||||
d->dc()->DrawGeometry(geometry, d->pen.brush.Get(), d->pen.qpen.widthF(), d->pen.strokeStyle.Get());
|
||||
d->stroke(path);
|
||||
}
|
||||
|
||||
void QWindowsDirect2DPaintEngine::clip(const QVectorPath &path, Qt::ClipOperation op)
|
||||
@ -1297,88 +1369,6 @@ static void adjustLine(QPointF *p1, QPointF *p2)
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsDirect2DPaintEngine::drawLines(const QLine *lines, int lineCount)
|
||||
{
|
||||
Q_D(QWindowsDirect2DPaintEngine);
|
||||
D2D_TAG(D2DDebugDrawLinesTag);
|
||||
|
||||
ensurePen();
|
||||
|
||||
if (emulationRequired(PenEmulation)) {
|
||||
QPaintEngineEx::drawLines(lines, lineCount);
|
||||
} else if (d->pen.brush) {
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
QPointF p1 = lines[i].p1();
|
||||
QPointF p2 = lines[i].p2();
|
||||
|
||||
// Match raster engine output
|
||||
if (p1 == p2 && d->pen.qpen.widthF() <= 1.0) {
|
||||
fillRect(QRectF(p1, QSizeF(d->pen.qpen.widthF(), d->pen.qpen.widthF())),
|
||||
d->pen.qpen.brush());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match raster engine output
|
||||
if (!antiAliasingEnabled())
|
||||
adjustLine(&p1, &p2);
|
||||
|
||||
adjustForAliasing(&p1);
|
||||
adjustForAliasing(&p2);
|
||||
|
||||
D2D1_POINT_2F d2d_p1 = to_d2d_point_2f(p1);
|
||||
D2D1_POINT_2F d2d_p2 = to_d2d_point_2f(p2);
|
||||
|
||||
if (!d->pen.dashBrush || state()->renderHints.testFlag(QPainter::HighQualityAntialiasing)) {
|
||||
d->dc()->DrawLine(d2d_p1, d2d_p2, d->pen.brush.Get(), d->pen.qpen.widthF(), d->pen.strokeStyle.Get());
|
||||
} else {
|
||||
d->pen.dashBrush->SetTransform(transformFromLine(lines[i], d->pen.qpen.widthF()));
|
||||
d->dc()->DrawLine(d2d_p1, d2d_p2, d->pen.dashBrush.Get(), d->pen.qpen.widthF(), NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsDirect2DPaintEngine::drawLines(const QLineF *lines, int lineCount)
|
||||
{
|
||||
Q_D(QWindowsDirect2DPaintEngine);
|
||||
D2D_TAG(D2DDebugDrawLineFsTag);
|
||||
|
||||
ensurePen();
|
||||
|
||||
if (emulationRequired(PenEmulation)) {
|
||||
QPaintEngineEx::drawLines(lines, lineCount);
|
||||
} else if (d->pen.brush) {
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
QPointF p1 = lines[i].p1();
|
||||
QPointF p2 = lines[i].p2();
|
||||
|
||||
// Match raster engine output
|
||||
if (p1 == p2 && d->pen.qpen.widthF() <= 1.0) {
|
||||
fillRect(QRectF(p1, QSizeF(d->pen.qpen.widthF(), d->pen.qpen.widthF())),
|
||||
d->pen.qpen.brush());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match raster engine output
|
||||
if (!antiAliasingEnabled())
|
||||
adjustLine(&p1, &p2);
|
||||
|
||||
adjustForAliasing(&p1);
|
||||
adjustForAliasing(&p2);
|
||||
|
||||
D2D1_POINT_2F d2d_p1 = to_d2d_point_2f(p1);
|
||||
D2D1_POINT_2F d2d_p2 = to_d2d_point_2f(p2);
|
||||
|
||||
if (!d->pen.dashBrush || state()->renderHints.testFlag(QPainter::HighQualityAntialiasing)) {
|
||||
d->dc()->DrawLine(d2d_p1, d2d_p2, d->pen.brush.Get(), d->pen.qpen.widthF(), d->pen.strokeStyle.Get());
|
||||
} else {
|
||||
d->pen.dashBrush->SetTransform(transformFromLine(lines[i], d->pen.qpen.widthF()));
|
||||
d->dc()->DrawLine(d2d_p1, d2d_p2, d->pen.dashBrush.Get(), d->pen.qpen.widthF(), NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsDirect2DPaintEngine::drawEllipse(const QRectF &r)
|
||||
{
|
||||
Q_D(QWindowsDirect2DPaintEngine);
|
||||
|
@ -92,9 +92,6 @@ public:
|
||||
void drawRects(const QRect *rects, int rectCount) Q_DECL_OVERRIDE;
|
||||
void drawRects(const QRectF *rects, int rectCount) Q_DECL_OVERRIDE;
|
||||
|
||||
void drawLines(const QLine *lines, int lineCount) Q_DECL_OVERRIDE;
|
||||
void drawLines(const QLineF *lines, int lineCount) Q_DECL_OVERRIDE;
|
||||
|
||||
void drawEllipse(const QRectF &r) Q_DECL_OVERRIDE;
|
||||
void drawEllipse(const QRect &r) Q_DECL_OVERRIDE;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user