summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp195
-rw-r--r--src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h26
-rw-r--r--src/opengl/gl2paintengineex/qtriangulatingstroker.cpp299
-rw-r--r--src/opengl/gl2paintengineex/qtriangulatingstroker_p.h258
-rw-r--r--src/opengl/opengl.pro6
5 files changed, 730 insertions, 54 deletions
diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp
index 13efbda..bcc6bdb 100644
--- a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp
+++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp
@@ -81,6 +81,8 @@
#include "qglengineshadermanager_p.h"
#include "qgl2pexvertexarray_p.h"
+#include "qtriangulatingstroker_p.h"
+
#include <QDebug>
QT_BEGIN_NAMESPACE
@@ -572,7 +574,6 @@ void QGL2PaintEngineExPrivate::updateMatrix()
//
// We expand out the multiplication to save the cost of a full 4x4
// matrix multiplication as most of the components are trivial.
-
const QTransform& transform = q->state()->matrix;
if (mode == TextDrawingMode) {
@@ -628,6 +629,9 @@ void QGL2PaintEngineExPrivate::updateMatrix()
// The actual data has been updated so both shader program's uniforms need updating
simpleShaderMatrixUniformDirty = true;
shaderMatrixUniformDirty = true;
+
+ dasher.setInvScale(inverseScale);
+ stroker.setInvScale(inverseScale);
}
@@ -838,28 +842,6 @@ void QGL2PaintEngineExPrivate::transferMode(EngineMode newMode)
mode = newMode;
}
-void QGL2PaintEngineExPrivate::drawOutline(const QVectorPath& path)
-{
- transferMode(BrushDrawingMode);
-
- // Might need to call updateMatrix to re-calculate inverseScale
- if (matrixDirty)
- updateMatrix();
-
- vertexCoordinateArray.clear();
- vertexCoordinateArray.addPath(path, inverseScale);
-
- if (path.hasImplicitClose()) {
- // Close the path's outline
- vertexCoordinateArray.lineToArray(path.points()[0], path.points()[1]);
- vertexCoordinateArray.stops().last() += 1;
- }
-
- prepareForDraw(currentBrush->isOpaque());
- drawVertexArrays(vertexCoordinateArray, GL_LINE_STRIP);
-}
-
-
// Assumes everything is configured for the brush you want to use
void QGL2PaintEngineExPrivate::fill(const QVectorPath& path)
{
@@ -922,8 +904,14 @@ void QGL2PaintEngineExPrivate::fill(const QVectorPath& path)
}
-void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(QGL2PEXVertexArray& vertexArray, bool useWindingFill)
+void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(const float *data,
+ int count,
+ const QVector<int> *stops,
+ const QGLRect &bounds,
+ StencilFillMode mode)
{
+ Q_ASSERT(count || stops);
+
// qDebug("QGL2PaintEngineExPrivate::fillStencilWithVertexArray()");
glStencilMask(0xff); // Enable stencil writes
@@ -955,19 +943,20 @@ void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(QGL2PEXVertexArray& ve
}
#endif
- if (useWindingFill) {
+ if (mode == WindingFillMode) {
+ Q_ASSERT(stops && !count);
if (q->state()->clipTestEnabled) {
// Flatten clip values higher than current clip, and set high bit to match current clip
glStencilFunc(GL_LEQUAL, GL_STENCIL_HIGH_BIT | q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
- composite(vertexArray.boundingRect());
+ composite(bounds);
glStencilFunc(GL_EQUAL, GL_STENCIL_HIGH_BIT, GL_STENCIL_HIGH_BIT);
} else if (!stencilClean) {
// Clear stencil buffer within bounding rect
glStencilFunc(GL_ALWAYS, 0, 0xff);
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
- composite(vertexArray.boundingRect());
+ composite(bounds);
}
// Inc. for front-facing triangle
@@ -975,19 +964,43 @@ void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(QGL2PEXVertexArray& ve
// Dec. for back-facing "holes"
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_DECR_WRAP);
glStencilMask(~GL_STENCIL_HIGH_BIT);
- drawVertexArrays(vertexArray, GL_TRIANGLE_FAN);
+ drawVertexArrays(data, stops, GL_TRIANGLE_FAN);
if (q->state()->clipTestEnabled) {
// Clear high bit of stencil outside of path
glStencilFunc(GL_EQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
glStencilMask(GL_STENCIL_HIGH_BIT);
- composite(vertexArray.boundingRect());
+ composite(bounds);
}
- } else {
+ } else if (mode == OddEvenFillMode) {
+ glStencilMask(GL_STENCIL_HIGH_BIT);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit
+ drawVertexArrays(data, stops, GL_TRIANGLE_FAN);
+
+ } else { // TriStripStrokeFillMode
+ Q_ASSERT(count && !stops); // tristrips generated directly, so no vertexArray or stops
glStencilMask(GL_STENCIL_HIGH_BIT);
+#if 0
glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit
- drawVertexArrays(vertexArray, GL_TRIANGLE_FAN);
+ glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR);
+ glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, data);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
+ glDisableVertexAttribArray(QT_VERTEX_COORDS_ATTR);
+#else
+
+ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+ if (q->state()->clipTestEnabled) {
+ glStencilFunc(GL_LEQUAL, q->state()->currentClip | GL_STENCIL_HIGH_BIT,
+ ~GL_STENCIL_HIGH_BIT);
+ } else {
+ glStencilFunc(GL_ALWAYS, GL_STENCIL_HIGH_BIT, 0xff);
+ }
+ glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR);
+ glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, data);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
+ glDisableVertexAttribArray(QT_VERTEX_COORDS_ATTR);
+#endif
}
// Enable color writes & disable stencil writes
@@ -1122,14 +1135,15 @@ void QGL2PaintEngineExPrivate::composite(const QGLRect& boundingRect)
}
// Draws the vertex array as a set of <vertexArrayStops.size()> triangle fans.
-void QGL2PaintEngineExPrivate::drawVertexArrays(QGL2PEXVertexArray& vertexArray, GLenum primitive)
+void QGL2PaintEngineExPrivate::drawVertexArrays(const float *data, const QVector<int> *stops,
+ GLenum primitive)
{
// Now setup the pointer to the vertex array:
glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR);
- glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, vertexArray.data());
+ glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, data);
int previousStop = 0;
- foreach(int stop, vertexArray.stops()) {
+ foreach(int stop, *stops) {
/*
qDebug("Drawing triangle fan for vertecies %d -> %d:", previousStop, stop-1);
for (int i=previousStop; i<stop; ++i)
@@ -1180,12 +1194,29 @@ void QGL2PaintEngineEx::fill(const QVectorPath &path, const QBrush &brush)
{
Q_D(QGL2PaintEngineEx);
- if (qbrush_style(brush) == Qt::NoBrush)
+ Qt::BrushStyle style = qbrush_style(brush);
+ if (style == Qt::NoBrush)
return;
if (!d->inRenderText)
ensureActive();
+
+ QOpenGL2PaintEngineState *s = state();
+ bool doOffset = !(s->renderHints & QPainter::Antialiasing) && style == Qt::SolidPattern;
+
+ if (doOffset) {
+ d->temporaryTransform = s->matrix;
+ QTransform tx = QTransform::fromTranslate(.49, .49);
+ s->matrix = s->matrix * tx;
+ d->matrixDirty = true;
+ }
+
d->setBrush(&brush);
d->fill(path);
+
+ if (doOffset) {
+ s->matrix = d->temporaryTransform;
+ d->matrixDirty = true;
+ }
}
void QGL2PaintEngineEx::stroke(const QVectorPath &path, const QPen &pen)
@@ -1197,23 +1228,89 @@ void QGL2PaintEngineEx::stroke(const QVectorPath &path, const QPen &pen)
if (penStyle == Qt::NoPen || qbrush_style(penBrush) == Qt::NoBrush)
return;
+ QOpenGL2PaintEngineState *s = state();
+
ensureActive();
- qreal penWidth = qpen_widthf(pen);
- if ( (pen.isCosmetic() && (penStyle == Qt::SolidLine)) && (penWidth < 2.5f) )
- {
- // We only handle solid, cosmetic pens with a width of 1 pixel
- const QBrush& brush = pen.brush();
- d->setBrush(&brush);
+ bool doOffset = !(s->renderHints & QPainter::Antialiasing);
+ if (doOffset) {
+ d->temporaryTransform = s->matrix;
+ QTransform tx = QTransform::fromTranslate(0.49, .49);
+ s->matrix = s->matrix * tx;
+ d->matrixDirty = true;
+ }
- if (penWidth < 0.01f)
- glLineWidth(1.0);
- else
- glLineWidth(penWidth);
+ bool opaque = penBrush.isOpaque() && s->opacity > 0.99;
+ d->setBrush(&penBrush);
+ d->transferMode(BrushDrawingMode);
+
+ // updateMatrix() is responsible for setting the inverse scale on
+ // the strokers, so we need to call it here and not wait for
+ // prepareForDraw() down below.
+ d->updateMatrix();
+
+ if (penStyle == Qt::SolidLine) {
+ d->stroker.process(path, pen);
+
+ } else { // Some sort of dash
+ d->dasher.process(path, pen);
+
+ QVectorPath dashStroke(d->dasher.points(),
+ d->dasher.elementCount(),
+ d->dasher.elementTypes());
+ d->stroker.process(dashStroke, pen);
+ }
+
+
+ QGLContext *ctx = d->ctx;
+
+ if (opaque) {
+ d->prepareForDraw(opaque);
+ glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR);
+ glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, false, 0, d->stroker.vertices());
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, d->stroker.vertexCount() / 2);
+
+// QBrush b(Qt::green);
+// d->setBrush(&b);
+// d->prepareForDraw(true);
+// glDrawArrays(GL_LINE_STRIP, 0, d->stroker.vertexCount() / 2);
- d->drawOutline(path);
- } else
- return QPaintEngineEx::stroke(path, pen);
+ glDisableVertexAttribArray(QT_VERTEX_COORDS_ATTR);
+
+ } else {
+ qreal width = qpen_widthf(pen) / 2;
+ if (width == 0)
+ width = 0.5;
+ qreal extra = pen.joinStyle() == Qt::MiterJoin
+ ? qMax(pen.miterLimit() * width, width)
+ : width;
+
+ if (pen.isCosmetic())
+ extra = extra * d->inverseScale;
+
+ QRectF bounds = path.controlPointRect().adjusted(-extra, -extra, extra, extra);
+
+ d->fillStencilWithVertexArray(d->stroker.vertices(), d->stroker.vertexCount() / 2,
+ 0, bounds, QGL2PaintEngineExPrivate::TriStripStrokeFillMode);
+
+ glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+
+ // Pass when any bit is set, replace stencil value with 0
+ glStencilFunc(GL_NOTEQUAL, 0, GL_STENCIL_HIGH_BIT);
+ d->prepareForDraw(false);
+
+ // Stencil the brush onto the dest buffer
+ d->composite(bounds);
+
+ glStencilMask(0);
+
+ d->updateClipScissorTest();
+ }
+
+ if (doOffset) {
+ s->matrix = d->temporaryTransform;
+ d->matrixDirty = true;
+ }
}
void QGL2PaintEngineEx::penChanged() { }
@@ -1542,7 +1639,7 @@ void QGL2PaintEngineEx::drawPixmaps(const QDrawPixmaps::Data *drawingData, int d
s = qFastSin(drawingData[i].rotation * Q_PI / 180);
c = qFastCos(drawingData[i].rotation * Q_PI / 180);
}
-
+
qreal right = 0.5 * drawingData[i].scaleX * drawingData[i].source.width();
qreal bottom = 0.5 * drawingData[i].scaleY * drawingData[i].source.height();
QGLPoint bottomRight(right * c - bottom * s, right * s + bottom * c);
diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h
index 5704a04..209cd36 100644
--- a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h
+++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h
@@ -62,6 +62,7 @@
#include <private/qglpixmapfilter_p.h>
#include <private/qfontengine_p.h>
#include <private/qdatabuffer_p.h>
+#include <private/qtriangulatingstroker_p.h>
enum EngineMode {
ImageDrawingMode,
@@ -160,6 +161,12 @@ class QGL2PaintEngineExPrivate : public QPaintEngineExPrivate
{
Q_DECLARE_PUBLIC(QGL2PaintEngineEx)
public:
+ enum StencilFillMode {
+ OddEvenFillMode,
+ WindingFillMode,
+ TriStripStrokeFillMode
+ };
+
QGL2PaintEngineExPrivate(QGL2PaintEngineEx *q_ptr) :
q(q_ptr),
width(0), height(0),
@@ -185,15 +192,24 @@ public:
// fill, drawOutline, drawTexture & drawCachedGlyphs are the rendering entry points:
void fill(const QVectorPath &path);
- void drawOutline(const QVectorPath& path);
void drawTexture(const QGLRect& dest, const QGLRect& src, const QSize &textureSize, bool opaque, bool pattern = false);
void drawCachedGlyphs(const QPointF &p, QFontEngineGlyphCache::Type glyphType, const QTextItemInt &ti);
- void drawVertexArrays(QGL2PEXVertexArray& vertexArray, GLenum primitive);
+ void drawVertexArrays(const float *data, const QVector<int> *stops, GLenum primitive);
+ void drawVertexArrays(QGL2PEXVertexArray &vertexArray, GLenum primitive) {
+ drawVertexArrays((const float *) vertexArray.data(), &vertexArray.stops(), primitive);
+ }
+
// ^ draws whatever is in the vertex array
void composite(const QGLRect& boundingRect);
// ^ Composites the bounding rect onto dest buffer
- void fillStencilWithVertexArray(QGL2PEXVertexArray& vertexArray, bool useWindingFill);
+
+ void fillStencilWithVertexArray(const float *data, int count, const QVector<int> *stops, const QGLRect &bounds, StencilFillMode mode);
+ void fillStencilWithVertexArray(QGL2PEXVertexArray& vertexArray, bool useWindingFill) {
+ fillStencilWithVertexArray((const float *) vertexArray.data(), 0, &vertexArray.stops(),
+ vertexArray.boundingRect(),
+ useWindingFill ? WindingFillMode : OddEvenFillMode);
+ }
// ^ Calls drawVertexArrays to render into stencil buffer
bool prepareForDraw(bool srcPixelsAreOpaque);
@@ -266,6 +282,10 @@ public:
float textureInvertedY;
+ QTriangulatingStroker stroker;
+ QDashedStrokeProcessor dasher;
+ QTransform temporaryTransform;
+
QScopedPointer<QPixmapFilter> convolutionFilter;
QScopedPointer<QPixmapFilter> colorizeFilter;
QScopedPointer<QPixmapFilter> blurFilter;
diff --git a/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp b/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp
new file mode 100644
index 0000000..250dab6
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp
@@ -0,0 +1,299 @@
+#include "qtriangulatingstroker_p.h"
+#include <qmath.h>
+
+
+#define CURVE_FLATNESS Q_PI / 8
+
+
+
+
+void QTriangulatingStroker::endCapOrJoinClosed(const qreal *start, const qreal *cur,
+ bool implicitClose, bool endsAtStart)
+{
+ if (endsAtStart) {
+ join(start + 2);
+ } else if (implicitClose) {
+ join(start);
+ lineTo(start);
+ join(start+2);
+ } else {
+ endCap(cur);
+ }
+}
+
+
+void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen)
+{
+ const qreal *pts = path.points();
+ const QPainterPath::ElementType *types = path.elements();
+ int count = path.elementCount();
+ if (count < 2)
+ return;
+
+ float realWidth = qpen_widthf(pen);
+ if (realWidth == 0)
+ realWidth = 1;
+
+ m_width = realWidth / 2;
+
+ bool cosmetic = pen.isCosmetic();
+ if (cosmetic) {
+ m_width = m_width * m_inv_scale;
+ }
+
+ m_join_style = qpen_joinStyle(pen);
+ m_cap_style = qpen_capStyle(pen);
+ m_vertices.reset();
+ m_miter_limit = pen.miterLimit() * qpen_widthf(pen);
+
+ // The curvyness is based on the notion that I originally wanted
+ // roughly one line segment pr 4 pixels. This may seem little, but
+ // because we sample at constantly incrementing B(t) E [0<t<1], we
+ // will get longer segments where the curvature is small and smaller
+ // segments when the curvature is high.
+ //
+ // To get a rough idea of the length of each curve, I pretend that
+ // the curve is a 90 degree arc, whose radius is
+ // qMax(curveBounds.width, curveBounds.height). Based on this
+ // logic we can estimate the length of the outline edges based on
+ // the radius + a pen width and adjusting for scale factors
+ // depending on if the pen is cosmetic or not.
+ //
+ // The curvyness value of PI/14 was based on,
+ // arcLength=2*PI*r/4=PI/2 and splitting length into somewhere
+ // between 3 and 8 where 5 seemed to be give pretty good results
+ // hence: Q_PI/14. Lower divisors will give more detail at the
+ // direct cost of performance.
+
+ // simplfy pens that are thin in device size (2px wide or less)
+ if (realWidth < 2.5 && (cosmetic || m_inv_scale == 1)) {
+ if (m_cap_style == Qt::RoundCap)
+ m_cap_style = Qt::SquareCap;
+ if (m_join_style == Qt::RoundJoin)
+ m_join_style = Qt::MiterJoin;
+ m_curvyness_add = 0.5;
+ m_curvyness_mul = CURVE_FLATNESS;
+ m_roundness = 1;
+ } else if (cosmetic) {
+ m_curvyness_add = realWidth / 2;
+ m_curvyness_mul = CURVE_FLATNESS;
+ m_roundness = qMax<int>(4, realWidth * CURVE_FLATNESS);
+ } else {
+ m_curvyness_add = m_width;
+ m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
+ m_roundness = qMax<int>(4, realWidth * m_curvyness_mul);
+ }
+
+ // Over this level of segmentation, there doesn't seem to be any
+ // benefit, even for huge penWidth
+ if (m_roundness > 24)
+ m_roundness = 24;
+
+ m_sin_theta = qSin(Q_PI / m_roundness); // ### Use qFastSin
+ m_cos_theta = qCos(Q_PI / m_roundness);
+
+ const qreal *endPts = pts + (count<<1);
+ const qreal *startPts;
+
+ Qt::PenCapStyle cap = m_cap_style;
+
+ if (!types) {
+ startPts = pts;
+
+ bool endsAtStart = startPts[0] == *(endPts-2) && startPts[1] == *(endPts-1);
+
+ Qt::PenCapStyle cap = m_cap_style;
+ if (endsAtStart || path.hasImplicitClose())
+ m_cap_style = Qt::FlatCap;
+ moveTo(pts);
+ m_cap_style = cap;
+ pts += 2;
+ lineTo(pts);
+ pts += 2;
+ while (pts < endPts) {
+ join(pts);
+ lineTo(pts);
+ pts += 2;
+ }
+
+ endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
+
+ } else {
+ bool endsAtStart;
+ while (pts < endPts) {
+ switch (*types) {
+ case QPainterPath::MoveToElement: {
+ if (pts != path.points())
+ endCapOrJoinClosed(startPts, pts, path.hasImplicitClose(), endsAtStart);
+
+ startPts = pts;
+ int end = (endPts - pts) / 2;
+ int i = 2; // Start looking to ahead since we never have two moveto's in a row
+ while (i<end && types[i] != QPainterPath::MoveToElement) {
+ ++i;
+ }
+ endsAtStart = startPts[0] == pts[i*2 - 2] && startPts[1] == pts[i*2 - 1];
+ if (endsAtStart || path.hasImplicitClose())
+ m_cap_style = Qt::FlatCap;
+
+ moveTo(pts);
+ m_cap_style = cap;
+ pts+=2;
+ ++types;
+ break; }
+ case QPainterPath::LineToElement:
+ if (*(types - 1) != QPainterPath::MoveToElement)
+ join(pts);
+ lineTo(pts);
+ pts+=2;
+ ++types;
+ break;
+ case QPainterPath::CurveToElement:
+ if (*(types - 1) != QPainterPath::MoveToElement)
+ join(pts);
+ cubicTo(pts);
+ pts+=6;
+ types+=3;
+ break;
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+ }
+
+ endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
+ }
+}
+
+void QTriangulatingStroker::cubicTo(const qreal *pts)
+{
+ const QPointF *p = (const QPointF *) pts;
+ QBezier bezier = QBezier::fromPoints(*(p - 1), p[0], p[1], p[2]);
+
+ QRectF bounds = bezier.bounds();
+ float rad = qMax(bounds.width(), bounds.height());
+ int threshold = qMin<float>(64, (rad + m_curvyness_add) * m_curvyness_mul);
+ if (threshold < 4)
+ threshold = 4;
+ qreal threshold_minus_1 = threshold - 1;
+ float vx, vy;
+
+ float cx = m_cx, cy = m_cy;
+ float x, y;
+
+ for (int i=1; i<threshold; ++i) {
+ qreal t = qreal(i) / threshold_minus_1;
+ QPointF p = bezier.pointAt(t);
+ x = p.x();
+ y = p.y();
+
+ normalVector(cx, cy, x, y, &vx, &vy);
+
+ emitLineSegment(x, y, vx, vy);
+
+ cx = x;
+ cy = y;
+ }
+
+ m_cx = cx;
+ m_cy = cy;
+
+ m_nvx = vx;
+ m_nvy = vy;
+}
+
+
+
+static void qdashprocessor_moveTo(qreal x, qreal y, void *data)
+{
+ ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::MoveToElement, x, y);
+}
+
+static void qdashprocessor_lineTo(qreal x, qreal y, void *data)
+{
+ ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::LineToElement, x, y);
+}
+
+static void qdashprocessor_cubicTo(qreal, qreal, qreal, qreal, qreal, qreal, void *)
+{
+ Q_ASSERT(0); // The dasher should not produce curves...
+}
+
+QDashedStrokeProcessor::QDashedStrokeProcessor()
+ : m_dash_stroker(0), m_inv_scale(1)
+{
+ m_dash_stroker.setMoveToHook(qdashprocessor_moveTo);
+ m_dash_stroker.setLineToHook(qdashprocessor_lineTo);
+ m_dash_stroker.setCubicToHook(qdashprocessor_cubicTo);
+}
+
+void QDashedStrokeProcessor::process(const QVectorPath &path, const QPen &pen)
+{
+
+ const qreal *pts = path.points();
+ const QPainterPath::ElementType *types = path.elements();
+ int count = path.elementCount();
+
+ m_points.reset();
+ m_types.reset();
+
+ qreal width = pen.width();
+ if (width == 0)
+ width = 1;
+
+ m_dash_stroker.setDashPattern(pen.dashPattern());
+ m_dash_stroker.setStrokeWidth(width);
+ m_dash_stroker.setMiterLimit(pen.miterLimit());
+ qreal curvyness = sqrt(width) * m_inv_scale / 8;
+
+ if (count < 2)
+ return;
+
+ const qreal *endPts = pts + (count<<1);
+
+ m_dash_stroker.begin(this);
+
+ if (!types) {
+ m_dash_stroker.moveTo(pts[0], pts[1]);
+ pts += 2;
+ while (pts < endPts) {
+ m_dash_stroker.lineTo(pts[0], pts[1]);
+ pts += 2;
+ }
+ } else {
+ while (pts < endPts) {
+ switch (*types) {
+ case QPainterPath::MoveToElement:
+ m_dash_stroker.moveTo(pts[0], pts[1]);
+ pts += 2;
+ ++types;
+ break;
+ case QPainterPath::LineToElement:
+ m_dash_stroker.lineTo(pts[0], pts[1]);
+ pts += 2;
+ ++types;
+ break;
+ case QPainterPath::CurveToElement: {
+ QBezier b = QBezier::fromPoints(*(((const QPointF *) pts) - 1),
+ *(((const QPointF *) pts)),
+ *(((const QPointF *) pts) + 1),
+ *(((const QPointF *) pts) + 2));
+ QRectF bounds = b.bounds();
+ int threshold = qMin<float>(64, qMax(bounds.width(), bounds.height()) * curvyness);
+ if (threshold < 4)
+ threshold = 4;
+ qreal threshold_minus_1 = threshold - 1;
+ for (int i=0; i<threshold; ++i) {
+ QPointF pt = b.pointAt(i / threshold_minus_1);
+ m_dash_stroker.lineTo(pt.x(), pt.y());
+ }
+ pts += 6;
+ types += 3;
+ break; }
+ default: break;
+ }
+ }
+ }
+
+ m_dash_stroker.end();
+}
diff --git a/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h b/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h
new file mode 100644
index 0000000..a28fc45
--- /dev/null
+++ b/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h
@@ -0,0 +1,258 @@
+#ifndef QTRIANGULATINGSTROKER_P_H
+#define QTRIANGULATINGSTROKER_P_H
+
+#include <private/qdatabuffer_p.h>
+#include <private/qvectorpath_p.h>
+#include <private/qbezier_p.h>
+#include <private/qnumeric_p.h>
+#include <private/qmath_p.h>
+
+
+class QTriangulatingStroker
+{
+public:
+ void process(const QVectorPath &path, const QPen &pen);
+
+ inline int vertexCount() const { return m_vertices.size(); }
+ inline const float *vertices() const { return m_vertices.data(); }
+
+ inline void setInvScale(qreal invScale) { m_inv_scale = invScale; }
+
+private:
+ inline void emitLineSegment(float x, float y, float nx, float ny);
+ inline void moveTo(const qreal *pts);
+ inline void lineTo(const qreal *pts);
+ void cubicTo(const qreal *pts);
+ inline void join(const qreal *pts);
+ inline void normalVector(float x1, float y1, float x2, float y2, float *nx, float *ny);
+ inline void endCap(const qreal *pts);
+ inline void arc(float x, float y);
+ void endCapOrJoinClosed(const qreal *start, const qreal *cur, bool implicitClose, bool endsAtStart);
+
+
+ QDataBuffer<float> m_vertices;
+
+ float m_cx, m_cy; // current points
+ float m_nvx, m_nvy; // normal vector...
+ float m_width;
+ qreal m_miter_limit;
+
+ int m_roundness; // Number of line segments in a round join
+ qreal m_sin_theta; // sin(m_roundness / 360);
+ qreal m_cos_theta; // cos(m_roundness / 360);
+ qreal m_inv_scale;
+ float m_curvyness_mul;
+ float m_curvyness_add;
+
+ Qt::PenJoinStyle m_join_style;
+ Qt::PenCapStyle m_cap_style;
+};
+
+class QDashedStrokeProcessor
+{
+public:
+ QDashedStrokeProcessor();
+
+ void process(const QVectorPath &path, const QPen &pen);
+
+ inline void addElement(QPainterPath::ElementType type, qreal x, qreal y) {
+ m_points.add(x);
+ m_points.add(y);
+ m_types.add(type);
+ }
+
+ inline int elementCount() const { return m_types.size(); }
+ inline qreal *points() const { return m_points.data(); }
+ inline QPainterPath::ElementType *elementTypes() const { return m_types.data(); }
+
+ inline void setInvScale(qreal invScale) { m_inv_scale = invScale; }
+
+private:
+ QDataBuffer<qreal> m_points;
+ QDataBuffer<QPainterPath::ElementType> m_types;
+ QDashStroker m_dash_stroker;
+ qreal m_inv_scale;
+};
+
+
+
+
+
+inline void QTriangulatingStroker::normalVector(float x1, float y1, float x2, float y2,
+ float *nx, float *ny)
+{
+ float dx = x2 - x1;
+ float dy = y2 - y1;
+ float pw = m_width / sqrt(dx*dx + dy*dy);
+ *nx = -dy * pw;
+ *ny = dx * pw;
+}
+
+
+
+inline void QTriangulatingStroker::emitLineSegment(float x, float y, float vx, float vy)
+{
+ m_vertices.add(x + vx);
+ m_vertices.add(y + vy);
+ m_vertices.add(x - vx);
+ m_vertices.add(y - vy);
+}
+
+
+
+// We draw a full circle for any round join or round cap which is a
+// bit of overkill...
+inline void QTriangulatingStroker::arc(float x, float y)
+{
+ float dx = m_width;
+ float dy = 0;
+ for (int i=0; i<=m_roundness; ++i) {
+ float tmpx = dx * m_cos_theta - dy * m_sin_theta;
+ float tmpy = dx * m_sin_theta + dy * m_cos_theta;
+ dx = tmpx;
+ dy = tmpy;
+ emitLineSegment(x, y, dx, dy);
+ }
+}
+
+
+
+inline void QTriangulatingStroker::endCap(const qreal *pts)
+{
+ switch (m_cap_style) {
+ case Qt::FlatCap:
+ break;
+ case Qt::SquareCap: {
+ float dx = m_cx - *(pts - 2);
+ float dy = m_cy - *(pts - 1);
+
+ float len = m_width / sqrt(dx * dx + dy * dy);
+ dx = dx * len;
+ dy = dy * len;
+
+ emitLineSegment(m_cx + dx, m_cy + dy, m_nvx, m_nvy);
+ break; }
+ case Qt::RoundCap:
+ arc(m_cx, m_cy);
+ break;
+ default: break; // to shut gcc up...
+ }
+
+ int count = m_vertices.size();
+ m_vertices.add(m_vertices.at(count-2));
+ m_vertices.add(m_vertices.at(count-1));
+}
+
+
+void QTriangulatingStroker::moveTo(const qreal *pts)
+{
+ m_cx = pts[0];
+ m_cy = pts[1];
+
+ float x2 = pts[2];
+ float y2 = pts[3];
+ normalVector(m_cx, m_cy, x2, y2, &m_nvx, &m_nvy);
+
+
+ // To acheive jumps we insert zero-area tringles. This is done by
+ // adding two identical points in both the end of previous strip
+ // and beginning of next strip
+ bool invisibleJump = m_vertices.size();
+
+ switch (m_cap_style) {
+ case Qt::FlatCap:
+ if (invisibleJump) {
+ m_vertices.add(m_cx + m_nvx);
+ m_vertices.add(m_cy + m_nvy);
+ }
+ break;
+ case Qt::SquareCap: {
+ float dx = x2 - m_cx;
+ float dy = y2 - m_cy;
+ float len = m_width / sqrt(dx * dx + dy * dy);
+ dx = dx * len;
+ dy = dy * len;
+ float sx = m_cx - dx;
+ float sy = m_cy - dy;
+ if (invisibleJump) {
+ m_vertices.add(sx + m_nvx);
+ m_vertices.add(sy + m_nvy);
+ }
+ emitLineSegment(sx, sy, m_nvx, m_nvy);
+ break; }
+ case Qt::RoundCap:
+ if (invisibleJump) {
+ m_vertices.add(m_cx + m_nvx);
+ m_vertices.add(m_cy + m_nvy);
+ }
+
+ // This emitLineSegment is not needed for the arc, but we need
+ // to start where we put the invisibleJump vertex, otherwise
+ // we'll have visible triangles between subpaths.
+ emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
+ arc(m_cx, m_cy);
+ break;
+ default: break; // ssssh gcc...
+ }
+ emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
+}
+
+
+
+void QTriangulatingStroker::lineTo(const qreal *pts)
+{
+ emitLineSegment(pts[0], pts[1], m_nvx, m_nvy);
+ m_cx = pts[0];
+ m_cy = pts[1];
+}
+
+
+
+
+
+void QTriangulatingStroker::join(const qreal *pts)
+{
+ // Creates a join to the next segment (m_cx, m_cy) -> (pts[0], pts[1])
+ normalVector(m_cx, m_cy, pts[0], pts[1], &m_nvx, &m_nvy);
+
+ switch (m_join_style) {
+ case Qt::BevelJoin:
+ break;
+ case Qt::MiterJoin: {
+ int p1 = m_vertices.size() - 6;
+ int p2 = m_vertices.size() - 2;
+ QLineF line(m_vertices.at(p1), m_vertices.at(p1+1),
+ m_vertices.at(p2), m_vertices.at(p2+1));
+ QLineF nextLine(m_cx - m_nvx, m_cy - m_nvy,
+ pts[0] - m_nvx, pts[1] - m_nvy);
+
+ QPointF isect;
+ if (line.intersect(nextLine, &isect) != QLineF::NoIntersection
+ && QLineF(line.p2(), isect).length() <= m_miter_limit) {
+ // The intersection point mirrored over the m_cx, m_cy point
+ m_vertices.add(m_cx - (isect.x() - m_cx));
+ m_vertices.add(m_cy - (isect.y() - m_cy));
+
+ // The intersection point
+ m_vertices.add(isect.x());
+ m_vertices.add(isect.y());
+ }
+ // else
+ // Do a plain bevel join if the miter limit is exceeded or if
+ // the lines are parallel. This is not what the raster
+ // engine's stroker does, but it is both faster and similar to
+ // what some other graphics API's do.
+
+ break; }
+ case Qt::RoundJoin:
+ arc(m_cx, m_cy);
+ break;
+
+ default: break; // gcc warn--
+ }
+
+ emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
+}
+
+
+#endif
diff --git a/src/opengl/opengl.pro b/src/opengl/opengl.pro
index e561932..058016e 100644
--- a/src/opengl/opengl.pro
+++ b/src/opengl/opengl.pro
@@ -60,7 +60,8 @@ SOURCES += qgl.cpp \
gl2paintengineex/qgl2pexvertexarray_p.h \
gl2paintengineex/qpaintengineex_opengl2_p.h \
gl2paintengineex/qglengineshadersource_p.h \
- gl2paintengineex/qglcustomshaderstage_p.h
+ gl2paintengineex/qglcustomshaderstage_p.h \
+ gl2paintengineex/qtriangulatingstroker_p.h
SOURCES += qglshaderprogram.cpp \
qglpixmapfilter.cpp \
@@ -72,7 +73,8 @@ SOURCES += qgl.cpp \
gl2paintengineex/qglengineshadermanager.cpp \
gl2paintengineex/qgl2pexvertexarray.cpp \
gl2paintengineex/qpaintengineex_opengl2.cpp \
- gl2paintengineex/qglcustomshaderstage.cpp
+ gl2paintengineex/qglcustomshaderstage.cpp \
+ gl2paintengineex/qtriangulatingstroker.cpp
}